REST easy with kbmMW #4 – Access management

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<TContact>;

  [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 servers are very easy to make using kbmMW.

First we make a server application (or service… it’s up to you).

In this case we will add a simple form both providing a GUI and a place for our kbmMW components.

In Delphi click File – New – VCL Forms application

Add one of each of the following kbmMW components to the form:

  • TkbmMWServer
  • TkbmMWTCPIPIndyServerTransport

2017-05-20 14_50_54-Project6 - RAD Studio 10.1 Berlin - Unit7.png

Set the Server property of kbmMWTCPIPIndyServerTransport1 to kbmMWServer1.

Double click the Bindings property of kbmMWTCPIPIndyServerTransport1 to open its editor. Add a binding for 0.0.0.0 port 80, which is the default HTTP server port. You can choose any other binding you want, but make sure to tell your REST users which port to access.2017-05-20 14_53_34-Project6 - RAD Studio 10.1 Berlin - Unit7.png

Set the Streamformat property of kbmMWTCPIPIndyTransport1 to REST.2017-05-20 14_56_15-Project6 - RAD Studio 10.1 Berlin - Unit7.png

Save your project. Saving it will prompt Delphi to update your uses section with required units.

Double click on the form, to create a form OnCreate event handler.

Enter two lines of code so it looks like this:

procedure TForm7.FormCreate(Sender: TObject);
begin
 kbmMWServer1.AutoRegisterServices;
 kbmMWServer1.Active:=true;
end;

Locate the interface section’s uses clause and add an additional unit kbmMWRESTTransStream.

Save again.

Now your unit code should look similar to this:

unit Unit7;

interface

uses
 Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
 Vcl.Controls, Vcl.Forms, Vcl.Dialogs, kbmMWCustomTransport, kbmMWServer,
 kbmMWTCPIPIndyServerTransport, kbmMWRESTTransStream;

type
 TForm7 = class(TForm)
 kbmMWServer1: TkbmMWServer;
 kbmMWTCPIPIndyServerTransport1: TkbmMWTCPIPIndyServerTransport;
 procedure FormCreate(Sender: TObject);
 private
 { Private declarations }
 public
 { Public declarations }
 end;

var
 Form7: TForm7;

implementation

{$R *.dfm}

procedure TForm7.FormCreate(Sender: TObject);
begin
 kbmMWServer1.AutoRegisterServices;
 kbmMWServer1.Active:=true;
end;

end.

Basically we now have the foundation for a REST capable web server.

Let’s add some functionality that can be called from any REST client.

In Delphi, Click FileNewOtherComponents4Developers Wizards and select the kbmMW Service Wizard. Click OK.

2017-05-20 15_09_18-Project6 - RAD Studio 10.1 Berlin - Unit7.png

Before continuing to select the type of kbmMW service we will add, we need to decide what type of REST server we want to create. It can be a pure REST server, which only serves data from your own code, or it can be a regular web server, which will also be able to serve data from files on disk, like html templates, images, CSS files etc.

For our purpose we just want to make a pure REST server, so we select
Smart service/kbmMW_1.0.

If you wanted it to be able to serve files from disk, and even proxy requests on to other FastCGI compatible servers (like PHP etc) you would have chosen HTTP Smart service.

2017-05-20 15_13_56-kbmMW service wizard.png

Click the funny looking next button. Type in the default name your REST service should be known as. In this sample, I’ve called it MyREST.

2017-05-20 15_15_16-Project6 - RAD Studio 10.1 Berlin - Unit7.png

Click next until you get to this page, then click the green checkmark button

2017-05-20 15_16_42-kbmMW service wizard.png

Now an almost empty service has been generated for you.

On the surface it looks like a regular TDataModule, and as such can contain any component that you can put on a TDataModule. But right now we are more interested in its code. Press F12 to switch to code view.

Browse past the explanatory remarks at the top, until you get to the actual code.

type

[kbmMW_Service('name:MyREST, flags:[listed]')]
[kbmMW_Rest('path:/MyREST')]
 // Access to the service can be limited using the [kbmMW_Auth..] attribute.
 // [kbmMW_Auth('role:[SomeRole,SomeOtherRole], grant:true')]

TkbmMWCustomSmartService8 = class(TkbmMWCustomSmartService)
 private
 { Private declarations }
 protected
 { Protected declarations }
 public
 { Public declarations }
 // HelloWorld function callable from both a regular client,
 // due to the optional [kbmMW_Method] attribute,
 // and from a REST client due to the optional [kbmMW_Rest] attribute.
 // The access path to the function from a REST client (like a browser)+
 // is in this case relative to the services path.
 // In this example: http://.../MyREST/helloworld
 // Access to the function can be limited using the [kbmMW_Auth..] attribute.
 // [kbmMW_Auth('role:[SomeRole,SomeOtherRole], grant:true')]
 [kbmMW_Rest('method:get, path:helloworld')]
 [kbmMW_Method]
 function HelloWorld:string;
 end;

implementation

uses kbmMWExceptions;

{$R *.dfm}


// Service definitions.
//---------------------

function TkbmMWCustomSmartService8.HelloWorld:string;
begin
 Result:='Hello world';
end;

The interesting bits are shown above in bold.

If you compile and run your application now, you have a REST only capable webserver which have one function… HelloWorld taking no arguments, and that returns a string.

Open up your favorite web browser and lets test the function by typing this in the address field:

http://localhost/MyREST/helloworld

Make sure that case is correct, since the HTTP standard describes that the URL part of the address must be case sensitive. If you would write http://localhost/MyREST/HelloWorld instead you would be told that the request is invalid.

This is all nice… but my REST client expect to receive a JSON object, not just simple text.

Ok.. I’ll show 3 ways to do that… the very manual way, the semi automated way and the fully automated way.

The manual way. Change the HelloWorld function to look like this:

function TkbmMWCustomSmartService8.HelloWorld:string;
begin
 Result:='{''result'':''Hello world''}';
end;

The REST client will receive an anonymous object with a property called result, containing “Hello world”.

The semi automated way:

uses kbmMWExceptions
 ,kbmMWObjectNotation
 ,kbmMWJSON;

{$R *.dfm}


// Service definitions.
//---------------------

function TkbmMWCustomSmartService8.HelloWorld:string;
var
 o:TkbmMWONObject;
 jsonstreamer:TkbmMWJSONStreamer;
begin
 o:=TkbmMWONObject.Create;
 jsonstreamer:=TkbmMWJSONStreamer.Create;

 o.AsString['result']:='Hello world';
 Result:=jsonstreamer.SaveToUTF16String(o);

 jsonstreamer.Free;
 o.Free;
end;

This allows you to create complex JSON documents pretty easily. The cool part is that since we use kbmMW’s object notation framework, we could have chosen to stream it as XML or YAML or BSON or MessagePack instead by simply instantiating the appropriate streamer.

The automated way:

type

TMyResult = class
private
 FResult:string;
public
 property Result:string read FResult write FResult;
end;

 [kbmMW_Service('name:MyREST, flags:[listed]')]
 [kbmMW_Rest('path:/MyREST')]
 // Access to the service can be limited using the [kbmMW_Auth..] attribute.
 // [kbmMW_Auth('role:[SomeRole,SomeOtherRole], grant:true')]

TkbmMWCustomSmartService8 = class(TkbmMWCustomSmartService)
 private
 [kbmMW_Rest('method:get, path:helloworld, anonymousResult:true')]
 [kbmMW_Method]
 function HelloWorld:TMyResult;
 end;

implementation

uses kbmMWExceptions;

{$R *.dfm}

// Service definitions.
//---------------------

function TkbmMWCustomSmartService8.HelloWorld:TMyResult;
begin
 Result:=TMyResult.Create;
 Result.Result:='Hello world';
end;

initialization
 TkbmMWRTTI.EnableRTTI(TkbmMWCustomSmartService8);
 kbmMWRegisterKnownClasses([TMyResult]);
end.

The automated way simply means returning an object with the desired information. kbmMW will automatically convert the object to JSON (because we are using the REST streamformat).

To make sure that kbmMW knows about the object type, we register it via the kbmMWRegisterKnownClasses. If we didn’t, kbmMW would complain that it do not know about the object.

Do not worry about the TMyResult instance being leaked. kbmMW will automatically free it when it goes out of scope. If you specifically do not want the returned object to be freed by kbmMW, you can tell so by including freeResult:false in the kbmMW_Rest attribute for the HelloWorld method.

Also notice that the kbmMW_Rest attribute now includes anonymousResult:true.

This tells kbmMW that we want the resulting JSON to be anonymous. If we didn’t include that attribute setting, the result would have looked like this:

{"TMyResult":{"Result":"Hello world"}}

Which is not necessarily wrong, but different.

There are lots of control options of how the object should be streamed by setting various attributes on it. One can for example choose that the Result property should be returned under a different name etc.

kbmMW also understands returning TkbmMemTable instances, arrays and many other types of information, so it is really easy to REST’ify your kbmMW business functionality with almost no lines of additional code.

As a final comment, since the HelloWorld method is also tagged with the attribute [kbmMW_Method], it is also callable by native kbmMW clients.

Feel free to share, comment and like this post 🙂