fix HOC to recive objects

This commit is contained in:
zmagod 2018-01-08 16:28:20 +01:00
parent c07f54a0c7
commit c5379896c0
5 changed files with 124 additions and 41 deletions

View file

@ -15,28 +15,34 @@ module ClientApi
def generate_permissions_object def generate_permissions_object
sanitize_permissions! sanitize_permissions!
@permissions = {} @permissions = {}
obj = @resource.fetch(:type)
.constantize
.public_send(:find_by_id, @resource.fetch(:id) {
raise ArgumentError, 'ID must be present'
}) if @resource
@required_permissions.collect do |permission|
parsed_permision = permission.gsub('can_', '')
if @resource if @resource
@required_permissions.collect do |permission| # return false if object does not exist
@permissions.merge!("#{permission}?" => @holder.eval(permission, result = obj ? @holder.eval('read_team', current_user, obj) : false
current_user, @permissions.merge!(permission => result)
@resource))
end
else else
@required_permissions.collect do |permission|
@permissions.merge!( @permissions.merge!(
"#{permission}?" => @holder.eval_generic(permission, current_user) permission => @holder.eval_generic(
parsed_permision, current_user
)
) )
end end
end end
end end
def sanitize_permissions! def sanitize_permissions!
@required_permissions = params.fetch(:parsePermission) do @required_permissions = params.fetch(:requiredPermissions) do
:permissions_array_missing :permissions_array_missing
end end
@holder = Canaid::PermissionsHolder.instance @holder = Canaid::PermissionsHolder.instance
@required_permissions.each do |permission| @required_permissions.each do |permission|
next if @holder.has_permission?(permission) next if @holder.has_permission?(permission.gsub('can_', ''))
# this error should happen only in development # this error should happen only in development
raise ArgumentError, "Method #{permission} has no related " \ raise ArgumentError, "Method #{permission} has no related " \
"permission registered." "permission registered."
@ -49,7 +55,7 @@ module ClientApi
def resource_valid? def resource_valid?
@resource = params[:resource] @resource = params[:resource]
return true unless @resource return true unless @resource
return true if Object.const_get(@resource.classify) return true if Object.const_get(@resource.fetch(:type).classify)
rescue NameError rescue NameError
return false return false
end end

View file

@ -5,10 +5,18 @@ import styled from "styled-components";
import { FormattedMessage, FormattedPlural } from "react-intl"; import { FormattedMessage, FormattedPlural } from "react-intl";
import { Button, Glyphicon } from "react-bootstrap"; import { Button, Glyphicon } from "react-bootstrap";
import { SETTINGS_NEW_TEAM_ROUTE } from "../../../../../config/routes"; import { SETTINGS_NEW_TEAM_ROUTE } from "../../../../../config/routes";
import * as Permissions from "../../../../../services/permissions"
const Wrapper = styled.div`margin: 15px 0;`; const Wrapper = styled.div`margin: 15px 0;`;
const TeamsPageDetails = ({ teams }) => { const TeamsPageDetails = ({ teams, permissions }) => {
const teamsNumber = teams.length; const teamsNumber = teams.length;
const newTeamButton = (
<LinkContainer to={SETTINGS_NEW_TEAM_ROUTE}>
<Button>
<Glyphicon glyph="plus" />&nbsp;<FormattedMessage id="global_team_switch.new_team" />
</Button>
</LinkContainer>
)
return ( return (
<Wrapper> <Wrapper>
<FormattedPlural <FormattedPlural
@ -30,11 +38,7 @@ const TeamsPageDetails = ({ teams }) => {
/> />
} }
/>&nbsp; />&nbsp;
<LinkContainer to={SETTINGS_NEW_TEAM_ROUTE}> {permissions.can_create_teams ? newTeamButton : ''}
<Button>
<Glyphicon glyph="plus" />&nbsp;<FormattedMessage id="global_team_switch.new_team" />
</Button>
</LinkContainer>
</Wrapper> </Wrapper>
); );
}; };
@ -53,7 +57,8 @@ TeamsPageDetails.propTypes = {
}; };
TeamsPageDetails.defaultProps = { TeamsPageDetails.defaultProps = {
teams: [] teams: [],
permissions: {}
}; };
export default TeamsPageDetails; export default Permissions.connect(TeamsPageDetails, ["can_create_teams"]);

View file

@ -7,10 +7,9 @@ export const getPermissionStatus = (
requiredPermissions: Array<string>, requiredPermissions: Array<string>,
resource: string resource: string
): Promise<*> => { ): Promise<*> => {
const parsePermission = requiredPermissions.map(el => el.replace("?", ""));
return axiosInstance return axiosInstance
.post(PERMISSIONS_PATH, { .post(PERMISSIONS_PATH, {
parsePermission, requiredPermissions,
resource resource
}) })
.then(({ data }) => data); .then(({ data }) => data);

View file

@ -7,14 +7,48 @@
Than you use the Permissions.connect method to wrap your component and Than you use the Permissions.connect method to wrap your component and
pass this "permissions helper methods" in your component params: pass this "permissions helper methods" in your component params:
If you need to specific model you have to specify it in the connect method
like the example below
> >
> Permissions.connect(MyComponent, ["can_update_team?", "can_read_team?"], "Team"); > Permissions.connect(MyComponent, ["can_update_team", "can_read_team"], "Team");
> >
In case your component is connected to Redux or some other HOC you can simply
chain the HOC's
>
> const mapStateToProps = ({ current_team }) => ({ current_team });
> const MyComponentWithPermissions = Permissions.connect(MyComponent, ["can_read_team"], "Team");
> export default connect(mapStateToProps)(MyComponentWithPermissions);
>
beside the permissions object you get the setPermissionResourceId/1 function
in your component props. Than you can pass the id of the resource when you have it.
Example: you need the current team for whatever reason you can call it in the
shouldComponentUpdate function...
> shouldComponentUpdate(nextProps: any, nextState: any) {
> this.props.setPermissionResourceId(this.props.current_team.id);
> return true; // remember to return true!
> }
JUST REMEMBER THAT YOU HAVE PASS A VALID ID and you have to check if is present
at the moment of execution. THE REQUEST WILL BE TRIGGERED WHEN YOU INVOKE
setPermissionResourceId/1 FUNCTION!!!!
else you can pass just the perrmissions on the user
>
> Permissions.connect(MyComponent, ["can_update"])
>
THE REQUEST WILL BE TRIGGERED WHEN THE COMPONENT MOUNTS!!!
Now you can access your permissions through component params. The permissions Now you can access your permissions through component params. The permissions
you required have 3 states [true, false, null]. Null is when you are waiting for server response. you required have 3 states [true, false, null]. Null is when you are waiting for server response.
You can use methods params.can_update_team? or whatever permissions you declare
Finally you can access to your permissions in the component using
props.permissions.can_.... whatever you specify. The props.permissions is
basically an object that holds all required permissions.
*/ */
import * as React from "react"; import * as React from "react";
import { getPermissionStatus } from "../api/permissions_api"; import { getPermissionStatus } from "../api/permissions_api";
@ -22,15 +56,20 @@ type State = {
permissions: any permissions: any
}; };
type ResourceObject = {
type: string,
id: number
};
type PermissionsObject = { type PermissionsObject = {
[string]: boolean | null [string]: boolean | null
} };
/* /*
This function accepts 3 arguments which are REQUIRED This function accepts 3 arguments which are REQUIRED
1.) WrappedComponent: Component that you want to have permissions 1.) @WrappedComponent: Component that you want to have permissions
2.) requiredPermissions: an array of strings with permissions methods that 2.) @requiredPermissions: an array of strings with permissions methods that
will be available in your component will be available in your component
3.) resource: a string of reference/model name 3.) @resource: a string of reference/model name
*/ */
export function connect<Props: {}>( export function connect<Props: {}>(
WrappedComponent: React.ComponentType<Props>, WrappedComponent: React.ComponentType<Props>,
@ -41,31 +80,55 @@ export function connect<Props: {}>(
requiredPermissions.forEach(el => { requiredPermissions.forEach(el => {
parsedPermissions[el] = null; parsedPermissions[el] = null;
}); });
return class extends React.Component<*, State> { return class extends React.Component<*, State> {
constructor(props: any) { constructor(props: any) {
super(props); super(props);
this.state = { permissions: parsedPermissions }; this.state = {
permissions: parsedPermissions,
permissionsRequestDone: false
};
(this: any).getPermissions = this.getPermissions.bind(this);
(this: any).setPermissionResourceId = this.setPermissionResourceId.bind(
this
);
} }
componentDidMount(): void { componentDidMount(): void {
getPermissionStatus(requiredPermissions, resource) if (!resource) {
this.getPermissions(requiredPermissions, resource);
}
}
setPermissionResourceId(id: number): void {
this.getPermissions(requiredPermissions, { type: resource, id });
}
getPermissions(
permissionsArray: Array<string>,
resourceObject: ResourceObject | undefined
): void {
if (!this.state.permissionsRequestDone) {
getPermissionStatus(permissionsArray, resourceObject)
.then(data => { .then(data => {
this.setState({ permissions: data }); this.setState({ permissions: data, permissionsRequestDone: true });
}) })
.catch(() => { .catch(() => {
const permissions: PermissionsObject = {}; const permissions: PermissionsObject = {};
requiredPermissions.forEach(el => { permissionsArray.forEach(el => {
permissions[el] = false; permissions[el] = false;
}); });
this.setState({ permissions }); this.setState({ permissions });
}); });
} }
}
render(): any { render(): any {
return ( return (
<WrappedComponent <WrappedComponent
permissions={this.state.permissions} permissions={this.state.permissions}
setPermissionResourceId={
resource ? this.setPermissionResourceId : undefined
}
{...this.props} {...this.props}
/> />
); );

View file

@ -4,10 +4,20 @@ describe ClientApi::PermissionsController, type: :controller do
login_user login_user
describe '#status' do describe '#status' do
let!(:user) { User.first || create(:user) }
let!(:team) { create :team, created_by: user }
let!(:user_team) { create :user_team, user: user, team: team, role: 2 }
let(:params) do let(:params) do
{ parsePermission: ['can_view_team'], resource: 'UserTeam' } { requiredPermissions: ['can_read_team'],
resource: { type: 'Team', id: team.id } }
end end
let(:subject) { post :status, format: :json, params: params } let(:subject) { post :status, format: :json, params: params }
it { is_expected.to be_success } it { is_expected.to be_success }
it 'returns an object with the permission' do
body = JSON.parse(subject.body)
expect(body).to eq('can_read_team' => true)
end
end end
end end