Skip to content

Commit

Permalink
refactor: organize under components
Browse files Browse the repository at this point in the history
  • Loading branch information
jannyHou committed Mar 9, 2020
1 parent ff79fdd commit 807e804
Show file tree
Hide file tree
Showing 19 changed files with 104 additions and 60 deletions.
60 changes: 44 additions & 16 deletions docs/site/migration/auth/example.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,28 +157,31 @@ retrieves the token from a request, decodes the user's information in it as
decide the `principal`'s access later.

To simplify the implementation for readers, we extracted the services and
bindings into a component in folder `src/component`, it consists of the
following files:
bindings into a component in folder `src/components/jwt-authentication`, it
consists of the following files:

- creating the jwt authentication strategy to decode the user profile from
token. See
[file src/component/jwt.auth.strategy.ts](https://github.com/strongloop/loopback-next/tree/master/examples/access-control-migration/src/component/jwt.auth.strategy.ts).
- creating the token service to organize utils for token operations. See
[file src/component/jwt.service.ts](https://github.com/strongloop/loopback-next/tree/master/examples/access-control-migration/src/component/jwt.service.ts).
- creating user service to organize utils for user operations. see
[file src/component/user.service.ts](https://github.com/strongloop/loopback-next/tree/master/examples/access-control-migration/src/component/user.service.ts).
token. See file
[jwt.auth.strategy.ts](https://github.com/strongloop/loopback-next/tree/master/examples/access-control-migration/src/components/jwt-authentication/jwt.auth.strategy.ts).
- creating the token service to organize utils for token operations. See file
[jwt.service.ts](https://github.com/strongloop/loopback-next/tree/master/examples/access-control-migration/src/components/jwt-authentication/jwt.service.ts).
- creating user service to organize utils for user operations. see file
[user.service.ts](https://github.com/strongloop/loopback-next/tree/master/examples/access-control-migration/src/components/jwt-authentication/user.service.ts).
- adding OpenAPI security specification to your app so that the explorer has an
Authorize button to setup the token for secured endpoints. See
[file src/component/security.spec.ts](https://github.com/strongloop/loopback-next/tree/master/examples/access-control-migration/src/component/security.spec.ts).
- creating bindings for the above services. See
[file src/component/keys.ts](https://github.com/strongloop/loopback-next/blob/master/examples/access-control-migration/src/component/keys.ts).
Authorize button to setup the token for secured endpoints. See file
[security.spec.ts](https://github.com/strongloop/loopback-next/tree/master/examples/access-control-migration/src/components/jwt-authentication/security.spec.ts).
- creating bindings for the above services. See file
[keys.ts](https://github.com/strongloop/loopback-next/blob/master/examples/access-control-migration/src/components/jwt-authentication/keys.ts).

You can enable the jwt authentication by mounting the authentication component
in the application constructor:

{% include code-caption.html content="src/application.ts" %}

```ts
// Add this line to import the component
import {JWTAuthenticationComponent} from './components/jwt-authentication';

export class AccessControlApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
Expand Down Expand Up @@ -300,7 +303,7 @@ document
[Programming Access Policies](../../Loopback-component-authorization#programming-access-policies)_

The complete authorizer file can be found in
[services/casbin.authorizer.ts](https://github.com/strongloop/loopback-next/blob/master/examples/access-control-migration/src/services/casbin.authorizer.ts).
[components/casbin-authorization/services/casbin.authorizer.ts](https://github.com/strongloop/loopback-next/blob/master/examples/access-control-migration/src/components/casbin-authorization/services/casbin.authorizer.ts).
It retrieves the three required fields from the authorization context: subject
from `principal`, `resource` as object, and action, to invoke casbin enforcers
and make decision.
Expand All @@ -316,7 +319,7 @@ and therefore we define the pre-process logic in a voter to generate the
resource name as `project${id}`, then passes it to the authorizer.

The complete voter file can be found in file
[services/assign-project-instance-id.voter.ts](https://github.com/strongloop/loopback-next/blob/master/examples/access-control-migration/src/services/assign-project-instance-id.voter.ts)
[components/casbin-authorization/services/assign-project-instance-id.voter.ts](https://github.com/strongloop/loopback-next/blob/master/examples/access-control-migration/src/components/casbin-authorization/services/assign-project-instance-id.voter.ts)

3. Create casbin enforcers

Expand All @@ -327,7 +330,7 @@ files per role. The authorizer will only invoke the enforcers for allowed
role(s).

The complete enforcer file can be found in file
[services/casbin.enforcers.ts](https://github.com/strongloop/loopback-next/blob/master/examples/access-control-migration/src/services/casbin.enforcers.voter.ts)
[components/casbin-authorization/services/casbin.enforcers.ts](https://github.com/strongloop/loopback-next/blob/master/examples/access-control-migration/src/components/casbin-authorization/services/casbin.enforcers.ts)

4. Write casbin model and policies

Expand All @@ -336,7 +339,32 @@ Since the model and policy are already covered in section
files are defined in folder
[fixtures/casbin](https://github.com/strongloop/loopback-next/blob/master/examples/access-control-migration/fixtures/casbin).

5. Casbin persistency and synchronize(2nd Phase implementation, TBD)
5. Mount the casbin authorization system as a component

The casbin authorizer, voter and enforcers above are packed under component
'src/components/casbin-authorization'. You can export their bindings in a
[component file](https://github.com/strongloop/loopback-next/blob/master/examples/access-control-migration/src/components/casbin-authorization/casbin-authorization-component.ts)
and mount the component in the application constructor:

{% include code-caption.html content="src/application.ts" %}

```ts
// Add this line to import the component
import {CasbinAuthorizationComponent} from './components/casbin-authorization';

export class AccessControlApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
constructor(options: ApplicationConfig = {}) {
// ...
// Add this line to mount the casin authorization component
this.component(CasbinAuthorizationComponent);
// ...
}
}
```

6. Casbin persistency and synchronize(2nd Phase implementation, TBD)

This will be supported at the 2nd phase of implementation. The plan is to have
model or operation hooks to update the casbin policies when new data created. It
Expand Down
26 changes: 8 additions & 18 deletions examples/access-control-migration/src/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,20 @@
// License text available at https://opensource.org/licenses/MIT

import {AuthenticationComponent} from '@loopback/authentication';
import {
AuthorizationComponent,
AuthorizationTags,
} from '@loopback/authorization';
import {AuthorizationComponent} from '@loopback/authorization';
import {BootMixin} from '@loopback/boot';
import {ApplicationConfig, BindingKey} from '@loopback/core';
import {RepositoryMixin} from '@loopback/repository';
import {RestApplication} from '@loopback/rest';
import {RestExplorerComponent} from '@loopback/rest-explorer';
import {ServiceMixin} from '@loopback/service-proxy';
import path from 'path';
import {JWTAuthenticationComponent} from './component/jwt-authentication-component';
import {SECURITY_SCHEME_SPEC} from './component/security.spec';
import {CasbinAuthorizationComponent} from './components/casbin-authorization';
import {
JWTAuthenticationComponent,
SECURITY_SCHEME_SPEC,
} from './components/jwt-authentication';
import {MySequence} from './sequence';
import {CasbinAuthorizationProvider} from './services/casbin.authorizer';
import {getCasbinEnforcerByName} from './services/casbin.enforcers';

/**
* Information from package.json
Expand All @@ -43,14 +41,14 @@ export class AccessControlApplication extends BootMixin(
// Set up default home page
this.static('/', path.join(__dirname, '../public'));

this.setUpBindings();
this.addSecuritySpec();

this.component(RestExplorerComponent);
// Bind authentication component related elements
// Bind authentication and authorization related elements
this.component(AuthenticationComponent);
this.component(AuthorizationComponent);
this.component(JWTAuthenticationComponent);
this.component(CasbinAuthorizationComponent);

this.projectRoot = __dirname;
// Customize @loopback/boot Booter Conventions here
Expand All @@ -64,14 +62,6 @@ export class AccessControlApplication extends BootMixin(
};
}

setUpBindings(): void {
// authorization
this.bind('casbin.enforcer.factory').to(getCasbinEnforcerByName);
this.bind('authorizationProviders.casbin-provider')
.toProvider(CasbinAuthorizationProvider)
.tag(AuthorizationTags.AUTHORIZER);
}

addSecuritySpec(): void {
this.api({
openapi: '3.0.0',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {AuthorizationTags} from '@loopback/authorization';
import {Binding, Component} from '@loopback/core';
import {CasbinAuthorizationProvider, getCasbinEnforcerByName} from './services';

export class CasbinAuthorizationComponent implements Component {
bindings = [
Binding.bind('casbin.enforcer.factory').to(getCasbinEnforcerByName),
Binding.bind('authorizationProviders.casbin-provider')
.toProvider(CasbinAuthorizationProvider)
.tag(AuthorizationTags.AUTHORIZER),
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './casbin-authorization-component';
export * from './services';
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
AuthorizationDecision,
AuthorizationMetadata,
} from '@loopback/authorization';
import {RESOURCE_ID} from '../keys';
import {RESOURCE_ID} from '../../../keys';

/**
* Instance level authorizer for known endpoints
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from '@loopback/authorization';
import {inject, Provider} from '@loopback/core';
import * as casbin from 'casbin';
import {RESOURCE_ID} from '../keys';
import {RESOURCE_ID} from '../../../keys';
const debug = require('debug')('loopback:example:acl');
const DEFAULT_SCOPE = 'execute';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import * as casbin from 'casbin';
import path from 'path';

const POLICY_PATHS = {
admin: '../../fixtures/casbin/rbac_policy.admin.csv',
owner: '../../fixtures/casbin/rbac_policy.owner.csv',
team: '../../fixtures/casbin/rbac_policy.team_member.csv',
admin: './../../../../fixtures/casbin/rbac_policy.admin.csv',
owner: './../../../../fixtures/casbin/rbac_policy.owner.csv',
team: './../../../../fixtures/casbin/rbac_policy.team_member.csv',
};

export async function getCasbinEnforcerByName(
Expand All @@ -28,7 +28,10 @@ export async function getCasbinEnforcerByName(
export async function createEnforcerByRole(
policyPath: string,
): Promise<casbin.Enforcer> {
const conf = path.resolve(__dirname, '../../fixtures/casbin/rbac_model.conf');
const conf = path.resolve(
__dirname,
'./../../../../fixtures/casbin/rbac_model.conf',
);
const policy = path.resolve(__dirname, policyPath);
return casbin.newEnforcer(conf, policy);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './assign-project-instance-id.voter';
export * from './casbin.authorizer';
export * from './casbin.enforcers';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './jwt-authentication-component';
export * from './keys';
export * from './services';
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import {
CoreBindings,
inject,
} from '@loopback/core';
import {JWTAuthenticationStrategy} from './jwt.auth.strategy';
import {JWTService} from './jwt.service';
import {JWTAuthenticationStrategy} from './services/jwt.auth.strategy';
import {JWTService} from './services/jwt.service';
import {
TokenServiceBindings,
TokenServiceConstants,
UserServiceBindings,
} from './keys';
import {MyUserService} from './user.service';
import {MyUserService} from './services/user.service';

export class JWTAuthenticationComponent implements Component {
bindings = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import {TokenService, UserService} from '@loopback/authentication';
import {BindingKey} from '@loopback/core';
// The User model is imported from the application,
// which makes the component not entirely independent
import {User} from '../models/user.model';
import {Credentials} from './user.service';
import {User} from '../../models/user.model';
import {Credentials} from './services/user.service';

export namespace TokenServiceConstants {
export const TOKEN_SECRET_VALUE = 'myjwts3cr3t';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './jwt.auth.strategy';
export * from './jwt.service';
export * from './security.spec';
export * from './user.service';
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {AuthenticationStrategy, TokenService} from '@loopback/authentication';
import {inject} from '@loopback/context';
import {HttpErrors, Request} from '@loopback/rest';
import {UserProfile} from '@loopback/security';
import {TokenServiceBindings} from './keys';
import {TokenServiceBindings} from '../keys';

export class JWTAuthenticationStrategy implements AuthenticationStrategy {
name = 'jwt';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {inject} from '@loopback/context';
import {HttpErrors} from '@loopback/rest';
import {securityId, UserProfile} from '@loopback/security';
import {promisify} from 'util';
import {TokenServiceBindings} from './keys';
import {TokenServiceBindings} from '../keys';

const jwt = require('jsonwebtoken');
const signAsync = promisify(jwt.sign);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {repository} from '@loopback/repository';
import {HttpErrors} from '@loopback/rest';
import {securityId, UserProfile} from '@loopback/security';
import {compare} from 'bcryptjs';
import {User} from '../models/user.model';
import {UserRepository} from '../repositories/user.repository';
import {User} from '../../../models/user.model';
import {UserRepository} from '../../../repositories/user.repository';

/**
* A pre-defined type for user credentials. It assumes a user logs in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import _ from 'lodash';
import {authenticate} from '@loopback/authentication';
import {authorize} from '@loopback/authorization';
import {repository} from '@loopback/repository';
import {param, get, getModelSchemaRef, patch} from '@loopback/rest';
import {get, getModelSchemaRef, param, patch} from '@loopback/rest';
import _ from 'lodash';
import {assignProjectInstanceId} from '../components/casbin-authorization';
import {Project} from '../models';
import {ProjectRepository} from '../repositories';
import {authenticate} from '@loopback/authentication';
import {authorize} from '@loopback/authorization';
import {assignProjectInstanceId} from '../services/assign-project-instance-id.voter';

// TBD: refactor the ACLs to a separate file
const RESOURCE_NAME = 'project';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
import {TokenService, UserService} from '@loopback/authentication';
import {inject} from '@loopback/context';
import {post, requestBody} from '@loopback/rest';
import {TokenServiceBindings, UserServiceBindings} from '../component/keys';
import {Credentials} from '../component/user.service';
import {
Credentials,
TokenServiceBindings,
UserServiceBindings,
} from '../components/jwt-authentication';
import {User} from '../models';

const CredentialsSchema = {
Expand Down
4 changes: 0 additions & 4 deletions examples/access-control-migration/src/services/index.ts

This file was deleted.

0 comments on commit 807e804

Please sign in to comment.