Giter VIP home page Giter VIP logo

aor-permissions's Introduction

archived Archived Repository
This code is no longer maintained. Feel free to fork it, but use it at your own risks.

aor-permissions

This project is discontinued. Its features have been merged in admin-on-rest 1.3.0. See admin-on-rest documentation

Build Status

A component for Admin-on-rest allowing to switch views depending on user roles.

Installation

Install with:

npm install --save aor-permissions

or

yarn add aor-permissions

Usage

First, the authClient must handle a new type: AUTH_GET_PERMISSIONS.

Here is a naive implementation using localstorage:

// in authClient.js
import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_CHECK, AUTH_ERROR } from 'admin-on-rest';
import { AUTH_GET_PERMISSIONS } from 'aor-permissions';
import { decode } from 'jsonwebtoken';

export default (type, params) => {
    if (type === AUTH_LOGIN) {
        const { username, password } = params;
        const request = new Request('https://mydomain.com/authenticate', {
            method: 'POST',
            body: JSON.stringify({ username, password }),
            headers: new Headers({ 'Content-Type': 'application/json' }),
        })
        return fetch(request)
            .then(response => {
                if (response.status < 200 || response.status >= 300) {
                    throw new Error(response.statusText);
                }
                return response.json();
            })
            .then(({ token, permissions }) => {
                const decoded = decode(token);
                localStorage.setItem('token', token);
                localStorage.setItem('permissions', decoded.permissions);
            });
    }
    // ... usual authClient code

    if (type === AUTH_GET_PERMISSIONS) {
        return Promise.resolve(localStorage.getItem('permissions'));
    }
};

Then, you may use the SwitchPermissions and Permission components:

Simple permissions check

// In products.js
import { SwitchPermissions, Permission } from 'aor-permissions';
import authClient from '../authClient';

// ...other views as usual (List, Create, etc.)

// Use this ProductEdit component as usual in your resource declaration
export const ProductEdit = props => (
    <SwitchPermissions authClient={authClient} {...props}>
        <Permission value="role1">
            <Edit {...props}>
                {/* Usual layout component */}
            </Edit>
        </Permission>
        <Permission value={['role2', 'role3']}>
            <Edit {...props}>
                {/* Usual layout component */}
            </Edit>
        </Permission>
        <Permission value={['role2', 'role3']} exact>
            <Edit {...props}>
                {/* Usual layout component */}
            </Edit>
        </Permission>
    </SwitchPermissions>
);

Permissions check depending on the resource/record

// In products.js
import { SwitchPermissions, Permission } from 'aor-permissions';
import authClient from '../authClient';

// ...other views as usual (List, Create, etc.)

const checkUserCanEdit = (params) => {
    const user = params.permissions; // This is the result of the `authClient` call with type `AUTH_GET_PERMISSIONS`
    const resource = params.resource; // The resource, eg: 'posts'
    const record = params.record; // The current record (only supplied for Edit)

    // Only user with admin role can edit the posts of the 'announcements' category
    if (record.category === 'announcement' && user.role === 'admin') {
        return true;
    }

    return false;
}

// Use this PostEdit component as usual in your resource declaration
// Note that in order to get the record, we must have the SwitchPermissions component inside the Edit component
export const PostEdit = props => (
    <Edit {...props}>
        <SwitchPermissions authClient={authClient} {...props}>
            <Permission resolve={checkUserCanEdit}>
                {/* Usual layout component */}
            </Permission>
            <Permission resolve={checkUserCanEdit}>
                {/* Usual layout component */}
            </Permission>
        </SwitchPermissions>
    </Edit>
);

Protect access to resources

import { Admin } from 'aor-permissions';
import { Resource } from 'admin-on-rest';
import restClient from '../restClient';
import authClient from '../authClient';
import { PostList, PostEdit, PostCreate } from './posts';

const resolveAccessToPosts = ({ resource, permissions, exact, value }) => {
    // value = the requested permissions specified in the `permissions` prop (eg `admin`). May be undefined
    // resource = the requested resource (eg `posts`)
    // exact = the value of the `exact` prop
    // permissions = the result of the authClient call
};

const resolveEditAccess = ({ resource, permissions, exact, value }) => {
    // value = the requested permissions specified in the `permissions` prop (eg `admin`). May be undefined
    // resource = the requested resource (eg `posts`)
    // exact = the value of the `exact` prop
    // permissions = the result of the authClient call
};

const App = () => (
    <Admin restClient={restClient} authClient={authClient}>
        <Resource
            name="posts"
            resolve={resolveAccessToPosts}
            list={PostList}
            edit={PostEdit}
            editPermissions="admin"
            editResolve={resolveEditAccess}
            create={PostCreate}
            createPermissions="admin"
            createExact={true}
        />
    </Admin>
);

API

SwitchPermissions

The SwitchPermissions component requires an authClient prop which accepts the same (authClient) as in Admin-on-rest. However, this client must be able to handle the new AUTH_GET_PERMISSIONS type.

It also accepts two optional props:

  • loading: A component to display while checking for permissions. It defaults to the Material-UI LinearProgress in indeterminate mode.
  • notFound: A component to display when no match was found while checking the permissions. Default to null.

The SwitchPermissions component only accepts Permission components as children. They are used to map a permission to a view.

Permission

The Permission component requires either a value with the permissions to check (could be a role, an array of rules, etc) or a resolve function.

If both are specified, only resolve will be used.

You can pass anything as children for this component: a view (List, Create, Edit), an input, a React node, whatever.

Using the value prop

Permissions matches differently depending on the value type and the authClient result type.

An additional exact prop may be specified on the Permission component depending on your requirements.

The following table shows how permissions are resolved:

permissions authClient result exact resolve
single value single value permissions must equal authClient result
single value array authClient result must contain permissions
array single value permissions must contain authClient result
 array array false at least one value of permissions must be present in authClient result
 array array true all values in permissions must be present in authClient result

Using the resolve prop

The function specified for resolve may return true or false directly or a promise resolving to either true or false. It will be called with an object having the following properties:

  • permissions: the result of the authClient call.
  • resource: the resource being checked (eg: products, posts, etc.)
  • value: the value of the value prop
  • exact: the value of the exact prop
  • record: Only when inside an Edit component, the record being edited

If multiple matches are found, the first one will be applied.

WithPermission

A simpler component which will render its children only if its permissions are matched. For example, in a custom Menu:

import React from 'react';
import { Link } from 'react-router-dom';
import MenuItem from 'material-ui/MenuItem';
import SettingsIcon from 'material-ui/svg-icons/action/settings';
import { WithPermission } from 'aor-permissions';
import authClient from './authClient';

const Menu = ({ onMenuTap, logout }) => (
    <div>
        {/* Other menu items */}

        <WithPermission authClient={authClient} value="admin">
            <MenuItem
                containerElement={<Link to="/configuration" />}
                primaryText="Configuration"
                leftIcon={<SettingsIcon />}
                onTouchTap={onMenuTap}
            />
        </WithPermission>
        {logout}
    </div>
);

export default Menu;

The WithPermission component accepts the following props:

  • authClient: the same (authClient) as in Admin-on-rest. However, this client must be able to handle the new AUTH_GET_PERMISSIONS type.
  • value: the permissions to check (could be a role, an array of rules, etc)
  • resolve: a function called to resolve the permissions. (same as Permission)

You can pass anything as children for this component: a view (List, Create, Edit), an input, a React node, whatever.

AuthProvider

Requiring and specifying the authClient for each SwitchPermissions and WithPermission components could be cumbersome. That's why we also provided an AuthProvider component.

It must enclose the Admin component.

It accepts a single prop: authClient.

import { Admin, Resource } from 'admin-on-rest';
import { AuthProvider } from 'aor-permissions';
import authClient from './authClient';

export const App = () => (
    <AuthProvider authClient={authClient}>
        <Admin>
            {/* Usual Resource components */}
        </Admin>
    </AuthProvider>
)

Admin

This component can be used instead of the default Admin component from admin-on-rest.

It allows to define permissions on each resource and for each resource's view.

It accepts the following props:

  • permissions: to define the permissions required for the whole resource
  • resolve: function called to check whether permissions for the whole resource are ok
  • exact: Boolean for exact match (useful when permissions is an array)

If defined, the resolve function will be called with an object containing the following properties:

  • permissions: the result of the authClient call.
  • value: the value of the permissions prop: the requested permissions
  • exact: the value of the exact prop

Additionnaly, the Admin components accepts for each view (list, create, edit and remove) the same three props prefixed with the view's name. For example: listPermissions, listResolve and listExact.

Note: when using this custom Admin component, there's no need to use the AuthProvider too as it will be added automatically.

Contributing

Run the tests with this command:

make test

Coverage data is available in ./coverage after executing make test.

An HTML report is generated in ./coverage/lcov-report/index.html.

aor-permissions's People

Contributors

alexisjanvier avatar djhi avatar fzaninotto avatar kmaschta avatar mateussmohamed avatar natrim avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aor-permissions's Issues

withPermission.js:182 Uncaught (in promise) TypeError: Cannot read property 'source' of undefined

Getting below uncaught exception when used for menu rendering
withPermission.js:182 Uncaught (in promise) TypeError: Cannot read property 'source' of undefined
Have Used authprovider for authclient
Code:

const Menu = ({onMenuTap, translate, logout}) => (
    <div style={styles.main}>
        <WithPermission value="Admin">
            <DashboardMenuItem onTouchTap={onMenuTap}/> {adminItems.map(item => (

                <MenuItem
                    key={item.resource}
                    containerElement={< Link to = {
                    `/${item.resource}`
                } />}
                    primaryText={item.name}
                    leftIcon={item.icon}
                    onTouchTap={onMenuTap}/>

            ))}
        </WithPermission>
        {logout}
    </div>
);

WithPermission: Warning about unknown prop if used inside menu

There is Warning about unknown prop if used inside menu

Warning: Unknown prop resolve on <a> tag. Remove this prop from the element.

When used like in example for menu, just with resolve

const checkPermission = (params) => { return true; };

<WithPermission authClient={authClient} resolve={checkPermission}> <MenuItem containerElement={<Link to="/configuration" />} primaryText="Configuration" leftIcon={<SettingsIcon />} onTouchTap={onMenuTap} /> </WithPermission>

The problem with <WithPermission>

export const PhotoList = (props) => (


...
<WithPermission value={['admin', 'editor']} >

  <WithPermission value={['admin', 'editor']} >
    <DeleteButton />
  </WithPermission>
</Datagrid>
);

In this design the buttons: DeleteButton / ShowButton create first request: GET/photos/undefined
instead of (for example): GET/photos/8ba93c09-468d-49ac-bbc2-90f2af0ae4b1

Permit disable actions on role permissions

I have a project with admin-on-rest where I need to condition permissions granted to the user, through the roles, to enable or disable the created, delete, edit, show and list actions.

The aor-permissions component does not seem to allow this.
I just managed to disable the buttons in the list, but of course, if you disable, for example, the delete in the list, it still appears in the show.
The ideal would be to act when defining the resource with something like this code:

import React from 'react';
import { simpleRestClient, Admin, Resource, Delete } from 'admin-on-rest';
import { can } from 'aor-roles';

import { PostList, PostCreate, PostEdit, PostShow } from './posts';

const App = () => (
  <AuthProvider authClient={authClient}>
    <Admin restClient={simpleRestClient('http://path.to.my.api')}>
      <Resource
        name="posts"
        list={can('posts.list') && PostList}
        create={can('posts.create') && PostCreate}
        edit={can('posts.edit') && PostEdit}
        show={can('posts.show') && PostShow}
        remove={can('posts.remove') && Delete}
      } />
    </Admin>
  </AuthProvider>
);

export default App;

I did not find a method to let inject a function like can over it to be re-evaluated after a disconnect and a connect of another user with different permissions.

BUG: WithPermission breaks EditButton in View mode

Hi I am using WithPermission around an EditButton:

const cardActionStyle = {
    zIndex: 2,
    display: 'inline-block',
    float: 'right',
};
const Actions = ({ resource, filters, displayedFilters, filterValues, basePath, showFilter, refresh }) => (
    <CardActions style={cardActionStyle}>
        {filters && React.cloneElement(filters, { resource, showFilter, displayedFilters, filterValues, context: 'button' }) }
        <WithPermission authClient={authClient} value={['admin']}>
            <CreateButton basePath={basePath} translate={true}/>
        </WithPermission>
        <FlatButton primary label="refresh" onClick={refresh} icon={<NavigationRefresh />} />
    </CardActions>
);

export const CompanyList = (props) => (
    <List title="All companies" {...props} filters={<CompanyFilter/>} actions={<Actions />} sort={{field: 'id', order: 'DESC'}} perPage={2}>
        <Datagrid>
            <TextField source="id"/>
            <TextField source="name"/>

            <ReferenceArrayField label="Client" source="clientId" reference="clients" sortable={false}>
                <SingleFieldList>
                    <ChipField source="name"/>
                </SingleFieldList>
            </ReferenceArrayField>
            <WithPermission authClient={authClient} value={['admin']}>
               <EditButton/>
            </WithPermission>
        </Datagrid>
    </List>
);

it makes so that the request when I press edit for a company is companies/undefined! while if I remove the WithPermission tag around EditButton it has the correct url for edit form companies/{companyId}

I am using those versions:

    "admin-on-rest": "1.2.3",
    "aor-permissions": "1.1.2"

Usage on List Filter

Is there a way for hiding/showing some filter inputs based on role/permission?

Permit Resource declaration based on permision

Can we have something like below. So that we can declare resources at higher instead of multiple edit/create layout for same resource

<SwitchResourceWithPermission> <Permission value="admin"> <Resource name="user" create={} edit={} list={}> </Permission> <Permission value="user"> <Resource name="user" list={}> </Permission> </SwitchResourceWithPermission>

Can't get <AuthProvider> to work

Using
admin-on-rest 1.0.2
aor-permissions 1.0.0

<WithPermission> and <SwitchPermission> work fine, they provide much more clarity than my previous tweaks for conditionnal rendering. Good job !

However, when I try to use <AuthProvider> nothing renders (blank page) and this error is yield in JS Console :

Error: withContext(AuthProviderComponent).getChildContext(): key "authClientFromContext" is not defined in childContextTypes.

My code is straightforward :

const App = () => (
  <AuthProvider authClient={authClient}>
      <Admin locale="fr" messages={messages} menu={menu} restClient={restClient} >
             {  } 
      </Admin>
  </AuthProvider>
);

export default App;

Permission based redirect on login

Hi Guys.

This is in reference to this issue #9

Have been trying this for a while today with this saga on login.

const permission = localStorage.getItem('permissions')

function* userLoginSuccess() {
  if (permission === 'publishing') {
    yield put(push('/tales'))
  } else {
    yield put(push('/tales/talesByRelation'))
  }
}

export default function* userLoginSaga() {
  yield all ([
    takeEvery('AOR/USER_LOGIN_SUCCESS', userLoginSuccess),
  ])
}

But no redirect happens. I get a warning from the SwitchPermissionsComponent

`
setState(…): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op

`
Please do advise.

Not working on list view

Hi!

Maybe I'm missing something but for me the is not working correctly on List views.

First Example

image

And here is the code for it:

export const TrainingList = (props) => {
	return <List {...props} perPage={25} filters={<TrainingFilter />}>
		<Datagrid>
			{/* Other stuff */}
			<WithPermission value={["gymadmin", "mainAdmin"]} >
				<AcivateTraining label="Aktíválás" addLabel/>
			</WithPermission>
                       {/* Other stuff */}
		</Datagrid>
	</List>
};

When addLabel prop was not present the label wasn't even showing up anywhere.

Second Example

image

And the code

export const GymUsersList = (props) => (
	<List {...props} perPage={25}>
		<Datagrid>
			<WithPermission value={"mainAdmin"}>
				<TextField source="gymId" />
			</WithPermission>
                       {/* Other stuff */}
		</Datagrid>
	</List>
);

Are these the expected behaviour?
Thanks in advance!
P.S: in Edit or Create view everything works as expected, this only shows up List view.

Routing Issues with permissions

Hello

Firstly, thanks for this fantastic package. Has been tremendously useful.

Issue:

My App.js

  <Admin menu={Menu} authClient={authClient} restClient={uploadCapableRESTClient}>
    <Resource name="appUsers" list={UserList} edit={AdminUserEdit} options={{ label: 'Users' }} />
    <Resource name="trackTale" list={BrandEditorTaleTrack} edit={EditTale} />
    <Resource name="tales" list={ AllEditorTaleDisplaySwitch } edit={EditTale} options={{ label: 'Assigned Tales' }}/>
</Admin>

In my custom Menu I am showing only role related links appropriately.
For user Admin the first route is /appusers
For user Editor the first route displayed is /tales.

When logging in as Editor however the app goes to the first route /appusers and since this user has no components attached to this route, the page only displays the menu. After clicking on the menu all behaviour is as expected.

Look into it when can.

Bring back ability to pass custom props assigned to <Resource/> down

When I've used <Admin /> from aor there were no problems with assigning some custom props to <Resource /> tags in order to have ability to make some manipulations in component that I've specified in menu prop of <Admin /> (i.e. menu={CustomMenu})
Now I am using Admin from aor-permissions and those custom props are not passed down anymore

Could we apply this patch to bring back this ability?

diff --git a/src/Admin.js b/src/Admin.js
index 46c9629..563d185 100644
--- a/src/Admin.js
+++ b/src/Admin.js
@@ -56,6 +56,7 @@
         edit: await applyPermissionsToAction({ permissions, resource, action: 'edit' }),
         show: await applyPermissionsToAction({ permissions, resource, action: 'show' }),
         remove: await applyPermissionsToAction({ permissions, resource, action: 'remove' }),
+        ...resource.props
     };
 
     return newResource;

Control Resource Presence via Permissions

I am trying to let Resources and their presence in the menu be controlled via permissions as shown in the code below, but it doesn't seem to work. Currently the menu item for the Resource just disappears permanently, and the authclient never even gets queried via AUTH_GET_PERMISSIONS. If it's supposed to work, what am I doing wrong? If it's not implemented for Resources yet, I think this would be an immensely useful feature to have!

const App = () => (
  <AuthProvider authClient={authClient}>
  <Admin restClient={restClient}>
    <WithPermission value="can_see_secret_data">
      <Resource name="secret_data" list={SecretDataList} />
    </WithPermission>
    <Resource name="public_data" list={PublicDataList} />
  </Admin>
  </AuthProvider>
);

Re-evaluate <Resources /> after logout and subsequent login as another user

Hi there!
Perhaps that ticket could be marked as feature request
So, details:
aor - v.1.2.0
aor-permissions - v1.1.0

here is the snippet of my app

import {
    Admin
} from 'aor-permissions';

                <Admin
                    appLayout={AppLayout}
                    menu={AppMenu}
                    authClient={authClient}
                    restClient={restClient}
                    customReducers={{auth: authReducer}}
                    messages={translations}
                >
                    <Resource
                        name="users"
                        list={UsersList}
                        create={UserCreate}
                        edit={UserEdit}
                        remove={UserDelete}
                        permissions={'manager'}
                    />
                    <Resource
                        name="clients"
                        list={ClientsList}
                        create={ClientCreate}
                        edit={ClientEdit}
                        remove={ClientDelete}
                        permissions={['advisor', 'manager']}
                    />
                </Admin>

I faced the following problem:

  1. I logged in as UserA which has role, lets say, 'manager' based on that role I've exposed to him some of the resources - Users and Clients
  2. When I logged out and logged in immediately as UserB which has role 'advisor' I was still able to see <Resource name="users"/> in my sidebar menu despite inappropriate permissions. Moreover the resource pages were also available to me (list, create, edit pages).

Only after page refresh (Ctrl + F5) the <Resources/> were re-evaluated against permissions again and <Resource name="users" /> was not avail anymore to UserB

In other words current implementation exposes to unprivileged users resources that were available to previously logged in user.

'value' param is always undefined inside func specified in `resolve` prop of WithPermission

Guys. I think you have a little bit confused yourselfs with those funky args naming in aor-permissions/src/resolvePermissions.js

Snippet of code from my app:

<WithPermission resolve={someFunc} value={'manager'}>
const someFunc = ({permissions, resource, value, exact, record}) => {
        ...
}

The problem - the value arg is always undefined inside someFunc no matter of what.
After spending some time on debugging the transpiled code I've figured out that reason of bug is the following line of code (sorry, I don't have enough time right now for patches so I will provide image :) )

image

After I made the mentioned fix in node_modules/aor-permissions/lib/resolvePermissions.js I was able to receive the value I've passed through value prop to WithPermission elem

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.