Giter VIP home page Giter VIP logo

zf-mvc-auth's People

Contributors

adamlundrigan avatar dannyvdsluijs avatar dejan9393 avatar dkcwd avatar ezimuel avatar intellix avatar michalbundyra avatar nuxwin avatar nyholm avatar ralphschindler avatar samsonasik avatar stefanotorresi avatar stevleibelt avatar svenrtbg avatar tjlytle avatar weierophinney avatar xjchengo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

zf-mvc-auth's Issues

Create authentication endpoint

In order to have authenticated access to APIs, we need an authentication endpoint.

The URL for this endpoint MUST be configurable.

The endpoint will use the configured authentication adapter.

The endpoint will need to return a token on valid authentication which the client will send in subsequent requests that require authorization. The application should be able to look up the identity based on this token.

Incorrect reponses when using the OAuth authentication adapter

Due to the changes from 101feec Oauth authentication adapter behavior is inconsistent and wrong.

Current Behavior

I. Clients are never challenged with proper 401 response

This is due to changes from 101feec in which a 401 response is assumed only if the error parameter from the OAuth2 response is not null. While this assertion is true for an invalid, a malformed or an expired token, it is not the case for "initial challenge" (When no credential were provided at all).

Indeed, when an authentication request is checked, and if something goes wrong, we have only few response possibilities:

No credential were provided at all by the client

In such case, a 401 response with the appropriate WWW-Authenticate header is created. This doesn't involve any error parameters in the WWW-Authenticate header.

Invalid, malformed or expired token

Same as above, but here, some error related parameters are added to the WWW-Authenticate header.

Insufficient scope

In such case, a 403 response is returned with a WWW-Authenticate header. Some error related parameters are also added to the WWW-Authenticate header.

II. Problem responses are not ApiProblemResponse

Apigility should be consistent when returning a response denoting a problem, whatever the authentication adapter in use. Currently, in the OAUth authentication adapter case, the problem responses are not marshalled to ApiProblemResponse, resulting to an empty body. Even if valid, we should be consistent here.

Expected behavior

  • On authentication failure, or when no credentials were provided at all, client must be challenged with proper 401 response which include appropriate WWW-Authenticate header if authentication is required
  • Response denoting problems must be marshalled to ApiProblemResponse

Final note and proposal

As per 101feec @weierophinney assumes that if no credential were provided at all, a GuestIdentity must be assumed (case of a public API). This assertion would be valid if all resources were public, but in Apigility, permissions are based on HTTP methods and not on API endpoints. Thus, assuming a GuestIdentity (therefore, a public API) when no credential are provided is wrong because clients are never challenged correctly in case of private resource. This is exactly the same assertion I've made by mistake in #92, which resulted to the #106 issue.

This demonstrates all the problem with current Apigility permissions system. Authorizations are always evaluated after authentication. The problem which we must solve is how to let the authentication layer know when a route require authentication or not. We could solve this problem by adding Guards at authentication level (see below).

Authentication level guards

Those guards would informs the MvcAuth event (e.g, with a route guard resolver listener) when authentication is required for the matched route. If so, client would be challenged or not. This would solve our problems and more, this would allows performance boost because if no required, authentication process would be aborted early (no authentication adapter involved).

Authorization level

We have either a Guest identity or Authenticated identity. Whatever... In case of Apigility, authentication guards already tell us if user can access the route or not. Here, a 403 response would result of auhorization failure only. This would be the case if the developer implement authorization layer (e.g. conditional ACL with assertions).

Related issues

Directly related: #99
Indirectly related: #97 and #106

Can authorization of API endpoints turned on by default?

Currently, all API endpoints are publicly available until you set the checkboxes on the authorization page. I enabled OAuth2 authentication and would like to modify that, new endpoints and http methods are secured by default.

Is that possible?

Role <> username

In ZF\MvcAuth\Identity\AuthenticatedIdentity
getRoleId return name but in my case role of user is different of user name.

What's best approach?

Create authorization listener

Create a listener that:

  • Retrieves the controller service name from the route matches
  • Retrieves the HTTP request method
  • Tests if:
    • the controller has ACLs listed (if none, do nothing), OR
    • the controller has indicated "require_all" (i.e., all methods require authentication), OR
    • the controller lists the current HTTP request method (i.e., requires authentication)

It will then determine if we have an authentication token, and, if not, return a "401 Unauthorized" response.

We may add later the ability to mark all services in an API and/or application as requiring auth.

two AclAuthorizationFactory classes is confusing

I want to point out it's quite confusing having two classes essentially named the same thing.

It'd be nice to get some documentation, clarification, or even a name change.

Specifically I'm speaking of src/Authorization/AclAuthorizationFactory.php and src/Factory/AclAuthorizationFactory.php

Unable to define my own ResolverInterface implementation

I've looked thoroughly at the 1.1.3 release version on how to accomplish this:

My Apigility API needs to have a custom authentication mechanism. Username and password are passed as basic authentication, but have to be checked in a custom way. I already have a \Zend\Authentication\Adapter\Http\ResolverInterface implementation, and I need to have this set into the authentication stack.

Here's my start point:https://github.com/zfcampus/zf-mvc-auth/blob/1.1.3/src/Factory/AuthenticationAdapterDelegatorFactory.php#L28

I can configure any amount of authentication adapters, but in the end I am limited to only the two known types 'ZF\MvcAuth\Authentication\HttpAdapter' and 'ZF\MvcAuth\Authentication\OAuth2Adapter'. I could live with the HttpAdapter, but I have no way to say "build it like in default, but at the end, set MY basic resolver".

Even a delegator factory for the listener won't help, because I cannot get access to the inner parts of what got created. In theory I could code-duplicate this AuthenticationAdapterDelegatorFactory and add my own case. I could even just add a DelegatorFactory on top and just do what I need to be done on the existing listener. But the problem is that:
a) if I get the preconfigured listener, I cannot access the attached adapters to try to replace the resolver inside (and even if that would be possible, all the intermediate objects pose the same problem)
b) if I want to avoid code duplication, I cannot build on the existing blocks, because AuthenticationHttpAdapterFactory also does not allow customization. It has a hardcoded call to HttpAdapterFactory::factory() that either injects an ApacheResolver for basic auth, or a FileResolver for digest auth.

I would think that adding a custom resolver for authentication is a common use case. Just think "user table in database" - why would I have to render that into a .htpasswd file to allow the ApacheResolver to work?

grant_type password with username/password combination not possible

On forehand let me apologise if I get this somewhat backwards. However as I understand OAuth2 it should be possible to request a token via a username/password (which is thus authentication not authorisation).

POST https://api.oauth2server.com/oauth
    grant_type=password&username=USERNAME&password=PASSWORD

I'm not sure if client_id must be provided.

However when I tried to get this working with zf-mvc-auth I found it is not supported. The grant_type password is recognised but only to validate the a client_id and secret...
Is that intended, and oversight, not implemented yet or is it my incorrect understanding things.

My use case is an mobile app for which users enter there password once to authenticate against an online account - we do not want to have them enter api secrets. I also don't want to store the password locally.
some ref : http://stackoverflow.com/questions/18603761/choosing-the-right-oauth2-grant-type-for-php-web-app

There is no support for new zf-oauth2's possibility to configure storages in config

zf-oauth2 v1.1.1 has possibility to configure several storages in configuration file like:

'storage' => [ 'ZF\OAuth2\Adapter\PdoAdapter', 'user_credentials' => 'SomeCustomUsersStorage' ],

But in zf-mvc-auth we have the following check(https://github.com/zfcampus/zf-mvc-auth/blob/master/src/Factory/DefaultAuthenticationListenerFactory.php#L96):

if (!isset($config['zf-oauth2']['storage'])
|| !is_string($config['zf-oauth2']['storage'])
|| !$services->has($config['zf-oauth2']['storage'])) { return false; }
Corrected variant of this check is able to support new zf-oauth2 and works like a charm
P.S.: I'm only not sure if it is latest zf-oauth2's feature, or it was available before

The authorization page is not correct if deny_by_default is TRUE

The authorization page says "A check means authentication is required for the given combination of service and HTTP method.".

But with the following configuration a check means just the opposite.

array(
    'zf-mvc-auth' => array(
        'deny_by_default' => true
    ),
)

Inject authenticated identity into MvcEvent

Since the authenticated identity is determined as part of the request lifecycle, pulling it from the AuthenticationService either within objects or factories can be problematic; the identity may be pulled before authentication has been triggered.

Ideally, the identity could be pushed into the MvcEvent, so that a controller or resource could query if it's available first, and then pull.

always_issue_new_refresh_token is passed to MongoClient, causing MongoConnectionException

I've recently updated apigility and I'm now fighting the changes needed.

One of the issues I can't seem to figure out is the following exception:

Fatal error: Uncaught exception 'MongoConnectionException' with message '- Found unknown connection string option 'always_issue_new_refresh_token' with value '1'

It seems like always_issue_new_refresh_token option (and probably others as well) are not being removed from the $options array before passing it to the MongoClient.

The pitfall is in the factory of the server adapter (when creating the mongo client):
https://github.com/zfcampus/zf-mvc-auth/blob/master/src/Factory/OAuth2ServerFactory.php#L197

Looking at the equivalent piece of code in https://github.com/zfcampus/zf-oauth2/blob/master/src/Factory/MongoAdapterFactory.php#L57 you can see the options is taken from a sub array "mongo".

I also found the following in the google group:
As you can see here: https://groups.google.com/a/zend.com/forum/#!topic/apigility-users/BtCPsO6jS6c

Wrong HTTP status code for authentication challenge

It seems the DefaultAuthorizationPostListener is changing the status to 403 even when an authentication challenge response with a 401 status code is set.

The following piece of code probably needs to be added:

    if ($response instanceof HttpResponse && 
            $response->getStatusCode() == 401) {
        return;
    }

confusion on README.md example

I'm trying to figure out the deny_by_default=true system. And notice that the example in the readme.md here claims the following :

`authorization` => array(
    'deny_by_default' => true,
    'ZF\\OAuth2\\Controller\\Auth' => array(
        'actions' => array(
            'token' => array(
                'GET'    => false,
                'POST'   => true,   // <-----
                'PATCH'  => false,
                'PUT'    => false,
                'DELETE' => false,
            ),
        ),
    ),
),

those booleans need to be switched. According to my findings and the rest of the README.md a true flag tells MVC-Auth that an identity is required for that action. We, when denying by default, want to enable the login with a false.. not true.... right?

Getting a little confused.

Clients are not challenged when they are expected to be

@weierophinney

My #92 fix was wrong after all... It prevent challenge of clients when needed...

The problem coming from the #92 fix

...
'zf-mvc-auth' => [
    'authentication' => 

        'adapters' => [
            'http' => [
                // HTTP auth adapter configuration
            ],
            'whatever' => [
                // Whatever auth adapter configuration
            ],
        ],

        'map' => [
            'API/VERSION1' => 'basic',
            'API/VERSION2 => 'whatevertype'
        ]
    ]
]
...

Now, let's imagine the following scenario: A client requests the following URI https://host.tld/API/VERSION1 (here, the matching authentication type is basic). No Authorization header is sent by the client.

Then, the following will occurs in the default authentication listener:

  • getTypeFromRequest() will not be called (we have already an authentication type which is basic)
  • pre-flight auth tasks will not be called on auth adapter. In that case, this involve that the client will not be challenged (no 401 status code, nor WWW-Authenticate header)
  • authenticate() method on the MVC http authentication adapter will be called (That adapter matches the 'basic' authentication type)

At this point, if the mvc http adapter don't find the Authorization header, it will simply return a GuestIdentity. So, later on, the authorization listener will simply set a 403 status code if GuestIdentity is not allowed to access the resource.

The problem that the #92 fix tried to solve

Currently, the authentication layer don't known if a resource is private or public. That layer only know which authentitcation type is available for a specific API.

Thus, current design (assuming that the fix referenced by #92 is reverted) involve challenging clients, whatever the resource is private or not. This means here that an API is just either public or private. There is no concept of public and private resources at this level.

This is bad because if someone want provide both public and private resources through the same API, it cannot because clients (even for public resources) will be challenged. That was the point of my #92 fix that is wrong because it delegate challenge phase to the authorization layer, which is only able to send 403 status code. This is normal because the authorization layer must not be concerned about authentication.

A way to resolve that problem would be to allow the developer to mark specific resource as public (no authentication required). Now here, we have to think how. We are currently mapping API/VERSION to authentication type only. We should maybe add another map allowing to mark specific resource as public. This would involve matching of the controller, action and method.

Restricted Zend Framework versions

This commit bd9f42f restricts the available Zend framework versions to below 2.5 for no apparent reason (other than testing on Travis, I think).

Unfortunately this conflicts with the already installed components in my API.

Testing shouldn't restrict the allowed versions, have a look at how Symfony deals with it: They test both with newest dependencies as well as oldest: https://github.com/symfony/symfony/blob/2.8/.travis.yml

I'd suggest to run the tests with composer update prior to execution instead of composer install, as well as composer update --prefer-lowest for some PHP versions.

Early Exception Issue

Not sure if this is even solvable since the component and its associated database connections (from OAuth) are required early, but here's the issue:

As any good developer should have, we have extensive error reporting and logging in place, emails, sms messages, etc and we have wrapped this logic into a module. But in this case, we had no notice of a critical error.

Our database server went away. The MvcAuth component attempts to retrieve a DB instance from the OAuth component on Bootstrap. Since the database server went offline, it rightly threw an exception. However, since this all occurred on bootstrap, the code in our ErrorReporter module never executed and the user was greeted with a blank white page. Since this occurred on bootstrap we couldn't route the user to a pretty "Whoops" page nor were we alerted.

The only solution at this time was to wrap the contents of public/index.php in a try catch statement, code out some error reporting and issue a die() statement to the user. Furthermore, because this all happens on bootstrap, the error occurs on pages where the DB connector and the Auth aren't even required.

In my opinion, this instantiation and configuration should happen elsewhere in the event chain so that the developer can programmatically and intelligently handle such errors.

Howto attach custom event listeners

Hello guys, if I want to integrate custom event listeners in the chain, how can I achieve this. I guess changing the source integrated by php composer is not a good way as this changes get overriden.

Is there a way to inject events from other modules. Or is there maybe an approach that I maybe don't know yet.

The reason is, that I want to use session based authentification as well as oauth authentication in my project side by side (the apigility part is mainly protected by oauth and my webapp mainly works with sessions).

Would be nice if anyone can give me a hint πŸ‘

BR Daniel

Doctrine Authentication support

Hi everyone,

I love apigility approach and I would like to use it in my next project, but for this I would need database-backed authentication. I use doctrine authentication mechanism, and what I have done so far is to override zf-mvc-auth's authentication factory to use one of my own in a different module which in turn class doctrine's factory. With some reserves this works, but the problems arise when it is time to authorize, because zf-mvc-auth needs a valid Identity and doctrine does not know about this kind of objects.

Maybe I am following the wrong approach. Could you please point me in the right direction so I can integrate this functionality in zf-mvc-auth?

Regards,
Γ“scar

The deny_by_default TRUE setting should not block apigility admin

array(
    'zf-mvc-auth' => array(
        'deny_by_default' => true
    ),
)

If deny_by_default is set to TRUE, the application home route, Apigility admin, documentation route and the swagger documentation routes are also blocked. Is this really desirable?

If yes, what is an easy way to figure out what all the apigility admin controllers are?

Authorization returns null

Hi there,

for some reason in "DefaultAuthenticationListener"
$request->getHeader('Authorization') returns null.

Update zendframework/zend-servicemanager 2.7.3 breaking Apigility application

Notice: Undefined index: ZF\MvcAuth\Authorization\AuthorizationInterface in /var/www/com.igui.erp.api/vendor/zendframework/zend-servicemanager/src/ServiceManager.php on line 480

Fatal error: Uncaught exception 'Zend\ServiceManager\Exception\ServiceNotFoundException' with message 'An alias "" was requested but no service could be found.' in /var/www/com.igui.erp.api/vendor/zendframework/zend-servicemanager/src/ServiceManager.php on line 551

Zend\ServiceManager\Exception\ServiceNotFoundException: An alias "" was requested but no service could be found. in /var/www/com.igui.erp.api/vendor/zendframework/zend-servicemanager/src/ServiceManager.php on line 551

DefaultAuthenticationListenerFactory creates own instance of OAuth2Server

I'm attempting to integrate Satellizer with Apigility. Satellizer uses JWT Bearer, which requires some extra setup on the Apigility side (in bshaffer/oauth2-server-php, technically). zf-mvc-auth's DefaultAuthenticationListenerFactory pulls all the pieces from zf-oauth2's configuration and builds a new OAuth2Server instance rather than pulling the pre-configured one that zf-oauth2 slaved long and hard to construct. Would it be acceptable to modify the factory to pull the preconfigured instance? (If yes, I can provide a PR)

Source: https://github.com/zfcampus/zf-mvc-auth/blob/master/src/Factory/DefaultAuthenticationListenerFactory.php#L75

Create token-based authentication adapter

Will need @ezimuel to fill in details on this. My understanding is we'd have a mechanism to generate unique, secret API tokens; the developer would then send this token in authentication/authorization requests, and the authentication adapter would do validation based on finding that token, and return the identity based on that token.

Client isn't challenged when authentication is set in map but no Authorization header is sent

I'm facing the situation, that the API client isn't challenged when authorization is required for a service and no Authorization header is given:

my config:

return array(
    'zf-mvc-auth' => array(
        'authentication' => array(
            'map' => array(
                'MyService\\V1' => 'http_digest',
            ),
            'adapters' => array(
                'http_digest' => array(
                    'adapter' => 'ZF\\MvcAuth\\Authentication\\HttpAdapter',
                    'options' => array(
                        'accept_schemes' => array(
                            0 => 'digest',
                        ),
                        'realm' => 'MDM',
                        'digest_domains' => '/my-service',
                        'nonce_timeout' => '3600',
                        'htdigest' => 'data/users.htdigest',
                    ),
                ),
            ),
        ),
        'authorization' => array(
            'deny_by_default' => false,
        ),
    ),
);

Sending the following request, should challange the client, but the server does not include a WWW-Authenticate header in its response:

> GET /my-service/my-action HTTP/1.1
> Host: localhost
> User-Agent: curl/7.43.0
> Accept: application/json
> 
< HTTP/1.1 403 Forbidden
< Date: Thu, 24 Sep 2015 15:22:55 GMT
< Server: Apache/2.4.16 (Unix) PHP/5.5.23
< X-Powered-By: PHP/5.5.23
< Vary: Accept-Encoding,User-Agent
< Access-Control-Allow-Headers: x-requested-with, Content-Type, origin, authorization, accept, client-security-token
< Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT
< Access-Control-Allow-Origin: *
< Access-Control-Max-Age: 1000
< Content-Length: 119
< Content-Type: application/problem+json

I've identified the following section in ZF\MvcAuth\Authentication\DefaultAuthenticationListener's __invoke function responsible for not challenging the client:

        $type = $this->getTypeFromMap($mvcEvent->getRouteMatch());
        if (false === $type && count($this->adapters) > 1) {
            // Ambiguous situation; no matching type in map, but multiple
            // authentication adapters; return a guest identity.
            $identity = new Identity\GuestIdentity();
            $mvcEvent->setParam('ZF\MvcAuth\Identity', $identity);
            return $identity;
        }

        $type = $type ?: $this->getTypeFromRequest($request);
        if (false === $type) {
            // No authentication type known; trigger any pre-flight actions,
            // and return a guest identity.
            $this->triggerAdapterPreAuth($request, $response);
            $identity = new Identity\GuestIdentity();
            $mvcEvent->setParam('ZF\MvcAuth\Identity', $identity);
            return $identity;
        }

        // Authenticate against first matching adapter
        $identity = $this->authenticate($type, $request, $response, $mvcAuthEvent);

        // If the adapter returns a response instance, return it directly.
        if ($identity instanceof HttpResponse) {
            return $identity;
        }

        // If no identity returned, create a guest identity
        if (! $identity instanceof Identity\IdentityInterface) {
            $identity = new Identity\GuestIdentity();
        }

Changing it like this, the client is at least challenged when no credentials are submitted:

        $type = $this->getTypeFromMap($mvcEvent->getRouteMatch());
        if (false === $type && count($this->adapters) > 1) {
            // Ambiguous situation; no matching type in map, but multiple
            // authentication adapters; return a guest identity.
            $identity = new Identity\GuestIdentity();
            $mvcEvent->setParam('ZF\MvcAuth\Identity', $identity);
            return $identity;
        }

        $typeFromRequest = $this->getTypeFromRequest($request);
        $type = $typeFromRequest ? "{$type}-{$typeFromRequest}" : false;

        if (false === $type) {
            // No authentication type known; trigger any pre-flight actions,
            // and return a guest identity.
            $this->triggerAdapterPreAuth($request, $response);
            $identity = new Identity\GuestIdentity();
            $mvcEvent->setParam('ZF\MvcAuth\Identity', $identity);
            return $identity;
        }

        // Authenticate against first matching adapter
        $identity = $this->authenticate($type, $request, $response, $mvcAuthEvent);

        // If the adapter returns a response instance, return it directly.
        if ($identity instanceof HttpResponse) {
            return $identity;
        }

        // If no identity returned, create a guest identity
        if (! $identity instanceof Identity\IdentityInterface) {
            $identity = new Identity\GuestIdentity();
        }

Migration from 1.0 to 1.1 but no ['zf-mvc-auth']['authentication']['map'] config

I have updated my composer to migrate from 1.0 to 1.1.
But the ['zf-mvc-auth']['authentication']['map'] wasn't created automatically as defined in the readme.

Note for users migrating from 1.0: In the 1.0 series, authentication was
per-application, not per API. The migration to 1.1 should be seamless; if you
do not edit your authentication settings, or provide authentication information
to any APIs, your API will continue to act as it did. The first time you perform
one of these actions, the Admin API will create a map, mapping each version of
each service to the configured authentication scheme, and thus ensuring that
your API continues to work as previously configured, while giving you the
flexibility to define authentication per-API and per-version in the future.

So sorry, no more details about this bug.

401 and 403 responses

In #apigility a user has recently posted this article:
http://robertlathanh.com/2012/06/http-status-codes-401-unauthorized-and-403-forbidden-for-authentication-and-authorization-and-oauth/

Now, as far as I can tell the approach of the module should reflect exactly the same interpretation, but I think there is problem in ZF\MvcAuth\Authentication\DefaultAuthenticationPostListener, as it returns too early and it is superseded by ZF\MvcAuth\Authorization\DefaultAuthorizationPostListener, so unauthenticated requests ends up in an empty 403 response rather than a 401.

If I got it right, the authentication listener basically just listens for authentication attempts, but it doesn't determine whether a specific request should be authenticated in the first place. Am I right?

One possible solution is to add further logic in the authorization listener, to determine if the request has to be authenticated and to to return a 401 in case no authentication is present, before even proceeding to check the ACL.

Toughts?

Add handling for missing Authorization header (send a 401 on authorization failure if no identity is asserted)

Apigility-specific issue:
When submitting a request where authorization is required (e.g., admin menu -> authorization -> GET) is checked and no Authorization header is provided a 403 is returned rather than a 401.

Because no identity is asserted by the original request the appropriate response would (from an "API as a black box" perspective) be a 401.

Due to the current authorization flow in Apigility a 403 is returned because a Guest Identity (default identity should authentication not happen) is not allowed to perform the action that an Authenticated Identity can perform.

This request is to add in some kind of listener to intercept the request and return a 401 when the guest identity is not allowed to perform an action and authorization is checked in the admin UI.

[proposal] Modularize OAuth2 Adapters

zf-mvc-auth and zf-oauth2 each have specific code for specific adapters for OAuth2 and they do not allow code which is not for those adapters to hook in properly to support multiple OAuth2 connections of the same adapter. A symptom is config::zf-oauth2 configuring in a global space.

I propose zf-oauth2 be reduced to a controller and user id provider and all adapter code be moved into individual adapters named zf-oauth2-[adapter]. These adapter repos will support the combined logic of zf-mvc-auth and zf-oauth2 adapters and factories. zf-oauth2 will no longer have local configuration.

Configuration of zf-oauth2-[adapter]s will be handled through zf-mvc-auth like this:

    'zf-mvc-auth' => array(
        'authentication' => array(
            'adapters' => array(
                'named_adapter' => array(
                    'adapter' => 'ZF\\MvcAuth\\Authentication\\OAuth2Adapter',
                    'storage' => array(
                        'adapter' => 'ZF\OAuth2\Pdo\Adapter',
                        'dsn' => 'mysql:dbname=database;host=localhost',
                        'route' => '/oauth',
                        'username' => 'db',
                        'password' => '',
                    ),
                ),
            ),
        ),
    ),

This would be handled in src/Factory/OAuth2ServerFactory.php at createStorageFromAdapter

private static function createStorageFromAdapter($adapter, array $config, ServiceLocatorInterface $services)
{
    $callback = $services->get($adapter);
    return $callback($config, $services);
}

This pattern is central to allowing multiple adapters. Without this pattern, such as using services, separate factories must be configured for each connection. Using this pattern the factory can use the zf-mvc-auth configuration to configure each adapter.

Use my own PDO adapter

Hi!

I use Oauth with Postgresql and I need to override ZF\OAuth2\Adapter\PdoAdapter with my own Adapter

My configuration is:

<?php
return array(
    'zf-oauth2' => array(
        'db' => array(
            'dsn_type' => 'PDO',
            'dsn' => 'pgsql:host=192.168.42.11;port=5432;dbname=****',
            'username' => '****',
            'password' => '****',
        ),
    ),

    'zf-mvc-auth' => array(
        'authentication' => array(
            'adapters' => array(
                'oauth2' => array(

                    'adapter' => 'ZF\\MvcAuth\\Authentication\\OAuth2Adapter',
                    'pdoadapter' => '',

                    'storage' => array(
                        'adapter' => 'pdo',
                        'storage' => 'Application\Service\OAuth2Adapter',
                        'dsn' => 'pgsql:host=192.168.42.11;port=5432;dbname=****',
                        'route' => '/oauth',
                        'username' => '****',
                        'password' => '****',

                        'storage_settings' => array(
                            'user_table' => 'organization.people'
                        ),
                    ),
                ),
            ),
        ),
    ),
);

ZF\MvcAuth\Factory\OAuth2ServerFactory ::createPdoAdapter return a new ZF\OAuth2\Adapter\PdoAdapter;

I can't use my override Application\Service\OAuth2Adapter

Create authentication token service

Once authentication has occurred, a token should be returned to the client. That token will be used for remaining requests.

A service will then need to validate the token whenever authorization is required.

This service will check for a header or potentially a query string parameter, and:

  • if missing, indicate missing token
  • if present, validate the token
    • if invalid, indicate invalid token
    • if valid, retrieve the identity based on the token, and set as a service

All false priviliged resources not getting into ACL, causes true returned

I've added some custom resources, and they don't seem to end up in my ACL... which would be okay if they were doing the desired behavior.. however the default in Apigility so far.. is that once a User is authenticated.. isAllowed will return 1 if the resource cannot be found.

This is a major problem, as now I need to lockdown resources relative to who is requesting them.. and the default catchall permission for an authenticated User is one.

'entity' => array(
'GET' => true,
'POST' => false,
'PATCH' => true,
'PUT' => true,
'DELETE' => true,
),
'entity::self' => array(
'GET' => true,
'POST' => false,
'PATCH' => true,
'PUT' => true,
'DELETE' => true,
),
'entity::otheruser'=> array(
'GET' => false,
'POST' => false,
'PATCH' => false,
'PUT' => false,
'DELETE' => false,
),
'collection' => array(
'GET' => true,
'POST' => true,
'PATCH' => false,
'PUT' => false,
'DELETE' => false,
),
'collection::self' => array(
'GET' => true,
'POST' => true,
'PATCH' => false,
'PUT' => false,
'DELETE' => false,
),
'collection::otheruser' => array(
'GET' => false,
'POST' => false,
'PATCH' => false,
'PUT' => false,
'DELETE' => false,
),

Resulting Rules inside ACLAuthorization.php are below ... I'm aware this may be totally unorthodox .. but I'm grasping at how to properly customize Mvc-Auth ..and this seemed like the way. I'm of course open to other ideas in lieu of a fix for this particular issue.

notice that type : TYPE_ALLOW .. that's what's generating the default catch all 1. Where do I set that to TYPE_DENY properly?

{
    "allResources": {
        "allRoles": {
            "allPrivileges": {
                "type": "TYPE_ALLOW",
                "assert": null
            },
            "byPrivilegeId": [

            ]
        },
        "byRoleId": {
            "guest": {
                "byPrivilegeId": [

                ],
                "allPrivileges": {
                    "type": "TYPE_DENY",
                    "assert": null
                }
            }
        }
    },
    "byResourceId": {
        "Module\\V1\\Rest\\Service\\Controller::collection": {
            "byRoleId": {
                "guest": {
                    "byPrivilegeId": {
                        "GET": {
                            "type": "TYPE_ALLOW",
                            "assert": null
                        },
                        "POST": {
                            "type": "TYPE_ALLOW",
                            "assert": null
                        }
                    }
                }
            }
        },
        "Module\\V1\\Rest\\Service\\Controller::entity": {
            "byRoleId": {
                "guest": {
                    "byPrivilegeId": {
                        "GET": {
                            "type": "TYPE_ALLOW",
                            "assert": null
                        },
                        "PATCH": {
                            "type": "TYPE_ALLOW",
                            "assert": null
                        },
                        "PUT": {
                            "type": "TYPE_ALLOW",
                            "assert": null
                        },
                        "DELETE": {
                            "type": "TYPE_ALLOW",
                            "assert": null
                        }
                    }
                }
            }
        },
        "Module\\V1\\Rest\\Service\\Controller::entity::self": {
            "byRoleId": {
                "guest": {
                    "byPrivilegeId": {
                        "GET": {
                            "type": "TYPE_ALLOW",
                            "assert": null
                        },
                        "PATCH": {
                            "type": "TYPE_ALLOW",
                            "assert": null
                        },
                        "PUT": {
                            "type": "TYPE_ALLOW",
                            "assert": null
                        },
                        "DELETE": {
                            "type": "TYPE_ALLOW",
                            "assert": null
                        }
                    }
                }
            }
        },
...
//more controllers
...
        "ZF\\OAuth2\\Controller\\Auth::token": {
            "byRoleId": {
                "guest": {
                    "byPrivilegeId": {
                        "POST": {
                            "type": "TYPE_ALLOW",
                            "assert": null
                        }
                    }
                }
            }
        }
    }
}

The IdentityInterface object within DefaultAuthenticationListener should be customizable

Currently only two different identity objects are supported: GuestIdentity and AuthenticatedIdentity, with the former having basically no internal functionality, and the latter having an asymmetrical functionality (setName is returned via getRoleId).

My use case is that during authentication I know some data about the client that are related to it's identity and that are relevant to the application later on. Using the identity object seems like the preferred way to transport this data, but the only thing currently supported is to stuff an array full of data into the constructor of AuthenticatedIdentity. Doing so affects getAuthenticationIdentity(), because this call does return that full array. Furthermore arrays are unstructured blobs of data, and I'd really like to have an object that I can ask defined things.

---- end of feature request ----

I could theoretically return an object from my customized authentication adapter, but then I have this code: https://github.com/zfcampus/zf-mvc-auth/blob/master/src/Authentication/DefaultAuthenticationListener.php#L142-L151

And it doesn't make sense to me. It is explicitly checked whether the result is an PHP array. If it is, and has a username key, this will be used as the name, and if not, that array is cast to a string (it will always result in "Array", won't it?). After that, that "name" is set as the name, working as the roleId.

Now if I pass an object into that AuthenticatedIdentity, which will internally be stored as identity, that code duplicates this information as "roleId", and I wonder what would happen if code that expects getRoleId() to return a string (as promised by Zend\Permissions\Acl\Role\RoleInterface) suddenly returns an object or anything else that is not a string, but also wasn't an array.

I hope anything above makes sense. :)

oauth2: client_id and scope should be hold in AuthenticatedIdentity

I think when DefaultAuthenticationListener is authenticating types 'oauth2' or 'bearer', AuthenticatedIdentity should hold client_id and scope that oauth server return.

Doing so, listeners could retrieve that information from MvcAuthEvent in order to be able to checks if scope is allowed for current request or track clients...

Thanks!

WWW-Authenticate:x-Basic instead Basic

In Chrome when we return WWW-Authenticate:Basic and 401 status in xhr request it show a popup, to not show the popup must return x-base.

Sorry about my english

Create authentication factory

Create a factory that can create an AuthenticationService instance composing an adapter based on the configuration provided.

Empty response when bad oauth2 credentials supplied (e.g. expired access token)

In the authenticate method of the oauth2 adapter (https://github.com/zfcampus/zf-mvc-auth/blob/master/src/Authentication/OAuth2Adapter.php#L135) - if the response is an IdentityInterface, the output is a json that looks like this (due to the SendApiProblemResponseListener, which parses the ApiProblem response which is generated later on):

{
  "type": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html",
  "title": "Forbidden",
  "status": 403,
  "detail": "Forbidden"
}

However - in the case of an error from the response of the oauth2 server (https://github.com/zfcampus/zf-mvc-auth/blob/master/src/Authentication/OAuth2Adapter.php#L154-L156), what returns is a Zend\Http\Response, causing the response to be an empty response with only the 401 status code but without a body (https://github.com/zfcampus/zf-mvc-auth/blob/master/src/Authentication/DefaultAuthenticationListener.php#L189-L191).

This issue started happening to me after I've updated apigility from 1.0.* to the latest.

Per-API authentication

A feature we want to implement for 1.1.0 is the ability to specify authentication per-API. The rationale is that a number of developers would like to serve multiple APIs from the same application, but use different authentication based on the API (for instance, they may want LDAP-based authentication for an "internal" API, and OAuth2 for an external API, but both would run from the same Apigility application).

We also need to remain compatible with pre-1.1.0!

Currently, in the 1.0 series, authentication is an application-level concern. As such, we trigger it before routing happens. Changing to per-API will require:

  • Moving authentication to occur post-routing (i.e., negative priority on the "route" event, but still prior to authorization).
  • Checking first for a API-specific authentication. If found, that gets used, and we go no further.
  • Checking next for application-wide authentication. If found, that gets used.

The hard part will be the "per-API" check, as it will have to be conventions-based; i.e., it will need to look at the namespace of the matched controller and compare it against known namespaces.

All authentication types will still be created at the application level, but mapped at the API level. This means we'll still have the top-level authentication key inside the zf-mvc-auth configuration, but will then also have a map of API module names => named authentication type.

Multiple authentication adapters BC break

Before 1.1 it was possible to configure both http and oauth2 authentication adapters globally, as DefaultAuthenticationListener inferred the type to use from the request.

With 1.1, not only it's not possible anymore (because it's deemed as an "ambiguous case"), but it's neither possible to assign one custom adapter for every resource, nor multiple adapters for one resource.

Is the only solution to write my custom adapter and manually assign it to every resource? Couldn't we move this block down and use the request before the map?

Incorrect instance passed to method

Factory/DefaultAuthenticationListenerFactory.php

 $oauth2Server = $this->createOAuth2Server($services);

        if ($oauth2Server) {
            $listener->setOauth2Server($oauth2Server);
        }

In version 1.0.3 the createOAuthServer returns an instance of ZF\OAuth2\Factory\OAuth2ServerInstanceFactory whereas the setOauth2Server method requires an instance of OAuth2\Server.

Authentication\DefaultAuthenticationListener.php

public function setOauth2Server(OAuth2Server $oauth2Server) 

The Listener has been rewritten in later versions of zf-mvc-auth, however zf-apigility/composer.json dev-master still references version 1.0.0 and overriding zf-mvc-auth version in composer results in conflicts.

It would we good if we could just add a verification on the $oauth2Server type and if a OAuth2ServerInstanceFactory object then invoke it to ensure the correct OAuth2\Server object is passed onto the setOauth2Server method:

https://github.com/matwright/zf-mvc-auth/blob/1.0.4-branch/src/Factory/DefaultAuthenticationListenerFactory.php#L38

Error for reference/

Argument 1 passed to ZF\MvcAuth\Authentication\DefaultAuthenticationListener::setOauth2Server() must be an instance of OAuth2\Server, instance of ZF\OAuth2\Factory\OAuth2ServerInstanceFactory given, called in /vendor/zfcampus/zf-mvc-auth/src/Factory/DefaultAuthenticationListenerFactory.php on line 41 and defined

Scope verification is not correct

Found this issue on the latest 1.7.1 version, and zf-mvc-auth's latest of 1.1.3. Problem is in false approving access for authorized users but with smaller scope access. I have an ACL roles called e.g. 'test' and 'live'. I have some resource called A_Resource, and allowed 'live' users access to it. This issue allows 'test' users to be accessed to A_Resource. I debugged a bit and this strange behaviour starts at:

public function authenticate(Request $request, Response $response, MvcAuthEvent $mvcAuthEvent)
    {
        $content       = $request->getContent();
        $oauth2request = new OAuth2Request(
            $_GET,
            $_POST,
            array(),
            $_COOKIE,
            $_FILES,
            $_SERVER,
            $content,
            $request->getHeaders()->toArray()
        );
        if (! $this->oauth2Server->verifyResourceRequest($oauth2request)) {//here
            return false;
        }
        $token    = $this->oauth2Server->getAccessTokenData($oauth2request);
        $identity = new Identity\AuthenticatedIdentity($token);
        $identity->setName($token['user_id']);
        return $identity;
    }

the method's signature is

public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response = null, $scope = null)

As a result, here:

public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null)
    {
        $token = $this->getAccessTokenData($request, $response);
        // Check if we have token data
        if (is_null($token)) {
            return false;
        }
        /**
         * Check scope, if provided
         * If token doesn't have a scope, it's null/empty, or it's insufficient, then throw 403
         * @see http://tools.ietf.org/html/rfc6750#section-3.1
         */
        //problem in $scope
        if ($scope && (!isset($token["scope"]) || !$token["scope"] || !$this->scopeUtil->checkScope($scope, $token["scope"]))) {
            $response->setError(403, 'insufficient_scope', 'The request requires higher privileges than provided by the access token');
            $response->addHttpHeaders(array(
                'WWW-Authenticate' => sprintf('%s realm="%s", scope="%s", error="%s", error_description="%s"',
                    $this->tokenType->getTokenType(),
                    $this->config['www_realm'],
                    $scope,
                    $response->getParameter('error'),
                    $response->getParameter('error_description')
                )
            ));
            return false;
        }
        // allow retrieval of the token
        $this->token = $token;
        return (bool) $token;
    }

we got problem with $scope variable, because it's isn't false, it is null, and this breaks the statement and not sending 403 error. The rest of checks in that if statement are fine. But seems that scope should be provided, and appropriate logic for default values also need to be implemented... Probably the problem is in authenticate method, passing role, all works great, but didn't find way to get requested scope there...

Looking forward for your response

Custom Authentication Adapter

Hi there, we have been following the documentation online about adding a custom adapter. We have added this config to the local.php

'zf-mvc-auth' => array(
        'authentication' => array(
            'adapters' => array(
                'certificate' => array(
                    'adapter' => 'example\\V1\\Authentication\\CertificateAdapter',
                    'options' => array(),
                ),
            ),
        ),
    ),

we also added this to the global.php

'zf-mvc-auth' => array(
        'authentication' => array(
            'map' => array(
                'example\\V1' => 'certificate',
            ),
        ),
    ),

The user interface does not reflect these config changes, additionally it doesn't look like the adapter is called, when the api is called. Are we being stupid or have we missed something?

there si no way to add extra configurations to oauth server (custom grant_types)

I need to add a custom grant type to the instances of oauth2server used in zf-mvc-auth.
There is no way to do this because there is no shared registry for all the oauth2 servers.
The class ZF\MvcAuth\Factory\DefaultAuthenticationListenerFactory creates a new oauth server every time that is called.
Also, ZF\OAuth2\Factory\OAuth2ServerFactory creates a new instance every time that is called.

This could be fixed if zf-mvc-auth provides a way to add custom grants by configuration.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.