-
Notifications
You must be signed in to change notification settings - Fork 21
authorization
n-odata-server leverages the authentication and authorization mechanisms supplied by loopback. This wiki describes how to use it in your applications.
To mention upfront: If you want a detailed description of Authentication, Authorization and permissions you should have a look to the
according loopback documentation.
In this document you find a brief introduction that helps you get started quickly.
First you have to define ACLs (Access Control Lists). These ACLs describe who is allowed to do what with your models.
The ACLs are defined in the model.json file. The following example is defined in file common/models/business-trip.json
of
the n-odata-server example project.
The ACLs are defined with the array acls
. The below definition denies access to this model (and all its entities)
for everyone and allows access for all users that have been assigned to the r_businesstrips_access
role.
{
"name": "BusinessTrip",
"plural": "BusinessTrips",
"base": "PersistedModel",
"options": {
"validateUpsert": true
},
"persistUndefinedAsNull": true,
"properties": {
...
},
"validations": [],
"relations": {},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "r_businesstrips_access",
"permission": "ALLOW"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
}
],
"methods": {}
}
To be able to access the model objects you have to have a user, a role and assign the user to the role.
In the n-odata-server example project we create these
artifacts in the file server/boot/initial_security.ts
. All files that are located in the server/boot
directory
are automatically executed when the application boots. In your project you can of course add code for creating
these artifacts wherever you want and write a nice UI for it. We decided to use a boot script for simplicity reasons here.
Here is the code we written to create the artifacts.
We first create a user if not already available. The user gets an email and a password. After the user has been
created or read from the database the initRoleForUser
function is called.
public init() {
// find user1 in db
this.User.findOne({where: {email: '[email protected]'}}).then((user) => {
if(!user) {
// create user if not already exists
this.User.create({username: 'user1', email: '[email protected]', password: 'secret'}).then((user) => {
if(user) {
logger.debug(`User created: ${JSON.stringify(user.toJSON())}`);
this.initRoleForUser(user);
} else {
logger.error(`user 'user1' could not be created. Program may not work as expected`);
}
});
} else {
this.initRoleForUser(user);
}
}).catch((err) => {
logger.error(`error: ${err}`);
});
};
The role r_businesstrips_access
is either read from the database or created within the function initRoleForUser
.
After the role is available the function assignUserToRole
is called.
private initRoleForUser(user:any) {
this.Role.findOne({where: {name: 'r_businesstrips_access'}, include: 'principals'}).then((role) => {
if(!role) {
this.Role.create({name: 'r_businesstrips_access', description: 'grants general access to businesstrips'}).then((role) => {
if(role) {
logger.debug(`Role created: ${JSON.stringify(role.toJSON())}`);
this.assignUserToRole(user, role);
} else {
logger.error(`role 'r_businesstrips_access' could not be created. Program may not work as expected`);
}
})
} else {
this.assignUserToRole(user, role);
}
})
}
The assignment of a user to a role is done adding a principal to the roles principals hasMany association.
private assignUserToRole(user, role) {
// Promise (then) does not work here
this.RoleMapping.findOne({where: {principalType: this.RoleMapping.USER, principalId: user.id, roleId: role.id}}).then((roleMapping) => {
if(!roleMapping) {
role.principals.create({
principalType: this.RoleMapping.USER,
principalId: user.id
}).then((roleMapping) => {
logger.debug(`Rolemapping created: ${JSON.stringify(roleMapping.toJSON())}`);
})
}
})
}
As soon as you have defined the ACLs and are sure that a user has been assigned to the appropriate role you can test your application.
First you have to login your user to get an access token.
curl -X POST --header "Content-Type: application/json" --header "Accept: application/json" -d "{
\"email\": \"[email protected]\",
\"password\": \"secret\"
} " "http://localhost:3000/api/Users/login"
You retrieve a result that looks like this.
{
"id": "yE1OCPVTHmGERUwDFegJawCTPV24hr8OTUGgGbNEiZSRoS3rLjJPoZt3WOdCAd4o",
"ttl": 1209600,
"created": "2016-10-17T09:18:24.302Z",
"userId": 5
}
The id
is your access token that you must provide with each request to the businesstrip model
Now as you know your access token you can access your model data.
curl -X GET -H "Authorization: yE1OCPVTHmGERUwDFegJawCTPV24hr8OTUGgGbNEiZSRoS3rLjJPoZt3WOdCAd4o" -H
"Cache-Control: no-cache" -H "Postman-Token: 16662279-b82e-e369-8e3d-1aef109357da"
"http://0.0.0.0:3000/odata/BusinessTrips"
You should get back something like this.
{
"d": {
"results": [
{
"id": "22",
"Starttime": "/Date(1463724000000)/",
"Endtime": "/Date(1463752800000)/",
"Location": "Hamburg",
"Customer": null,
"Distance": 120.7,
"Note": null,
"Breakfast": false,
"Lunch": false,
"Dinner": false,
"__metadata": {
"uri": "http://0.0.0.0:3000/odata/BusinessTrips('22')",
"type": "NODATASERVER.BusinessTrip"
}
},
...
...
...
}
}
}
If you provide a wrong or no access token you get a 401 error that looks like this.
{
"message": "Authorization Required",
"stack": "Error: Authorization Required\n
at ...
at ...
at ...
"statusCode": 401,
"code": "AUTHORIZATION_REQUIRED"
}