-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Conversation
authorize({allow: ['$authenticated']}); | ||
export const denyAll = () => authorize({deny: ['$everyone']}); | ||
export const denyUnauthenticated = () => | ||
authorize({deny: ['$anonymous', 'unauthenticated']}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$unauthenticated
?
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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();
There was a problem hiding this comment.
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.
b70c54c
to
23a85a2
Compare
packages/authorization/README.md
Outdated
## Basic use | ||
|
||
Start by decorating your controller methods with `@authorize` to require the | ||
request to be authenticated. |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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']}); |
There was a problem hiding this comment.
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
?
1f6e799
to
2b66269
Compare
describe('@authorize decorator', () => { | ||
it('can add authorize metadata to target method', () => { | ||
class TestClass { | ||
@authorize({allow: ['ADMIN'], scopes: ['secret.read']}) |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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
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 |
3046254
to
cf5b9c9
Compare
cf5b9c9
to
096f20e
Compare
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 ? |
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.
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. |
Thank you for your response. |
@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. |
096f20e
to
f9f0d70
Compare
f9f0d70
to
a24807f
Compare
I rewrote the code based on interceptors. |
@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. |
54f8510
to
997eb86
Compare
997eb86
to
0ee7df4
Compare
72250ed
to
3c7ae65
Compare
For reviewers: here is a PoC PR that demos applying the authorization module in a real user scenario: |
There was a problem hiding this 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']}) |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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?
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. |
Connect to #538
Checklist
npm test
passes on your machinepackages/cli
were updatedpackages/example-*
were updated