diff --git a/app/controllers/client_api/permissions_controller.rb b/app/controllers/client_api/permissions_controller.rb index 807f36a27..4d893211b 100644 --- a/app/controllers/client_api/permissions_controller.rb +++ b/app/controllers/client_api/permissions_controller.rb @@ -15,28 +15,34 @@ module ClientApi def generate_permissions_object sanitize_permissions! @permissions = {} - if @resource - @required_permissions.collect do |permission| - @permissions.merge!("#{permission}?" => @holder.eval(permission, - current_user, - @resource)) - end - else - @required_permissions.collect do |permission| + 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 + # return false if object does not exist + result = obj ? @holder.eval('read_team', current_user, obj) : false + @permissions.merge!(permission => result) + else @permissions.merge!( - "#{permission}?" => @holder.eval_generic(permission, current_user) + permission => @holder.eval_generic( + parsed_permision, current_user + ) ) end end end def sanitize_permissions! - @required_permissions = params.fetch(:parsePermission) do + @required_permissions = params.fetch(:requiredPermissions) do :permissions_array_missing end @holder = Canaid::PermissionsHolder.instance @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 raise ArgumentError, "Method #{permission} has no related " \ "permission registered." @@ -49,7 +55,7 @@ module ClientApi def resource_valid? @resource = params[: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 return false end diff --git a/app/javascript/src/scenes/SettingsPage/scenes/teams/components/TeamsPageDetails.jsx b/app/javascript/src/scenes/SettingsPage/scenes/teams/components/TeamsPageDetails.jsx index 1d946b66e..168542018 100644 --- a/app/javascript/src/scenes/SettingsPage/scenes/teams/components/TeamsPageDetails.jsx +++ b/app/javascript/src/scenes/SettingsPage/scenes/teams/components/TeamsPageDetails.jsx @@ -5,10 +5,18 @@ import styled from "styled-components"; import { FormattedMessage, FormattedPlural } from "react-intl"; import { Button, Glyphicon } from "react-bootstrap"; import { SETTINGS_NEW_TEAM_ROUTE } from "../../../../../config/routes"; +import * as Permissions from "../../../../../services/permissions" const Wrapper = styled.div`margin: 15px 0;`; -const TeamsPageDetails = ({ teams }) => { +const TeamsPageDetails = ({ teams, permissions }) => { const teamsNumber = teams.length; + const newTeamButton = ( + + + + ) return ( { /> } />  - - - + {permissions.can_create_teams ? newTeamButton : ''} ); }; @@ -53,7 +57,8 @@ TeamsPageDetails.propTypes = { }; TeamsPageDetails.defaultProps = { - teams: [] + teams: [], + permissions: {} }; -export default TeamsPageDetails; +export default Permissions.connect(TeamsPageDetails, ["can_create_teams"]); diff --git a/app/javascript/src/services/api/permissions_api.js b/app/javascript/src/services/api/permissions_api.js index cdfd776f9..3305a6078 100644 --- a/app/javascript/src/services/api/permissions_api.js +++ b/app/javascript/src/services/api/permissions_api.js @@ -7,10 +7,9 @@ export const getPermissionStatus = ( requiredPermissions: Array, resource: string ): Promise<*> => { - const parsePermission = requiredPermissions.map(el => el.replace("?", "")); return axiosInstance .post(PERMISSIONS_PATH, { - parsePermission, + requiredPermissions, resource }) .then(({ data }) => data); diff --git a/app/javascript/src/services/permissions/index.js b/app/javascript/src/services/permissions/index.js index 49bfd7c70..667e2c66a 100644 --- a/app/javascript/src/services/permissions/index.js +++ b/app/javascript/src/services/permissions/index.js @@ -7,14 +7,48 @@ Than you use the Permissions.connect method to wrap your component and 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 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 { getPermissionStatus } from "../api/permissions_api"; @@ -22,15 +56,20 @@ type State = { permissions: any }; +type ResourceObject = { + type: string, + id: number +}; + type PermissionsObject = { [string]: boolean | null -} +}; /* This function accepts 3 arguments which are REQUIRED - 1.) WrappedComponent: Component that you want to have permissions - 2.) requiredPermissions: an array of strings with permissions methods that + 1.) @WrappedComponent: Component that you want to have permissions + 2.) @requiredPermissions: an array of strings with permissions methods that 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( WrappedComponent: React.ComponentType, @@ -41,31 +80,55 @@ export function connect( requiredPermissions.forEach(el => { parsedPermissions[el] = null; }); - return class extends React.Component<*, State> { constructor(props: any) { 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 { - getPermissionStatus(requiredPermissions, resource) - .then(data => { - this.setState({ permissions: data }); - }) - .catch(() => { - const permissions: PermissionsObject = {}; - requiredPermissions.forEach(el => { - permissions[el] = false; + if (!resource) { + this.getPermissions(requiredPermissions, resource); + } + } + + setPermissionResourceId(id: number): void { + this.getPermissions(requiredPermissions, { type: resource, id }); + } + + getPermissions( + permissionsArray: Array, + resourceObject: ResourceObject | undefined + ): void { + if (!this.state.permissionsRequestDone) { + getPermissionStatus(permissionsArray, resourceObject) + .then(data => { + this.setState({ permissions: data, permissionsRequestDone: true }); + }) + .catch(() => { + const permissions: PermissionsObject = {}; + permissionsArray.forEach(el => { + permissions[el] = false; + }); + this.setState({ permissions }); }); - this.setState({ permissions }); - }); + } } render(): any { return ( ); diff --git a/spec/controllers/client_api/permissions_controller_spec.rb b/spec/controllers/client_api/permissions_controller_spec.rb index 8d7698a77..710ded152 100644 --- a/spec/controllers/client_api/permissions_controller_spec.rb +++ b/spec/controllers/client_api/permissions_controller_spec.rb @@ -4,10 +4,20 @@ describe ClientApi::PermissionsController, type: :controller do login_user 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 - { parsePermission: ['can_view_team'], resource: 'UserTeam' } + { requiredPermissions: ['can_read_team'], + resource: { type: 'Team', id: team.id } } end + let(:subject) { post :status, format: :json, params: params } 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