Skip to content

Commit

Permalink
Merge pull request #49 from pundit-community/rename-setup-method-and-…
Browse files Browse the repository at this point in the history
…document-the-approach

Rename setup method and document the approach in readme
  • Loading branch information
chrisalley authored Aug 7, 2023
2 parents eae0014 + 9eb0a98 commit 90d2c75
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 20 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
*.log
*.swp
.DS_Store
.vscode
node_modules
.cache
dist
Expand Down
6 changes: 1 addition & 5 deletions examples/extended-policy-example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ class PostPolicy extends Policy {

constructor(user: AuthorisableUser, record: AuthorisablePost) {
super(user, record)
this.actions = new Map([
['view', (): boolean => this.view()],
['publish', (): boolean => this.publish()],
['destroy', (): boolean => this.destroy()],
])
this.setup.apply(this)
}

// eslint-disable-next-line class-methods-use-this
Expand Down
67 changes: 60 additions & 7 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ npm install --save pundit

## Usage

In order to use Pundit.js, you can initialize a policy by passing an object of
functions, called actions. Actions typically map to permissions or routes in
your application.
In order to use Pundit.js, you can initialize a policy by setting up an object
of functions/methods called actions. Actions typically map to permissions or
routes in your application.

**Client-side permissions should not replace a proper authorization system in
your backend.**
Expand All @@ -44,16 +44,69 @@ your backend.**
A policy accepts a user, often the current user of your session, and the
resource you wish to authorize against.

Policies can be defined by extending the `Policy` class. Add a constructor that
accepts the user and record objects as parameters, also calling
`this.setup.apply(this)` to initialise the actions defined in the class.

```javascript
import { Policy } from 'pundit'

export default class PostPolicy extends Policy {
constructor(user, record) {
super(user, record)
this.setup.apply(this)
}

edit() {
return this.user.id === this.record.userId
}

destroy() {
return this.user.isAdmin
}
}
```

Actions are defined as methods belonging to the extended `Policy` class. Each
action method should return true or false depending on whether the specified
user/record combination is permitted for that action.

You can then instantiate the class and use the `can` method to check if the
action is authorised.

```javascript
import PostPolicy from 'src/policies/post.policy.js'

const user = { id: 1, isAdmin: false }
const post = { id: 11, userId: 1 }
const postPolicy = new PostPolicy(user, post)

postPolicy.can('edit') // Returns true
postPolicy.can('destroy') // Returns false
```

Since the role of Pundit.js is to reuse authorisation logic, for real world use
we recommend that you define policy classes in a centralised folder in
your application such as `src/policies`. The policies can then be imported
into each file that needs to check the authorisation rules for the resource
type.

Actions can also be added to an instantiated policy by using the `add` method
which accepts a plain function with user and record parameters. The following
is equivalent logic to defining policy actions by extending the `Policy` class:

```javascript
import { Policy } from 'pundit'

const postPolicy = new Policy(user, postRecord)
const user = { id: 1, isAdmin: false }
const post = { id: 11, userId: 1 }
const postPolicy = new Policy(user, post)

postPolicy.add('edit', (user, record) => user.id === record.userId)
postPolicy.add('destroy', (user) => user.isAdmin())
postPolicy.add('destroy', (user) => user.isAdmin)

postPolicy.can('edit')
postPolicy.can('destroy')
postPolicy.can('edit') // Returns true
postPolicy.can('destroy') // Returns false
```

### Using with React
Expand Down
7 changes: 4 additions & 3 deletions src/policy.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
type ActionFunction = (user: unknown, record: unknown) => boolean
type ActionMethod = () => boolean

export default class Policy {
user: unknown

record: unknown

actions: Map<string, ActionFunction>
actions: Map<string, ActionFunction | ActionMethod>

constructor(user: unknown, record: unknown) {
this.user = user
Expand All @@ -30,14 +31,14 @@ export default class Policy {
return newPolicy
}

actionsFromClass(): void {
setup(): void {
const actionNames = Object.getOwnPropertyNames(
this.constructor.prototype
).filter((methodName) => methodName !== 'constructor')
this.actions = new Map(
actionNames.map((actionName) => [
actionName,
(): boolean => this[actionName](this.user, this.record),
(): boolean => this[actionName](),
])
)
}
Expand Down
10 changes: 5 additions & 5 deletions test/policy.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Policy from '../src/policy'

describe('can function', () => {
describe('can method', () => {
const policy = new Policy(null, null)

it('returns true if the action is authorised', () => {
Expand All @@ -21,7 +21,7 @@ describe('can function', () => {
})
})

describe('add function', () => {
describe('add method', () => {
const policy = new Policy(null, null)

it('adds an action to an instance of the Policy class', () => {
Expand All @@ -41,7 +41,7 @@ describe('add function', () => {
})
})

describe('copy function', () => {
describe('copy method', () => {
it('copies the user and record params when these are specified', () => {
const originalPolicy = new Policy(undefined, undefined)
const paramUser = { id: 1 }
Expand Down Expand Up @@ -75,7 +75,7 @@ describe('copy function', () => {
})
})

describe('actionsFromClass function', () => {
describe('setup method', () => {
type AuthorisableUser = { isAdmin: boolean }
type AuthorisableRecord = { draft: boolean }

Expand All @@ -89,7 +89,7 @@ describe('actionsFromClass function', () => {
record: AuthorisableRecord | undefined
) {
super(user, record)
this.actionsFromClass.apply(this)
this.setup.apply(this)
}

// eslint-disable-next-line class-methods-use-this
Expand Down

0 comments on commit 90d2c75

Please sign in to comment.