Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add authorization component #1205

Merged
merged 10 commits into from
Aug 14, 2019
Merged

feat: add authorization component #1205

merged 10 commits into from
Aug 14, 2019

Conversation

raymondfeng
Copy link
Contributor

@raymondfeng raymondfeng commented Mar 28, 2018

Connect to #538

Checklist

  • npm test passes on your machine
  • New tests added or existing tests modified to cover all changes
  • Code conforms with the style guide
  • API Documentation in code was updated
  • Documentation in /docs/site was updated
  • Affected artifact templates in packages/cli were updated
  • Affected example projects in packages/example-* were updated

authorize({allow: ['$authenticated']});
export const denyAll = () => authorize({deny: ['$everyone']});
export const denyUnauthenticated = () =>
authorize({deny: ['$anonymous', 'unauthenticated']});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$unauthenticated?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between $anonymous and $unauthenticated ?

Copy link

@JesusTheHun JesusTheHun Oct 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One must be careful while implementing the all roles. Very often what the developer wants is all but . A developer may be tempted to write something like :

@authorize({allow: ['MODERATOR']})
@authorize.denyAll()
// maybe a helper like this may be wanted
@authorize.denyAllBut(['MODERATOR'])
// or of course
@authorize.allowAllBut(['$anonymous'])
myMethod();

This is a case to handle. The order of annotation is very important and counter intuitive so the documentation must warn the user about that. Hence the helper denyAllBut();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a rule that makes the order of decorators irrelevant.

@raymondfeng raymondfeng force-pushed the authorization branch 2 times, most recently from b70c54c to 23a85a2 Compare April 11, 2018 16:12
## Basic use

Start by decorating your controller methods with `@authorize` to require the
request to be authenticated.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

authenticated or authorized?

this.providers = {
[AuthorizationBindings.AUTHORIZE_ACTION.key]: AuthorizationProvider,
[AuthorizationBindings.AUTHORIZE_METADATA
.key]: AuthorizationMetadataProvider,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this formatting rather weird, was it created by prettier?

securityContext: SecurityContext,
metadata: AuthorizationMetadata,
) => {
return AuthorizationDecision.ALLOW;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct? Shouldn't the reference implementation actually handle $everyone, $unauthenticated and $anonymous roles/scopes as defined in packages/authorization/src/decorators/authorize.ts?

authorize({allow: ['$authenticated']});
export const denyAll = () => authorize({deny: ['$everyone']});
export const denyUnauthenticated = () =>
authorize({deny: ['$anonymous', '$unauthenticated']});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between anonymous and unauthenticated?

describe('@authorize decorator', () => {
it('can add authorize metadata to target method', () => {
class TestClass {
@authorize({allow: ['ADMIN'], scopes: ['secret.read']})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by the definition of scope and the role based definition in the same decorator.
@scope('secret.read') this is my scope definition
@role({ allow: ['ADMIN']}) this is my role based access definition

These are two completely different things.

Scenario 1 : I use role based authorization, I decorate my route to define who can access to this route. This is @role.
Scenario 2 : I use scope based authorization, my user is allowed to access a set of scopes, coming from database or token ; if the route he tries to access is within his scopes, access is granted. This is @scope
Scenario 3 : I use both, every role has a set of scope associated, hard coded or defined through user land configuration. This is @scope ; in my authentication configuration I bind each role to a set of scopes.


it('can add denyAll to target method', () => {
class TestClass {
@authorize.denyAll()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a security perspective, denyAll() should be the default behavior. If the user wants a whitelist approach, it should be able to set it in the component configuration

@JesusTheHun
Copy link

For future releases, it will be nice to bring a context based authorization. Sometimes referred as a voter approach.

@vote({ voter: 'binding.myService', identifier: 'articleId' })
// or with helper
@voteFromRepository({ voter: ArticleRepository, identifier: 'articleId' })
@scope('article.write');
myWriteMethod(articleId: string);
class ArticleRepository implements Voter {}
interface Voter {
    public vote(user: MyUserModel, articleId: string, scope: string): boolean
}

The authorization decorator retrieve the articleId at runtime, ask the ArticleRepository to tell is this user is allowed to perform action on this very article, within the provided scope. The MyUserModel instance is injected by asking the authentication component.

@raymondfeng raymondfeng force-pushed the authorization branch 3 times, most recently from 3046254 to cf5b9c9 Compare October 18, 2018 05:47
@JesusTheHun
Copy link

JesusTheHun commented Nov 7, 2018

Is there any progress on this subject ?

We would like to launch several API using LB4 in the next months but it lacks authorization and authentication components.

edit : Is it realistic to expect these two components to be added to the December milestone ?

@bajtos
Copy link
Member

bajtos commented Nov 8, 2018

Is there any progress on this subject ?

Right now, we are trying to figure out a plan how to approach the large task of figuring out authorization implementation. I summarized my current thinking in #1035 (comment), but it's not definitive yet.

We would like to launch several API using LB4 in the next months but it lacks authorization and authentication components.
edit : Is it realistic to expect these two components to be added to the December milestone ?

Considering our current velocity and also the fact that many of us will be taking time off (vacations) in December, I find it very unlikely to have production-level implementation of authentication and authorization components done by the end of 2018.

@JesusTheHun
Copy link

Thank you for your response.
Do you see anything I can do to help you on this path ?

@bajtos
Copy link
Member

bajtos commented Nov 9, 2018

@JesusTheHun thank you for the offer! I am afraid this work is currently blocked by missing hasOne relation that @b-admike is working on right now - see #1879. Once hasOne is landed, you can help us with #1996, #1997 and #1998. But be warned, this is an exploratory work that is likely to discover missing pieces. These missing pieces may be non-trivial to add and requiring design discussions first.

@raymondfeng
Copy link
Contributor Author

I rewrote the code based on interceptors.

@dhmlau
Copy link
Member

dhmlau commented Jul 23, 2019

@emonddr @jannyHou, per our discussion earlier, I think @jannyHou will be taking over this PR, so I'm assigning to her.

@jannyHou, please reach out to @raymondfeng when you're about to start on this. He mentioned this PR is almost ready, might need some more test coverage and other things. It would be good to connect with him on more details. Thanks.

@jannyHou
Copy link
Contributor

For reviewers: here is a PoC PR that demos applying the authorization module in a real user scenario:
loopbackio/loopback4-example-shopping#231

Copy link
Contributor

@agnes512 agnes512 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went through the tests and the code. Except some small questions it looks good to me( if I understand them correctly).

import {get} from '@loopback/rest';

export class MyController {
@authorize({allow: ['ADMIN']})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So is this saying user profile will be available for this user as a role of ADMIN?

* @param target Target object/class
* @param methodName Target method
*/
export function getAuthorizeMetadata(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am ok with both names. But it'd be better if we make it more consistent 😛

export const EVERYONE = '$everyone';
export const AUTHENTICATED = '$authenticated';
export const UNAUTHENTICATED = '$unauthenticated';
export const ANONYMOUS = '$anonymous';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does ANONYMOUS mean the user does not need authentication, and UNAUTHENTICATED mean the user didn't pass the authentication?
Can we also add a comment on them?

@raymondfeng
Copy link
Contributor Author

raymondfeng commented Aug 13, 2019

See https://docs.spring.io/spring-security/site/docs/3.0.x/reference/anonymous.html

I'm fine to remove it for simplicity.

@jannyHou jannyHou changed the title [WIP] feat: add authorization component feat: add authorization component Aug 13, 2019
@jannyHou
Copy link
Contributor

See https://docs.spring.io/spring-security/site/docs/3.0.x/reference/anonymous.html
I'm fine to remove it for simplicity.

Thank you 👍 I will keep it in this PR...if anyone has a good reason to remove it I can quickly make another PR to remove it lol.

@jannyHou jannyHou merged commit 594a743 into master Aug 14, 2019
@delete-merged-branch delete-merged-branch bot deleted the authorization branch August 14, 2019 14:13
@jannyHou jannyHou added this to the Aug 2019 milestone milestone Aug 14, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants