Project

General

Profile

Actions

Authz2023 » History » Revision 12

« Previous | Revision 12/25 (diff) | Next »
Shuvam Misra, 27/12/2023 11:41 AM


Authorization architecture, design and implementation

It's 2023. Trump may become President of God's Own Country next year. We have moved from bespoke authentication and authorization design and implementation to Keycloak and IDshield. With all this comes a new view about the architecture of authorization data, and its implementation.

Authorisation information

This information specifies what a user can and cannot do. It has four dimensions:

  • Raw capability: this specifies if the user can perform a specific operation. In implementation, it maps on to a web service call (WSC). Can user X call WSC Y?
  • Visibility constraints: this specifies whether the user can see all data in a specific table or for a specific call (e.g. all sales data) and if not, then which subsets can he see? This is equivalent to doing a SELECT on a table and using the visibility constraints for a WHERE clause. For instance, user X can see all sales data and user Y can see only North Zone data. In other words, the visibility constraint defines the scope of the access right.
  • Attribute constraints: this specifies whether the user can see/edit all the attributes (or columns, in DB parlance) of a class of entities, or can only see a subset. For instance, some privileged users can see the full employee list with all attributes, but most users are not allowed to see the salary data. So, there are restrictions on certain restricted attributes. We can then divide the list of attributes into a general-access set and a privileged set.
  • Value constraints: this specifies whether a user's access is restricted to certain value limits of certain quantitative fields. For instance, a junior manager is permitted to approve an invoice with a total value less than a million dollars, whereas a vice president can approve invoices of up to ten million.

Combining all these, we can make a sample statement like this: Mister Joe Pesci can do voucher edits (he has the voucheredit capability) for vouchers of only retail sales (visibility constraint based on voucher type) whose value is less than $20,000 (value constraint). And while he does so, he is not permitted to change the date of the voucher (attribute constraint).

Expressing this in a tight notation, we can specify this tuple:

joe.pesci:   (voucheredit, vouchertype=retailsales, val=(amt,le,20000), attr=!date)

We call these tuples qualified capabilities, which are the result of applying constraints to raw capabilities. Here, the (amt, le, 20000) indicates that the amount of the voucher needs to be less than or equal to (hence le) the limit given.

Mr Pesci may have multiple such qualified capabilities, for various combinations of these four elements.

joe.pesci:   (voucherview, vouchertype=ALL, val={}, attr={})
joe.pesci:   (voucheredit, vouchertype=retailsales, val=(amt,le,20000), attr=!date)
joe.pesci:   (vouchernew,  vouchertype=retailsales, val=(amt,le,20000), attr={})

With this set of records, Mr Pesci can see all vouchers, create vouchers only in retail sales of value less than $20,000 and enter all details (no attribute constraints) but when editing these vouchers, he is not allowed to edit the date.

We can collapse the semantics of the fourth term (attribute constraints) if we expand the set of values for the first term. So, instead of having a generic voucheredit raw capability, we define two raw capabilities: vouchereditfull and vouchereditnodate. With this refinement, we can express the previous set of qualified capabilities in the following way:

joe.pesci:   (voucherview,        vouchertype=ALL,         val={})
joe.pesci:   (vouchereditnodate,  vouchertype=retailsales, val=(amt,le,20000))
joe.pesci:   (vouchernewfull,     vouchertype=retailsales, val=(amt,le,20000))

Thus, the fourth member of the tuple can be eliminated everywhere by applying this trick of defining a more fine-grained set of capabilities.

We make one more pragmatic simplification: we assume that quantitative limits (i.e. val=) will always be upper caps. It is very unlikely that there will be a lower cap to authorisation constraints. In that case, we may assume that the operator will always be le, therefore val=(amt, le, 20000) now becomes simplified to limit=(amt, 20000). Therefore we now have:

joe.pesci:   (voucherview,        vouchertype=ALL,         limit={})
joe.pesci:   (vouchereditnodate,  vouchertype=retailsales, limit=(amt,20000))
joe.pesci:   (vouchernewfull,     vouchertype=retailsales, limit=(amt,20000))

We can now move from the particular to the general. If we generalise the example of Mr Pesci's capabilities. We can now say that a qualified capability has

  • one raw capability
  • zero or more scope constraints
  • zero or more upper-limit constraints

Switching to JSON, we get

"usercaps": {
    "user": "joe.pesci",
    "caplist": [{
        "cap": "voucherview",
        "scope": [
            {"vouchertype": "ALL"}
        ],
        "limit": []
    },{
        "cap": "vouchereditnodate",
        "scope": [
            {"vouchertype": "retailsales"}
        ],
        "limit": [
            {"amt": 20000}
        ]
    },{
        "cap": "vouchernewfull",
        "scope": [
            {"vouchertype": "retailsales"}
        ],
        "limit": [
            {"amt": 20000}
        ]
    }]
}

There will be one such block for each user in the system, and the caplist can have dozens or hundreds of elements in its array. In this representation, one qualified capability is represented by:

    {
        "cap": "vouchernewfull",
        "scope": [
            {"vouchertype": "retailsales"}
        ],
        "limit": [
            {"amt": 20000}
        ]
    }

Using the authorisation information

The list of tuples shown above may be searched by a matching engine to see if a user has the right to perform an operation. The matching may start from one end of the series and go down till it reaches. If it finds a tuple which matches in all aspects with the operation being attempted, the matching ends saying "Yes, proceed". If the matching reaches the end of the series without any matching, then it is deemed that the user does not have the rights to perform the operation.

For a tuple to match, all its terms must match.

The authz_check() function

This function may be designed to take two parameters: all details of an access attempt, and the array of authorization rules for the user.

The application code needs to know the list of attributes based on which authorization decisions are made. Basing our example on the tuples shown earlier, the code may go through the following questions and put together a set of attributes and values:

  • who is the user? joe.pesci
  • what operation is being attempted? Voucher edit.
    • Is the date too being updated? If yes, then vouchereditfull
    • Else, vouchereditnondate
  • What's the type of the voucher being accessed? Pull out the record from the database and find out its type. bulksales
  • What is the amount of the voucher being accessed? 15520.50

It is not necessary that all details of the operation being attempted are contained in the request parameters of the WSC. Some authorisation determining parameters may be environmental, like

  • what is the country from which the access is being attempted (use geo-IP)
  • what is the time of day? (It's conceivable that access rules do not permit editing of records outside office hours, only viewing.)

So, first, all the relevant attributes of the operation request are put together:

{
    "user": "joe.pesci",
    "vouchertype": "bulksales",
    "cap": "vouchereditfull",
    "voucheramt": "15520.50"
}

Having put together this packet of attributes and values, the authorization-check function may be called. The function will first peep inside this structure, pull out the username (joe.pesci), load the user's authorization rights tuples from backing store, and perform a matching operation. If the full set of authorization tuples are part of the JWT payload, then no database access is needed.

Given the sample tuples listed above, joe.pesci will not be granted permission to perform the operation, since he has no rights to operate on bulksales vouchers.

Variety of constraint variables

The examples above have dealt with voucher operations including viewing, editing, and creation. For those operations, the list of constraint variables were

  • voucher type
  • voucher amount

For an entirely different operation, say viewing of MIS reports of sales data, the constraints may be

  • which zone the user belongs to (he will see only his own region's data)
  • which department he belongs to (he will see only his product category's data, and dept maps to product category)
  • what his rank is (this decides whether he can see only the last month's data, or the last year's, or all historical data)

So, for voucher-related capabilities, one set of constraint variables are applied, and for sales report viewing, a totally different set of variables apply. These can be keyed to the capability:

  • for voucherview, vouchereditfull, vouchereditnondate, vouchernew, the voucher type is a constraint variable
  • for salesreport, the user's department ID is a constraint variable

Updated by Shuvam Misra over 1 year ago · 12 revisions