Building on the previous articles about how to create a REST server using kbmMW, we have now reached the stage where we should consider access management.

What is access management? It’s the “science of who are allowed to do what.

It is obvious that data exists in this world, which should be protected from being read, created or altered by people/processes we have not authorized to do so. Or turned on its head, some data should be protected and be accessible only by people/processes that we trust.

Other data might be left freely available for reading, but never for modifying and so forth.

Fortunately kbmMW have features built in to support us with that.

We start by adding a TkbmMWAuthorizationManager to the main form (Unit7 in the previous posts).2017-05-26 15_07_56-Project6 - RAD Studio 10.1 Berlin - Unit7.png

We can use the authorization manager as is, standalone, but it often makes sense to connect it to the kbmMWServer instance. Thus set the property kbmMWServer1.AuthorizationManager to point on kbmMWAuthorizationManager1.

This way, every call into the application server will checked by the authorization manager for access rights.

The kbmMW authorization manager is an entity which understands the topics:

  • resource
  • actor
  • role
  • authorization
  • constraint
  • login

A resource is basically anything that you want to add some sort of protection for. It can be database related, it can be a specific object, it can be a function or a service that you want to ensure is only handled in ways that you want it to, by people/processes that you have granted access to it. Resources can be grouped in resource trees, where having access to one resource also automatically provides same access to resources underneath that resource.

An actor, is typically a person (or a person’s login credentials), a process or something else that identifies “someone” that want access to your resource’s.

A role is a way to categorize general access patterns. Roles in a library, could be a librarian, an administrator and a loaner. Roles in a bank could be a customer, a teller, a clerk, an administrator and so forth. The idea is that each of the roles will have different access rights to the various resources. Actors usually will be given at least one role. An actor can have different roles, for example depending on how the actor log’s in, or from where.

An authorization is a “license” to operate as an actor or a role on a specific resource. An authorization can be negative, thus specifically denying an actor or role access to specific resources and their subtrees.

A constraint is a limitation to an authorization or to a login. The authorization may only be valid within a specific timeframe, or be allowed to be accessed from specific equipment and such, or the login can only happen during daytime etc.

A login is the match between an actor/password and a login token. When an actor is attempting to be logged in, the system verifies login name, password, requested role and whatever constraints has been defined related to login in. Only when everything has been checked up and a login is allowed, a token is issued, which the actor/user/process will need to send along with every request it makes to the kbmMW based server.

So let us define two roles we want to have access to our REST server. We can choose to name them ‘Reader’ and ‘ReadWriter’, but as kbmMW do not pose any restrictions to naming of roles (nor on actors and resources), we can name them anything as long as the names are unique within their category (roles, actors, resources).

  • Reader
  • ReadWriter

In code we define the roles like this (for example in the OnCreate event of the main form:

 kbmMWAuthorizationManager1.AddRole('READER');
 kbmMWAuthorizationManager1.AddRole('READWRITER');

We also, somehow, need to tell the authorization manager which actors exists so it can match up login attempts with actors.

The simple way is to predefine them to the authorization manager. That can for example also happen in the OnCreate event of the form, or elsewhere before the first access to the server. The actors can be defined from a database or a configuration file or LDAP etc. as needed.

 kbmMWAuthorizationManager1.AddActor('HANS','HANSPASSWORD','READER');
 kbmMWAuthorizationManager1.AddActor('CHRISTINE','CHRISTINEPASSWORD','READWRITER');

This defines two actors with their passwords, and which role they should act as upon login if they do not specifically ask for a different role.

It is possible not to predefine actors, but instead use an event handler to verify their existence in a different system via the OnLogin event of the kbmMWAuthorizationManager1 instance.

procedure TForm7.kbmMWAuthorizationManager1Login(Sender: TObject;
 const AActorName, ARoleName: string; var APassPhrase: string;
 var AActor: TkbmMWAuthorizationActor; var ARole: TkbmMWAuthorizationRole;
 var AMessage: string);
begin
...
end;

An AActorName and the requested role name in ARoleName is provided. Optionally an actor instance may also be provided, if the actorname is known to kbmMW. If not, AActor is nil, and must be created by you if you know about the actor.

ARole may be nil, if it’s an unknown role that is requested. You can choose to define the role on the fly by returning a newly created TkbmMWAuthorizationRole instance. Remember to add any newly created actor or role instances to the kbmMWAuthorizationManagers Actors and Roles list properties before returning.

APassword will contain the password delivered with the login attempt. You are allowed to modify it on the fly (for example to change it to a SHA256 hash, so no human readable passwords are stored in the authorization manager).

If you return nil for AActor or ARole, then it means that the login failed. You can provide an explanation in the AMessage argument if you want.

But let us continue with our simple actor definition for this sample

Now that we have actors and roles defined, the authorization manager is ready to handle login attempts.

There is only one way to login, and that is by calling the Login method of the authorization manager. This can, for example, be called from a new REST function in your REST service.

An alternative is to let kbmMW automatically detect login attempts, and call the Login method for you. To do that, set the Options property of kbmMWAutorizationManager1 to [mwaoAutoLogin].

As you may remember, all requests to the kbmMW server must be accompanied with a Token identifying a valid login. If that token is not available, kbmMW (with mwaoAutoLogin set), is triggered to use whatever username/password passed on from the caller, as data for a login attempt and will return the token back to the called if the login succeeded.

As a REST server is essentially a web server, adhering to the HTTP protocol standards, what happens when kbmMW detects an invalid (or non existing) login, is that kbmMW will raise an EkbmMWAuthException, which in turn (when the call comes via the REST streamformat), will be translated into an HTTP error 401, which is presented to the caller. In fact, if you would raise that exception anywhere within your business code and you do not manage it yourself, it will automatically be forwarded to the caller as a 401.

This will prompt most browsers to present a login dialog, where username/password can be entered, and next call to back to the server, will include that login information. kbmMW will automatically detect this and use it.

So we have actor, role and login in place. Now we need to determine what resources we have. A resource can be anything you want to tag a unique name on.

Most of the time, it makes sense to define REST methods as a resource. This is done very easily in our smart service, where we have the functions for manipulating and retrieving contacts (Unit8). We use the kbmMW_Auth attribute.

[kbmMW_Service('name:MyREST, flags:[listed]')]
[kbmMW_Rest('path:/MyREST')]
TkbmMWCustomSmartService8 = class(TkbmMWCustomSmartService)
 public
  [kbmMW_Auth('role:[READER,READWRITER], grant:true')]
  [kbmMW_Rest('method:get, path:helloworld, anonymousResult:true')]
  [kbmMW_Method]
  function HelloWorld:TMyResult;

  [kbmMW_Auth('role:[READER,READWRITER], grant:true')]
  [kbmMW_Rest('method:get, path:contacts, anonymousResult:true')]
  function GetContacts:TObjectList;

  [kbmMW_Auth('role:[READWRITER], grant:true')]
  [kbmMW_Rest('method:put, path:addcontact')]
  function AddContact([kbmMW_Rest('value:"{$name}"')] const AName:string;
    [kbmMW_Rest('value:"{$address}"')] const AAddress:string;
    [kbmMW_Rest('value:"{$zipcode}"')] const AZipCode:string;
    [kbmMW_Rest('value:"{$city}"')] const ACity:string):string; overload;

  [kbmMW_Auth('role:[READWRITER], grant:true')]
  [kbmMW_Rest('method:get, path:"addcontact/{name}"')]
  function AddContact([kbmMW_Rest('value:"{name}"')] const AName:string):string; overload;

  [kbmMW_Auth('role:[READWRITER], grant:true')]
  [kbmMW_Rest('method:delete, path:"contact/{id}"')]
  function DeleteContact([kbmMW_Rest('value:"{id}"')] const AID:string):boolean;
 end;

What happens behind the scenes is that kbmMW automatically define resource names for the functions like this: MyREST..AddContect, MyREST..GetContacts etc.

Notice the extra dot! If we had defined the service to have a version, when we created it, that would be put between the dots.

As you can see, the resource name is just a string, and you can define all the resources you want to yourself, but know that if you use kbmMW smart services, it will automatically define resource names in the above format.

kbmMW will also automatically ask the authorization manager to validate that it is allowed to use a resource, upon a call from any client.

You can choose to make finer grained authorization by manually calling the authorization manager for validation of a call like this:

var
  res:TkbmMWAuthorizationStatus;
  sMessage:string;
begin
...
  res:=AuthorizationManager1.IsAuthorized(logintoken, 'YOURRESOURCENAME', sMessage);

res can have the value of mwasAuthorized, mwasNotAuthorized or mwasConstrained.

mwasConstained means that the authorization might be given under different circumstances (different time on day or similar). The returned sMessage may explain in more detail what was the reason that the access was denied.

In a kbmMW smart service, you can get the login token (logintoken) as an argument to the method like this:

 [kbmMW_Auth('role:[READER], grant:true')]
 [kbmMW_Rest('method:get, path:"someCall"')]
 function SomeCall([kbmMW_Arg(mwatToken)] const AToken:string):boolean;

When the SomeCall method is called, its AToken argument contains the logintoken.

You can also access the services ClientIdentity.Token property instead from within your methods if you do not want the token to be part of the argument list of your method call.

Now your REST server is protected by SSL and calls to it’s functionality limited by login.

There are many more features in the authorization manager, which I have not explained here, but visit our site at http://www.components4developers.com, and look for the kbmMW documentations section for whitepapers.

If you like this, please share the word about kbmMW wherever you can and feel free to link, like, share and copy the posts of this blog to where you find they could be useful.

REST easy with kbmMW #1

REST easy with kbmMW #2 – Database

REST easy with kbmMW #3 – SSL

REST easy with kbmMW #5 – Logging

Loading

9 thoughts on “REST easy with kbmMW #4 – Access management”
  1. Below is a portion of the code that I implemented to test the capabilities of the Authorization Manager etc.:
    [kbmMW_Service(‘name:MyREST, flags:[listed]’)]
    [kbmMW_Rest(‘path:/myrest’)]
    TRESTFunctionsService = class(TkbmMWCustomSmartService)
    public
    [kbmMW_Auth(‘role:[WRITER], grant:true’)]
    [kbmMW_Method(‘EchoString’)]
    [kbmMW_Rest(‘method:get, ‘ + ‘path: [ “echostring/{AString}”,”myechostring/{AString}” ]’)]
    function EchoString([kbmMW_Rest(‘value: “{AString}”‘)] const AString: String): String;

    var
    FAuthMan: TkbmMWAuthorizationManager;

    begin
    FAuthMan := TkbmMWAuthorizationManager.Create(Self);
    FAuthMan.Options := [mwaoAutoLogin];
    FAuthMan.AddRole(‘READER’);
    FAuthMan.AddRole(‘WRITER’);
    FAuthMan.AddActor(‘JOEBLOW’,’joeblow’,’READER’);
    FAuthMan.AddActor(‘ALLIEBACK’,’allieback’,’WRITER’);

    kServer.AuthorizationManager := FAuthMan;

    end;

    function TRESTFunctionsService.EchoString(const AString: String): String;
    begin
    Result := AString;
    end;

    When I run the server and open firefox to navigate to the url, I get the login dialog, but when I enter the defined usernames and passwords, I continually get prompted to login again.

    I even tried this:

    procedure TWSMobileServer.AuthLogin(Sender: TObject; const AActorName, ARoleName: string; var APassPhrase: string;
    var AActor: TkbmMWAuthorizationActor; var ARole: TkbmMWAuthorizationRole; var AMessage: string);
    begin
    // Use this event in Wellsoft to follow traditional authentication logic
    if Assigned(AActor) then
    begin
    LogEntry(‘Actor Name: ‘ + AActor.Name);
    LogEntry(‘Role Name: ‘ + AActor.DefaultRole);
    LogEntry(‘Pass Phrase: ‘ + AActor.PassPhrase);
    AMessage := ‘200’;
    LogEntry(‘Message: ‘ + AMessage);
    LogEntry(‘Count of Authorizations: ‘ + IntToStr(FAuthMan.Authorizations.Count));
    end;
    end;

    …to test if the necessary authorization classes for actor and role were being created and populated properly, and they are. Authorizations count is always -0-.

    If I cancel the repeating login dialog – even after entering valid credentials – the servers sends back ‘Anonymous user not authorized’.

  2. One thing that I noticed in stepping through your code, is that this method:

    function TkbmMWAuthorizationResources.Get(const AName:string):TkbmMWAuthorizationResource;
    var
    i:integer;
    s:string;
    begin
    s:=UpperCase(AName);
    for i:=0 to FResources.Count-1 do
    begin
    Result:=FResources.Items[i];
    if Result.FName=s then
    exit;
    end;
    Result:=nil;
    end;

    … has an FResources.Count of -0-.

    So even though I’m using a ‘smart’ service and defining resources inline in the declaration using attributes, it appears that the problem could possibly lie in the fact that kbmMW doesn’t see any resources as being defined.

    As you can see from the previously provided code, I’ve defined 2 actors and 2 roles. They both show up in your code. But no resources show up.

    Anything that I might have missed?

    1. Since I couldn’t get the smart service to generate a resource simply by supplying the following declarations in the service interface:

      [kbmMW_Auth(‘role:[READER,READWRITER], grant:true’)]
      [kbmMW_Rest(‘method:get, path:helloworld, anonymousResult:true’)]
      [kbmMW_Method]
      function HelloWorld:TMyResult;

      …I decided to try explicitly creating a resource this way:

      FAuthMan := TkbmMWAuthorizationManager.Create(Self);
      FAuthMan.Options := [mwaoAutoLogin];
      FAuthMan.DefaultMaxIdleTime := 0;
      FAuthMan.AddResource(‘helloworld’);
      FAuthMan.AddRole(‘READER’);
      FAuthMan.AddActor(‘JOEBLOW’,’joeblow’,’READER’);
      FAuthMan.Actors.Get(‘JOEBLOW’).MaxIdleTime := -1;

      I then traced the creation of AddResource into the constructor of TkbmMWAuthorizatonResource, where my new resource ‘helloworld’ was uppercased and assigned to the FName field.

      When tracing the authorization process I discovered in TkbmMWAuthorizationResources.Get – in the comparison

      if Result.FName=s then

      …Result.FName contains the value of ‘READER’ – a role name, while s contains the value of ‘MyRest.1.0.HelloWorld’, a comparison that will never evaluate to true.

      1. Hi,
        Please use our normal support channels for support, since they are monitored more closely. (nntp://news.components4developers.com)

        With the kbmMW Enterprise Edition installation you also get a host of demo apps. Look in .\Demo\AuthRESTFishFact for one showing how to use authorization along with REST.

        It probably will clear up the confusion.

      2. Are you missing:
        kbmMWSecurity and kbmMWSmartServiceUtils in your service units uses clause in the initialization section?
        If so, the kbmMW_…. attributes are never read and understood by Delphi.
        But please move the diskussion to our news server.

Leave a Reply to REST easy with kbmMW #2 – Database Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.