Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authentication with ACL #3

Closed
electrical opened this issue Sep 28, 2011 · 36 comments
Closed

Authentication with ACL #3

electrical opened this issue Sep 28, 2011 · 36 comments

Comments

@electrical
Copy link

Hi,

Like i said on Twitter i'm searching for a more extensive way of authentication.
In this case i want the users to authenticate them selves so i know what rights that user has.
each user can have different rights to different REST functions.

I could do HTTP AUTH, and use that to build up the ACL and check that when a REST call is done ?
Or is there a cleaner way ?

@ghost ghost assigned Luracast Sep 28, 2011
@Luracast
Copy link
Collaborator

Restler has a hidden GEM called annotations support which is not yet documented fully. Reason being Restler 3.0 which is currently in production changes the syntax slightly. Any way since you have asked I will provide the code to make it work on Restler 2.0

@Luracast
Copy link
Collaborator

Luracast commented Oct 2, 2011

Access Control using Annotations

Use the following 3 files as an example to write your own secure version :)

PHPDoc comment with the following syntax will set the properties of specified class when an instance is made by Restler

@class ClassName(property1=value&property2=value)

This works for Authentication classes and Format classes

look at the following URI's to understand how it will work

Usage Example

index.php/user and index.php/admin

{
  "error": {
    "code": 401,
    "message": "Unauthorized"
  }
}

index.php/user?password=anyone and index.php/user?password=secretpassword


"allow both user & admin"

index.php/admin?password=secretpassword


"allow only admin"

index.php/admin?password=anyone

{
  "error": {
    "code": 401,
    "message": "Unauthorized: Insufficient Access Rights"
  }
}

PHP Files

accesscontrol.php (Auth class)

<?php
class AccessControl implements iAuthenticate{

    public $requiredRole = 'admin';

    function __isAuthenticated() {

        //hardcoded password=>role for brevity
        $roles = array('anyone'=>'user', 'secretpassword'=>'admin');

        if(!isset($_GET['password']) || !array_key_exists($_GET['password'], $roles))
            return FALSE;
        switch ($roles[$_GET['password']]){
            case 'admin':
               return TRUE;
            case 'user':
                if($this->requiredRole=='user')return TRUE;   
        }
        throw new RestException(401, 'Insufficient Access Rights');
    }

}

simple.php (API Class)

<?php
class Simple {
    /**
     * @class AccessControl(requiredRole=user)
     */
    protected function user() {
        return 'allow both user & admin';
    }
    /**
     * @class AccessControl(requiredRole=admin)
     */
    protected function admin(){
        return 'allow only admin';
    }
}

index.php (Gateway)

<?php
require_once '../../restler/restler.php';

spl_autoload_register('spl_autoload');

$r = new Restler();

$r->addAPIClass('Simple','');
$r->addAuthenticationClass('AccessControl');
$r->handle();

@Luracast Luracast closed this as completed Oct 2, 2011
@electrical
Copy link
Author

Thanks for your time and code :-)
I'm gonna work with it and see if i can get it to work.

Cheers.

@electrical
Copy link
Author

Just to let you know, i got it to work how i want it to.
1 small addition to it.
Sometimes i need to know which ACL is being used in a function because same user roles can access the same function but are not able to do the same.

In my case in a DELETE function users are allowed only to delete their own items or able to delete any item depending on their user role.

To export the userrole i put this in restler.php:

$object->auth_obj = $auth_obj;

Not sure if it is usefull to implement in your version ?

@Luracast
Copy link
Collaborator

Luracast commented Oct 2, 2011

I need to find a decoupled way for doing this! Authentication methods are called before the actual method so they can't have a reference to objects that are dealt within.

I'm trying to avoid referring to Auth instance from the api method directly, then we cant use the method with out the auth class

Maybe we can use a data class with static properties that we want to exchange, it anyway has default values so API method wont fail when Auth class is missing

What do you think?

<?php

class UserDetails{
    static $name = 'defaultUser';
    static $role = 'user';
}
//with in the Access Control method
UserDetails::$name = 'Arul';
UserDetails::$role = 'admin';

@electrical
Copy link
Author

Sounds like a good plan.
I'll have a look if i can build an example up quickly, unless you already know how you wanna do it :-)

@electrical
Copy link
Author

made a quicky which can be implemented more generic i think:

<?php

//inside the Restler class definition
private static $data = array();

public static function AddObject($name,$object) {
     self::$data[$name] = $object;
}

public static function GetObject($name) {
    return(self::$data[$name]);
}

That way you can export any object and import it anywhere you like

@electrical
Copy link
Author

Quick example:

Set objects:

Restler::AddObject("restler",$this);
Restler::AddObject("auth_".$auth_class,$auth_obj);

and get an object:

$obj = Restler::GetObject("auth_SimpleAuth");
$var = $obj->requiredRole;

@Luracast
Copy link
Collaborator

Luracast commented Oct 2, 2011

This is good, but we should do this in a generic VO kind of class instead of Restler

That way your class can be reused elsewhere with out any change, just by copying the VO class along with it

By the way, In case you want to do it the other way

Restler places its instance as the restler property in all the restler instantiated classes including AuthClasses, APIClasses, Formats, Responder

Which you may use to read properties

@electrical
Copy link
Author

Very good point :-)
I just thought to keep it dumb and simple instead of having an other class added.
But yeah, lets use a separate class for it.

@electrical
Copy link
Author

Euhm, can you give an example of your second proposal ?
Not getting it quick ( can be to tired for it :p )

@rpijnenburg
Copy link

I think i understand what you mean.
You want a class with those functions to store all objects in it?

<?php
class ObjectStore {

    private static $objstore = array();

    public static function AddObject($name,$object) {
         if(self::__CheckObject($name)==false) {
              self::$objstore[$name] = $object;
         } else {
               // throw some error //
         }
    }

    public static function GetObject($name) {
        if(self::__CheckObject($name)) {
              return(self::$objstore[$name]);
        } else {
            // throw some error //
        }
    }

    public static function DeleteObject($name) {
        unset(self::$objstore[$name]);
        return(true);
    }

    private static function __CheckObject($name) {
        return(isset(self::$objstore[$name]));
    }

}

The DeleteObject is optional, but can be usefull.

What do you think of this?

@electrical
Copy link
Author

Did a quick fork and pushed changes.

Perhaps you want to rewrite it to your own way of writing.

https://github.com/electrical/Restler/commit/911df4d58acf9c7d4d22d13ee3efda5314f91791

Cheers.

ps. above comment was done from my work account. forgot i was still logged in :-)

@electrical
Copy link
Author

An other question :-)
When i assign more then 1 ACL entries like this:

<?php
        /**

Only the last one works but i would like to have the option that 1 of those 2 is enough.

Any idea on that?
        * @class SimpleAuth(requiredRole=install_delete)
        * @class SimpleAuth(requiredRole=install_delete_all)
        */

@Luracast
Copy link
Collaborator

Luracast commented Oct 3, 2011

try the following, it will set the value as an array

@class SimpleAuth(requiredRole=install_delete,install_delete_all)

@electrical
Copy link
Author

Works perfect :-) thanks.

@roynasser
Copy link

Added

private $permissionRequired = '1';

into class Authentication implements iAuthenticate, before __isAuthenticated

and I tried accessing $this->permissionRequired inside isAuthenticated. it always comes up with the default value '1'... even though I added

* @class Authentication(requiredPermission=123)

I tried a print_r($GLOBAL) inside __isAuthenticated for debugging... here is what I find...:

                                    [metadata] => Array
                                        (
                                            [protected] => 
                                            [description] => All methods in this class are protected
                                            [url] => GET /:ID
                                            [Authentication] => Array
                                                (
                                                    [requiredPermission] => 123
                                                )

                                        )

so I assume it does have access?? notice it got the classname Authentication correct... Am I missing something in restler? or doing something wrong?

Is there any way to get the functino name or something else? are there any "public" annotations possible? like something such as

/*

and then be able to pull that from the authentication and from other parts of the code (obviuosly "description" would be useless in the code, but maybe annotation[FunctionName] could be useful?
thanks!

@electrical
Copy link
Author

Hi RVN-BR,

Could you provide the following so we have a clearer image.

  1. Your API gateway ( where you initialize the API )
  2. Your authentication calss
  3. The class you try to secure.

From what i see is that a change in name.

You have at the top permissionRequired, and later youare talking about requiredPermission ( you changed it )

Perhaps that's whats wrong?

@roynasser
Copy link

sorry about that, I pasted correctly but when I typed in the echo statement i messed up when posting here...

Codes:

Authclass:

<? php
class Authentication implements iAuthenticate{
    public $requiredPermission = '1';
    function __isAuthenticated() {
        //return isset($_GET['key']) && $_GET['key']==SimpleAuth::KEY ? TRUE : FALSE;
echo $this->requiredPermission;
//print_r($this);
//print_r($GLOBALS);
}

index.php (gateway)

<? php
require_once 'APILib/restler.php';

#set autoloader
#do not use spl_autoload_register with out parameter
#it will disable the autoloading of formats
spl_autoload_register("spl_autoload");

$r = new Restler();
$r->setSupportedFormats('JsonFormat', 'XmlFormat');
$r->addAuthenticationClass('Authentication');

API class (part of the file)

<?
    /**
    * @url GET /:ID
    * @class Authentication(requiredPermission=123)
    */

    function Test($ID) {
            ////not doing anything in here yet
            return "123456";
        }

@electrical
Copy link
Author

The only thing i can think of is that by putting public $requiredPermission = '1'; in top of your authentication class you over write the value that's beeing passed.

What if you remove that, and try it again.

The $this->requiredPermission should then be set from the api to the auth class.

@roynasser
Copy link

I got that from your example, where you put: public $requiredRole = 'admin';

I tried just public $requiredPermission;

but still nothing... might it be something missing in my restler? I got it off here a few days ago... weird... :( I'll try more tomorrow...

@electrical
Copy link
Author

ah i see what you mean :-) forgot about that example.
I am missing the api class where you specify the needed rights for that function in your index file, or did you leave that our when you pasted it here?
If you left it out in your version it will never be set.

@roynasser
Copy link

What do you mean specify the rights in the index file?
I tried with this:

test.php

authentication.php

per;
//print_r($this);
     }

index.php

setSupportedFormats('JsonFormat', 'XmlFormat');
$r->addAPIClass('test','v1/test');
$r->addAuthenticationClass('authentication');

I think this is everything now.. :p

@electrical
Copy link
Author

Okay, now its making a bit more sense for me now i can see everything :-)
so now if you do a get you will get a "1" back right?
and if you change your definition @class Authentication(per=1) to something else, for example a string, will you get that back then?

@roynasser
Copy link

New test... complete new set of files.... I give up for now :p

index.php

<?
require_once 'APILib/restler.php';
spl_autoload_register("spl_autoload");
$r = new Restler();
$r->setSupportedFormats('JsonFormat');
$r->addAuthenticationClass('authen');   /// I tried flipping this line with the line below so the auth would be defined after adding classes... Does it make a difference?
$r->addAPIClass('teste');
$r->handle();

teste.php

<?php
class teste {
/**
 * @class Authen(Level=12)
 */
    protected function index($to='world') {
        return "Hello $to!";
    }
}

authen.php

<?php
class Authen implements iAuthenticate{
    public $Level;
    function __isAuthenticated() {
        throw new RestException (401,"A level of '".$this->Level . "' is required for this");
    }
}

What happens is that I can never read the "Level" defined in my teste.php file (* @class Authen(Level=12)) from the auth function... so I can never compare whatever level the user has with the level required by the function...

To sum up, GET API/ gives me:

{
  "error": {
    "code": 401,
    "message": "Unauthorized: A level of '' is required for this"
  }
}

I hope i'm making at least some sense :( sorry if I'm not....its been a long week...

@electrical
Copy link
Author

I have a feeling it never gets to the correct function and then never sets the level value. ( just a hunch; i could be wrong )
everything seems to be okay in my eyes, checked it with my files and don't see much difference in there what would explain it.

Can you remove the auth stuff first and confirm when you call the url you are reaching the right function and getting the correct output?

@roynasser
Copy link

Thanks a bunch for your help, once again...

What do you mean doesnt get to the correct function? If I remove the ˜protecte" from the function in the API, it loads the same function, albeit without going through the authentication (using the same URL)...so the URL does seem to be mapping correctly...

It is also the only function in the API (I just did some new install with blank slate...)

I'm guessing my restler version is to blame.... is it after a certain release that this was implemented? could this have been accidentally disabled or removed in more recent versions?

@roynasser
Copy link

Wait a second... was this commited to the main branch? :o

is the entire @Class notation functionality included only somewhere else than the usual restler download? i only downloaded from the main git... i assumed it had that? (although maybe not the addobject and remove object stuff that electrical contributed)

@electrical
Copy link
Author

if it goes to the right function without the authentication then the function mapping is correct.
then indeed i would guess there is a error in the newer restler version.

Could you try it with this version of the library?

https://github.com/Luracast/Restler/blob/9746eb821561013fb961f79eefbc135ab74a2b13/restler/restler.php

If that works we will know for sure it got broken somewhere.

@electrical
Copy link
Author

btw, the whole @class notation works in version 2.0.1 which i still got ( haven't updated it yet )
The object stuff to manage class objects will be implemented later i hope. but is not needed for the authentication to work.

@Luracast
Copy link
Collaborator

Current version of rester supports @class annotation and works fine. It was never removed.

@RVN-BR let me provide the working code first. Here is the corrected and tested version of the above code. you may also download it from my dropbox

Gateway (index.php)

<?php
require_once '../../restler/restler.php';
require_once 'say.php';
$r = new Restler();
$r->addAuthenticationClass('Auth'); //order does not matter
$r->addAPIClass('Say'); //take note of the capitalization
$r->handle();

Auth class (auth.php)

<?php
class Auth implements iAuthenticate
{
    public $level = 1;
    public function __isAuthenticated ()
    {
        throw new RestException(401, 
        "A level of '" . $this->level . "' is required");
    }
}

API class (say.php)

<?php
class Say
{
    /**
     * @class Auth(level=12)
     */
    protected function hello ($to = 'world')
    {
        return "Hello $to!";
    }
}

calling http://restler2.dev/test/ACL2/index.php/say/hello gives me

{
  "error": {
    "code": 401,
    "message": "Unauthorized: A level of '12' is required"
  }
}

@roynasser
Copy link

SOLVED.... phew....

Turns out addAuthenticationClass isnt "case sensitive" per se, but if you dont use the exact name, it will break the @class notation... (authentication works as it should but it doesnt pass the @Class variable to the class)

Capitalization of the addAPIClass call seems to not make any difference, even though you noted that line on your code... I will keep everything exactly like the code from now on, even though the initial API module must be called in all lower-cases, the @url notation does allow sub-paths to be case-sensitive...

oh well, thanks for all the help! :)

@Luracast
Copy link
Collaborator

Capitalization does make a difference in linux, unix and MacOS only on windows it is not case sensitive

So here is the best practice advice to make it work in all cases

  • name your php files in all lowercase (needed for spl_autoload to find it automatically)
  • name your classes using PascalCase and use proper class name where ever class name is required

@roynasser
Copy link

I am quite aware of capitalization issues in different operating systems, however the inconstansistancy of it working to call the function and to do all authentication routines, but not for passing the variable with the @Class notation is what got me....

camelCase would have been my preference for everything, I think it makes things easier to read... I understand that the php files must all be lowercase for autoload to work, but the URL could potentially follow the class name and not the filename? I'd suggest that if possible.

Right now I will revert to the following conventions...

lowercase filenames - myclass.php
camelCase Class names - class MyClass
camelCase for addAPIClass and Authentication - addAPIClass('MyClass')
camelCase for functions within classes - protected function MyCommand($id)
lowercase for URLs (this one makes little or no sense in the scope of things...) - http://myserver/API/myclass/
camelCase routing within functions using @url mapping - http://myserver/API/myclass/123/DoCommand/

so far so good... One other alternative is using underscores... are there any restrictions with underscores?

thanks!

@Luracast
Copy link
Collaborator

There are no restrictions using underscore

@Arul-
Copy link
Member

Arul- commented Oct 4, 2012

Take a look at this live example if you need a Restler v3 version of the above

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants