Step 5. Authorization

Once we’ve established who our user is, we need to implement some way of controlling what operations our user can perform, based on the rights he/she has been assigned. In this tutorial, we will be taking a look at how to install access limitations that are based on user roles. The roles themselves are stored in the Roles microservice and are loaded into a UserSession by the loadSession interceptor we discussed in the previous step.

Our Authorizer class was made to provide flexible access management. We will be using this class to limit access to certain operations in our facade’s RESTful services. This class’s implementation can be found in the Authorize.py file, located in the folder services/version1. Its code is as follows:

/src/services/version1/Authorize.ts

import { UnauthorizedException } from 'pip-services3-commons-nodex';
import { HttpResponseSender } from 'pip-services3-rpc-nodex';

import { BasicAuthManager } from 'pip-services3-rpc-nodex';
import { RoleAuthManager } from 'pip-services3-rpc-nodex';
import { OwnerAuthManager } from 'pip-services3-rpc-nodex';

export class AuthorizerV1 {
    private basicAuth = new BasicAuthManager();
    private roleAuth = new RoleAuthManager();
    private ownerAuth = new OwnerAuthManager();

    public anybody(): (req: any, res: any, next: () => void) => void {
        return this.basicAuth.anybody();
    }

    public signed(): (req: any, res: any, next: () => void) => void {
        return this.basicAuth.signed();
    }

    public owner(idParam: string = 'user_id'): (req: any, res: any, next: () => void) => void {
        return this.ownerAuth.owner(idParam);
    }
        
    public ownerOrAdmin(idParam: string = 'user_id'): (req: any, res: any, next: () => void) => void {
        return this.ownerAuth.ownerOrAdmin(idParam);
    }

    public siteRoles(roles: string[], idParam: string = 'site_id'): (req: any, res: any, next: () => void) => void {
        return (req, res, next) => {
            let user = req.user;
            if (user == null) {
                HttpResponseSender.sendError(
                    req, res,
                    new UnauthorizedException(
                        null, 'NOT_SIGNED',
                        'User must be signed in to perform this operation'
                    ).withStatus(401)
                );
            } else {
                let siteId = req.params[idParam];
                let authorized = user.roles.includes('admin');
                
                if (siteId != null && !authorized) {
                    for (let role of roles)
                        authorized = authorized || user.roles.includes(siteId + ':' + role);
                }

                if (!authorized) {
                    HttpResponseSender.sendError(
                        req, res,
                        new UnauthorizedException(
                            null, 'NOT_IN_SITE_ROLE',
                            'User must be site:' + roles.join(' or site:') + ' to perform this operation'
                        ).withDetails('roles', roles).withStatus(403)
                    );
                } else {
                    next();
                }
            }
        };
    }

    public admin(): (req: any, res: any, next: () => void) => void {
        return this.roleAuth.userInRole('admin');
    }

    public siteAdmin(idParam: string = 'site_id'): (req: any, res: any, next: () => void) => void {
        return this.siteRoles(['admin'], idParam);
    }

    public siteManager(idParam: string = 'site_id'): (req: any, res: any, next: () => void) => void {
        return this.siteRoles(['admin', 'manager'], idParam);
    }

    public siteUser(idParam: string = 'site_id'): (req: any, res: any, next: () => void) => void {
        return this.siteRoles(['admin', 'manager', 'user'], idParam);
    }

    public siteAdminOrOwner(userIdParam: string = 'user_id', siteIdParam: string = 'site_id'): (req: any, res: any, next: () => void) => void {
        return (req, res, next) => {
            let user = req.user;
            if (user == null) {
                HttpResponseSender.sendError(
                    req, res,
                    new UnauthorizedException(
                        null, 'NOT_SIGNED',
                        'User must be signed in to perform this operation'
                    ).withStatus(401)
                );
            } else {
                let userId = req.params[userIdParam] || req.param(userIdParam);
                if (userId != null && userId == user.user_id) {
                    next();
                } else {
                    let siteId = req.params[siteIdParam];
                    let authorized = user.roles.includes('admin')
                        || user.roles.includes(siteId + ':admin');
                    
                    if (!authorized) {
                        HttpResponseSender.sendError(
                            req, res,
                            new UnauthorizedException(
                                null, 'NOT_IN_SITE_ROLE',
                                'User must be site:admin to perform this operation'
                            ).withDetails('roles', ['admin']).withStatus(403)
                        );
                    } else {
                        next();
                    }
                }
            }
        };
    }
}

/src/service/services/version1/Authorize.cs

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using PipServices3.Commons.Refer;
using PipServices3.Rpc.Auth;
using PipServices3.Rpc.Services;
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Pip.Services.SampleFacade.Build;
using Pip.Services.SampleFacade.Operations.Version1;
using System.Collections.Generic;
using PipServices3.Commons.Errors;
using System.Linq;
using Microsoft.Extensions.Primitives;

namespace Pip.Services.SampleFacade.Services.Version1
{
    public class AuthorizerV1 : IReferenceable
    {
        private BasicAuthorizer _basicAuth = new BasicAuthorizer();
        private RoleAuthorizer _roleAuth = new RoleAuthorizer();
        private OwnerAuthorizer _ownerAuth = new OwnerAuthorizer();

        public void SetReferences(IReferences references)
        {
        }

        public Func<HttpRequest, HttpResponse, ClaimsPrincipal, RouteData, Func<Task>, Task> Anybody()
        {
            return _basicAuth.Anybody();
        }

        public Func<HttpRequest, HttpResponse, ClaimsPrincipal, RouteData, Func<Task>, Task> Signed()
        {
			return _basicAuth.Signed();
		}

        public Func<HttpRequest, HttpResponse, ClaimsPrincipal, RouteData, Func<Task>, Task> Owner(string idParam = "user_id")
        {
            return _ownerAuth.Owner(idParam);
        }

        public Func<HttpRequest, HttpResponse, ClaimsPrincipal, RouteData, Func<Task>, Task> OwnerOrAdmin(string idParam = "user_id")
        {
            return async (request, response, user, routeData, next) =>
            {
                if (user == null || !user.Identity.IsAuthenticated)
                {
                    await HttpResponseSender.SendErrorAsync(
                        response,
                        new UnauthorizedException(
                            null, "NOT_SIGNED",
                            "User must be signed in to perform this operation"
                        ).WithStatus(401)
                    );
                }
                else
                {
                    var identity = user.Identity as ClaimsIdentity;
                    var userIdClaim = identity?.Claims.FirstOrDefault(c =>
                        c.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier");

					if (!request.Query.TryGetValue(idParam, out StringValues userId))
					{
						userId = routeData.Values.TryGetValue(idParam, out object idValue) 
                            ? new StringValues(idValue.ToString())
                            : new StringValues();
					}

					if (userIdClaim?.Value != userId.ToString())
                    {
                        await HttpResponseSender.SendErrorAsync(
                            response,
                            new UnauthorizedException(
                                null, "FORBIDDEN",
                                "Only data owner can perform this operation"
                            ).WithStatus(403)
                        );
                    }
                    else
                    {
                        await next();
                    }
                }
            };

            //return _ownerAuth.OwnerOrAdmin(idParam);
        }

        public Func<HttpRequest, HttpResponse, ClaimsPrincipal, RouteData, Func<Task>, Task> SiteRoles(string[] roles, string idParam = "site_id")
        {
            return async (HttpRequest request, HttpResponse response, ClaimsPrincipal user, RouteData routeData, Func<Task> next) =>
            {
                var sessionUser = GetContextItem<SessionUserV1>(request, "user");

                if (sessionUser == null)
                {
                    await HttpResponseSender.SendErrorAsync(response, new UnauthorizedException(
                        null, "NOT_SIGNED",
                        "User must be signed in to perform this operation",
                        null
                    ).WithStatus(401));

                    return;
                }

                var siteId = routeData.Values["site_id"];
                var authorized = sessionUser.Roles.Contains("admin");

                if (siteId != null && !authorized)
                {
					foreach (var role in roles)
					{
                        authorized = authorized || sessionUser.Roles.Contains(siteId + ":" + role);
                    }
                }

                if (!authorized)
                {
                    await HttpResponseSender.SendErrorAsync(response, new UnauthorizedException(
                            null, "NOT_IN_SITE_ROLE",
                            "User must be site:" + string.Join(" or site:", roles) + " to perform this operation"
                        ).WithDetails("roles", roles).WithStatus(403));

                    return;
                }

                await next();
            };
        }

        public Func<HttpRequest, HttpResponse, ClaimsPrincipal, RouteData, Func<Task>, Task> Admin()
        {
            return _roleAuth.UserInRole("admin");
        }

        public Func<HttpRequest, HttpResponse, ClaimsPrincipal, RouteData, Func<Task>, Task> SiteAdmin(string idParam = "site_id")
        {
            return SiteRoles(new[] { "admin" }, idParam);
        }

        public Func<HttpRequest, HttpResponse, ClaimsPrincipal, RouteData, Func<Task>, Task> SiteManager(string idParam = "site_id")
        {
            return SiteRoles(new[] { "admin", "manager" }, idParam);
        }

        public Func<HttpRequest, HttpResponse, ClaimsPrincipal, RouteData, Func<Task>, Task> SiteUser(string idParam = "site_id")
        {
            return SiteRoles(new[] { "admin", "manager", "user" }, idParam);
        }

        public Func<HttpRequest, HttpResponse, ClaimsPrincipal, RouteData, Func<Task>, Task> SiteAdminOrOwner(string userIdParam = "user_id", string siteIdParam = "site_id")
        {
            return async (HttpRequest request, HttpResponse response, ClaimsPrincipal user, RouteData routeData, Func<Task> next) =>
            {
                var sessionUser = GetContextItem<SessionUserV1>(request, "user");

                if (sessionUser == null)
                {
                    await HttpResponseSender.SendErrorAsync(response, new UnauthorizedException(
                        null, "NOT_SIGNED",
                        "User must be signed in to perform this operation",
                        null
                    ).WithStatus(401));

                    return;
                }

                var userId = request.Query[userIdParam].ToString();

				if (userId == null || userId != sessionUser.Id)
				{
					var siteId = request.Query[siteIdParam].ToString();
					var authorized = sessionUser.Roles.Contains("admin")
                        || sessionUser.Roles.Contains(siteId + "admin");

					if (!authorized)
					{
						await HttpResponseSender.SendErrorAsync(response, new UnauthorizedException(
								null, "NOT_IN_SITE_ROLE",
								"User must be site:admin to perform this operation"
							).WithDetails("roles", new[] { "admin" }).WithStatus(403));

						return;
					}
				}

				await next();
            };
        }

        private static T GetContextItem<T>(HttpRequest request, string name)
            where T : class
        {
            if (request != null && request.HttpContext.Items.TryGetValue(name, out object item))
            {
                return item as T;
            }

            return null;
        }
    }
}

/services/version1/Authorize.go

package services1

import (
	"net/http"

	"github.com/gorilla/mux"
	cdata "github.com/pip-services3-go/pip-services3-commons-go/data"
	cerr "github.com/pip-services3-go/pip-services3-commons-go/errors"
	rpcauth "github.com/pip-services3-go/pip-services3-rpc-go/auth"
	rpcservices "github.com/pip-services3-go/pip-services3-rpc-go/services"
)

type AuthorizerV1 struct {
	basicAuth rpcauth.BasicAuthManager
	roleAuth  rpcauth.RoleAuthManager
}

func NewAuthorizerV1() *AuthorizerV1 {
	c := &AuthorizerV1{
		basicAuth: rpcauth.BasicAuthManager{},
		roleAuth:  rpcauth.RoleAuthManager{},
	}
	return c
}

// Anybody who entered the system
func (c *AuthorizerV1) Anybody() func(res http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
	return c.basicAuth.Anybody()
}

// Only registered and authenticated users
func (c *AuthorizerV1) Signed() func(res http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
	return c.basicAuth.Signed()
}

// System administrator
func (c *AuthorizerV1) Admin() func(res http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
	return c.roleAuth.UserInRole("admin")
}

// Only the user session owner
func (c *AuthorizerV1) Owner(idParam string) func(res http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
	if idParam == "" {
		idParam = "user_id"
	}
	return func(res http.ResponseWriter, req *http.Request, next http.HandlerFunc) {

		user, ok := req.Context().Value("user").(cdata.AnyValueMap)
		partyId := req.URL.Query().Get(idParam)
		if partyId == "" {
			partyId = mux.Vars(req)[idParam]
		}

		if !ok {
			rpcservices.HttpResponseSender.SendError(
				res, req,
				cerr.NewUnauthorizedError(
					"", "NOT_SIGNED",
					"User must be signed in to perform c operation").WithStatus(401),
			)
		} else if partyId == "" {
			rpcservices.HttpResponseSender.SendError(
				res, req,
				cerr.NewUnauthorizedError(
					"", "NO_USER_ID",
					"User id is not defined").WithStatus(401),
			)
		} else {
			isOwner := partyId == user.GetAsString("id")

			if !isOwner {
				rpcservices.HttpResponseSender.SendError(
					res, req,
					cerr.NewUnauthorizedError(
						"", "NOT_OWNER", "Only user owner access is allowed").WithDetails("user_id", partyId).WithStatus(403),
				)
			} else {
				next.ServeHTTP(res, req)
			}
		}
	}
}

Not available

/pip_facades_sample_python/services/version1/Authorize.py

# -*- coding: utf-8 -*-
from typing import List

import bottle
from pip_services3_commons.convert import JsonConverter
from pip_services3_commons.errors import UnauthorizedException
from pip_services3_rpc.auth.BasicAuthorizer import BasicAuthorizer
from pip_services3_rpc.auth.OwnerAuthorizer import OwnerAuthorizer
from pip_services3_rpc.auth.RoleAuthorizer import RoleAuthorizer


class AuthorizerV1:

    def __init__(self):
        self.__basic_auth = BasicAuthorizer()
        self.__role_auth = RoleAuthorizer()
        self.__owner_auth = OwnerAuthorizer()

    # Anybody who entered the system
    def anybody(self):
        return self.__basic_auth.anybody()

    # Only registered and authenticated users
    def signed(self):
        return self.__basic_auth.signed()

    # Only the user session owner
    def owner(self, id_param: str = 'user_id'):
        return self.__owner_auth.owner(id_param)

    def owner_or_admin(self, id_param: str = 'user_id'):
        return self.__owner_auth.owner_or_admin(id_param)

    def site_roles(self, roles: List[str], id_param: str = 'site_id'):
        def inner():
            user = getattr(bottle.request, 'user', None)

            if user is None:
                raise UnauthorizedException(
                    None, 'NOT_SIGNED',
                    'User must be signed in to perform this operation'
                ).with_status(401)

            else:
                user.roles = getattr(user, 'roles', False) or []
                site_id = bottle.request.params['kwargs'].get(id_param)
                authorized = 'admin' in user.roles
                if site_id is not None and not authorized:
                    for role in roles:
                        authorized = authorized or (site_id + ':' + role) in user.roles

                if not authorized:
                    raise UnauthorizedException(
                        None, 'NOT_IN_SITE_ROLE',
                        'User must be site:' + ' or site:'.join(roles) + ' to perform this operation'
                    ).with_details('roles', roles).with_status(403)

        return inner

    def admin(self):
        return self.__role_auth.user_in_role('admin')

    def site_admin(self, id_param: str = 'site_id'):
        return self.site_roles(['admin'], id_param)

    def site_manager(self, id_param: str = 'site_id'):
        return self.site_roles(['admin', 'manager'], id_param)

    def site_user(self, id_param: str = 'site_id'):
        return self.site_roles(['admin', 'manager', 'user'], id_param)

    def site_admin_or_owner(self, user_id_param: str = 'user_id', site_id_param: str = 'site_id'):
        def inner():
            user = bottle.request.user
            if user is None:
                raise UnauthorizedException(
                    None, 'NOT_SIGNED',
                    'User must be signed in to perform this operation'
                ).with_status(401)

            else:
                user_id = dict(bottle.request.query.decode()).get(user_id_param) or JsonConverter.to_json(
                    bottle.request.json)
                if user_id is not None and user_id == user.user_id:
                    return
                else:
                    site_id = bottle.request.params.get(site_id_param)
                    authorized = 'admin' in user.roles or site_id + ':admin' in user.roles
                    if not authorized:
                        raise UnauthorizedException(
                            None, 'NOT_IN_SITE_ROLE',
                            'User must be site:admin to perform this operation'
                        ).with_details('roles', ['admin']).with_status(403)

        return inner

Not available

Let’s take a closer look at each of these methods:

  • anybody - allows everyone access, even unauthorized users.
  • signed - access is granted only to authorized users.
  • admin - access is granted only to users with the Administrator role.
  • owner - access is granted only for the session owner.

The logic pretty much boils down to making a decision about whether we should allow further access, or send an answer with the corresponding error. In case of the latter, the error is based on the information provided by the clients and the information about the user that is embedded into the interceptor’s request for the active session.

Setting specific access levels to certain resources is configured when registering routes in the service. The service’s implementation is described in Step 6 - REST services and versioning.

Step 6 - REST services and versioning