Implement OAuth2 server, link oauth:Client and auth::Oauth2Client, implement permission checks

This commit is contained in:
garrettmills
2020-05-16 23:55:08 -05:00
parent 6f621f5891
commit d558f21375
51 changed files with 2808 additions and 159 deletions

View File

@@ -2,6 +2,16 @@ const { Middleware } = require('libflitter')
class PermissionMiddleware extends Middleware {
async test(req, res, next, { check }) {
// If the request was authorized using an OAuth2 bearer token,
// make sure the associated client has permission to access this endpoint.
if ( req?.oauth?.client ) {
if ( !req.oauth.client.can(check) )
return res.status(401)
.message('Insufficient permissions (OAuth2 Client).')
.api()
}
// Make sure the user has permission
if ( !req.user.can(check) )
return res.status(401)
.message('Insufficient permissions.')

View File

@@ -0,0 +1,60 @@
const { Middleware } = require('libflitter')
class APIRouteMiddleware extends Middleware {
static get services() {
return [...super.services, 'models']
}
async test(req, res, next, { allow_token = true, allow_user = true }) {
// First, check if there is a user in the session.
if ( allow_user && req.is_auth ) {
return next()
} else if ( allow_token ) {
return req.app.oauth2.authorise()(req, res, async e => {
if ( e ) return next(e)
// Look up the OAuth2 client an inject it into the route
if ( req.user && req.user.id ) {
const User = this.models.get('auth:User')
const user = await User.findById(req.user.id)
if ( !user )
return res.status(401)
.message('The user this token is associated with no longer exists.')
.api()
req.user = user
req.is_auth = true
// Look up the token and the associated client
const Oauth2BearerToken = this.models.get('auth::Oauth2BearerToken')
const Client = this.models.get('oauth:Client')
// e.g. "Bearer XYZ".split(' ')[1] -> "XYZ"
const bearer = req.headers.authorization.split(' ')[1]
const token = await Oauth2BearerToken.findOne({ accessToken: bearer })
if ( !token )
return res.status(401)
.message('Unable to lookup OAuth2 token.')
.api()
const client = await Client.findOne({uuid: token.clientID})
if ( !client )
return res.status(401)
.message('This OAuth2 client is no longer authorized.')
.api()
req.oauth.token = token
req.oauth.client = client
} else
return res.status(401)
.message('Unable to lookup user associated with that token.')
.api()
next()
})
}
return res.status(401).api()
}
}
module.exports = exports = APIRouteMiddleware

View File

@@ -0,0 +1,41 @@
const app_routes = {
prefix: '/api/v1/applications',
middleware: [
'auth:APIRoute',
],
get: {
'/': [
['middleware::api:Permission', { check: 'v1:applications:list' }],
'controller::api:v1:App.get_applications',
],
'/:id': [
['middleware::api:Permission', { check: 'v1:applications:get' }],
'controller::api:v1:App.get_application',
],
},
post: {
'/': [
['middleware::api:Permission', { check: 'v1:applications:create' }],
'controller::api:v1:App.create_application',
],
},
patch: {
'/:id': [
['middleware::api:Permission', { check: 'v1:applications:update' }],
'controller::api:v1:App.update_application',
],
},
delete: {
'/:id': [
['middleware::api:Permission', { check: 'v1:applications:delete' }],
'controller::api:v1:App.delete_application',
],
},
}
module.exports = exports = app_routes

View File

@@ -9,30 +9,100 @@ const auth_routes = {
'/mfa/enable/date': ['middleware::auth:UserOnly', 'controller::api:v1:Auth.get_mfa_enable_date'],
'/roles': [
'middleware::auth:APIRoute',
['middleware::api:Permission', { check: 'v1:auth:roles:list' }],
'controller::api:v1:Auth.get_roles',
],
'/users': [
'middleware::auth:APIRoute',
['middleware::api:Permission', { check: 'v1:auth:users:list' }],
'controller::api:v1:Auth.get_users',
],
'/groups': [
'middleware::auth:APIRoute',
['middleware::api:Permission', { check: 'v1:auth:groups:list' }],
'controller::api:v1:Auth.get_groups',
],
'/users/:id': [
'middleware::auth:APIRoute',
['middleware::api:Permission', { check: 'v1:auth:users:get' }],
'controller::api:v1:Auth.get_user',
],
'/groups/:id': [
'middleware::auth:APIRoute',
['middleware::api:Permission', { check: 'v1:auth:groups:get' }],
'controller::api:v1:Auth.get_group',
],
},
post: {
'/validate/username': ['controller::api:v1:Auth.validate_username'],
'/attempt': [ 'controller::api:v1:Auth.attempt' ],
'/mfa/generate': ['middleware::auth:UserOnly', 'controller::api:v1:Auth.generate_mfa_key'],
'/mfa/attempt': ['middleware::auth:DMZOnly', 'controller::api:v1:Auth.attempt_mfa'],
'/validate/username': [
'controller::api:v1:Auth.validate_username'
],
'/attempt': [
'controller::api:v1:Auth.attempt'
],
'/mfa/generate': [
'middleware::auth:UserOnly',
'controller::api:v1:Auth.generate_mfa_key'
],
'/mfa/attempt': [
'middleware::auth:DMZOnly',
'controller::api:v1:Auth.attempt_mfa'
],
'/mfa/enable': [
'middleware::auth:UserOnly',
['middleware::auth:RequireTrust', { scope: 'mfa.enable', deplete: true }],
'controller::api:v1:Auth.enable_mfa'
],
'/mfa/disable': [
'middleware::auth:UserOnly',
['middleware::auth:RequireTrust', { scope: 'mfa.disable', deplete: true }],
'controller::api:v1:Auth.disable_mfa',
],
'/groups': [
'middleware::auth:APIRoute',
['middleware::api:Permission', { check: 'v1:auth:groups:create' }],
'controller::api:v1:Auth.create_group',
],
'/users': [
'middleware::auth:APIRoute',
['middleware::api:Permission', { check: 'v1:auth:users:create' }],
'controller::api:v1:Auth.create_user',
],
},
patch: {
'/groups/:id': [
'middleware::auth:APIRoute',
['middleware::api:Permission', { check: 'v1:auth:groups:update' }],
'controller::api:v1:Auth.update_group',
],
'/users/:id': [
'middleware::auth:APIRoute',
['middleware::api:Permission', { check: 'v1:auth:users:update' }],
'controller::api:v1:Auth.update_user',
],
},
delete: {
'/groups/:id': [
'middleware::auth:APIRoute',
['middleware::api:Permission', { check: 'v1:auth:groups:delete' }],
'controller::api:v1:Auth.delete_group',
],
'/users/:id': [
'middleware::auth:APIRoute',
['middleware::api:Permission', { check: 'v1:auth:users:delete' }],
'controller::api:v1:Auth.delete_user',
],
},
}

View File

@@ -0,0 +1,49 @@
const iam_routes = {
prefix: '/api/v1/iam',
middleware: [
'auth:APIRoute'
],
get: {
'/policy': [
['middleware::api:Permission', { check: 'v1:iam:policy:list' }],
'controller::api:v1:IAM.get_policies',
],
'/policy/:id': [
['middleware::api:Permission', { check: 'v1:iam:policy:get' }],
'controller::api:v1:IAM.get_policy',
],
},
post: {
'/policy': [
['middleware::api:Permission', { check: 'v1:iam:policy:create' }],
'controller::api:v1:IAM.create_policy',
],
'/check_entity_access': [
['middleware::api:Permission', { check: 'v1:iam:check_entity_access' }],
'controller::api:v1:IAM.check_entity_access',
],
'/check_user_access': [
['middleware::api:Permission', { check: 'v1:iam:check_user_access' }],
'controller::api:v1:IAM.check_user_access',
],
},
patch: {
'/policy/:id': [
['middleware::api:Permission', { check: 'v1:iam:policy:update' }],
'controller::api:v1:IAM.update_policy',
],
},
delete: {
'/policy/:id': [
['middleware::api:Permission', { check: 'v1:iam:policy:delete' }],
'controller::api:v1:IAM.delete_policy',
],
},
}
module.exports = exports = iam_routes

View File

@@ -2,7 +2,7 @@ const ldap_routes = {
prefix: '/api/v1/ldap',
middleware: [
'auth:UserOnly',
'auth:APIRoute',
],
get: {

View File

@@ -2,15 +2,21 @@ const message_routes = {
prefix: '/api/v1/message',
middleware: [
'auth:UserOnly',
'auth:APIRoute',
],
get: {
'/banners': ['controller::api:v1:Message.get_banners'],
'/banners': [
['middleware::api:Permission', { check: 'v1:message:banners:get' }],
'controller::api:v1:Message.get_banners',
],
},
post: {
'/banners/read/:banner_id': ['controller::api:v1:Message.read_banner'],
'/banners/read/:banner_id': [
['middleware::api:Permission', { check: 'v1:message:banners:update' }],
'controller::api:v1:Message.read_banner',
],
},
}

View File

@@ -0,0 +1,41 @@
const oauth_routes = {
prefix: '/api/v1/oauth',
middleware: [
'auth:APIRoute',
],
get: {
'/clients': [
['middleware::api:Permission', { check: 'v1:oauth:clients:list' }],
'controller::api:v1:OAuth.get_clients',
],
'/clients/:id': [
['middleware::api:Permission', { check: 'v1:oauth:clients:get' }],
'controller::api:v1:OAuth.get_client',
],
},
post: {
'/clients': [
['middleware::api:Permission', { check: 'v1:oauth:clients:create' }],
'controller::api:v1:OAuth.create_client',
],
},
patch: {
'/clients/:id': [
['middleware::api:Permission', { check: 'v1:oauth:clients:update' }],
'controller::api:v1:OAuth.update_client',
],
},
delete: {
'/clients/:id': [
['middleware::api:Permission', { check: 'v1:oauth:clients:delete' }],
'controller::api:v1:OAuth.delete_client',
],
},
}
module.exports = exports = oauth_routes

View File

@@ -2,16 +2,25 @@ const password_routes = {
prefix: '/api/v1/password',
middleware: [
'auth:UserOnly',
'auth:APIRoute',
],
get: {
'/resets': ['controller::api:v1:Password.get_resets'],
'/app_passwords': ['controller::api:v1:Password.get_app_passwords'],
'/resets': [
['middleware::api:Permission', { check: 'v1:password:resets:get' }],
'controller::api:v1:Password.get_resets',
],
'/app_passwords': [
['middleware::api:Permission', { check: 'v1:password:app_passwords:get' }],
'controller::api:v1:Password.get_app_passwords',
],
},
post: {
'/app_passwords': ['controller::api:v1:Password.create_app_password'],
'/app_passwords': [
['middleware::api:Permission', { check: 'v1:password:app_passwords:create' }],
'controller::api:v1:Password.create_app_password',
],
'/resets': [
['middleware::auth:RequireTrust', { scope: 'password.reset' }],
'controller::api:v1:Password.reset_password',
@@ -19,7 +28,10 @@ const password_routes = {
},
delete: {
'/app_passwords/:uuid': ['controller::api:v1:Password.delete_app_password'],
'/app_passwords/:uuid': [
['middleware::api:Permission', { check: 'v1:password:app_passwords:delete' }],
'controller::api:v1:Password.delete_app_password',
],
}
}

View File

@@ -2,17 +2,19 @@ const profile_routes = {
prefix: '/api/v1/profile',
middleware: [
'auth:UserOnly',
'auth:APIRoute',
],
get: {
'/:user_id': [ // user_id | 'me'
['middleware::api:Permission', { check: 'v1:profile:get' }],
'controller::api:v1:Profile.fetch',
],
},
patch: {
'/:user_id': [ // user_id | 'me'
['middleware::api:Permission', { check: 'v1:profile:update' }],
'controller::api:v1:Profile.update',
],
},

View File

@@ -0,0 +1,50 @@
const reflect_routes = {
prefix: '/api/v1/reflect',
middleware: [
'auth:APIRoute'
],
get: {
'/scopes': [
['middleware::api:Permission', { check: 'v1:reflect:scopes' }],
'controller::api:v1:Reflect.get_scopes',
],
'/tokens': [
['middleware::api:Permission', { check: 'v1:reflect:tokens:list' }],
'controller::api:v1:Reflect.get_tokens',
],
'/tokens/:id': [
['middleware::api:Permission', { check: 'v1:reflect:tokens:get' }],
'controller::api:v1:Reflect.get_token',
],
},
post: {
'/tokens': [
['middleware::api:Permission', { check: 'v1:reflect:tokens:create'}],
'controller::api:v1:Reflect.create_token',
],
'/check_permissions': [
['middleware::api:Permission', { check: 'v1:reflect:check_permissions' }],
'controller::api:v1:Reflect.check_permissions',
],
},
patch: {
'/tokens/:id': [
['middleware::api:Permission', { check: 'v1:reflect:tokens:update' }],
'controller::api:v1:Reflect.update_token',
],
},
delete: {
'/tokens/:id': [
['middleware::api:Permission', { check: 'v1:reflect:tokens:delete' }],
'controller::api:v1:Reflect.delete_token',
],
},
}
module.exports = exports = reflect_routes

View File

@@ -2,7 +2,7 @@ const saml_routes = {
prefix: '/api/v1/saml',
middleware: [
'auth:UserOnly',
'auth:APIRoute',
],
get: {