zfcampus / zf-mvc-auth Goto Github PK
View Code? Open in Web Editor NEWLicense: BSD 3-Clause "New" or "Revised" License
License: BSD 3-Clause "New" or "Revised" License
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.
Due to the changes from 101feec Oauth authentication adapter behavior is inconsistent and wrong.
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:
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.
Same as above, but here, some error related parameters are added to the WWW-Authenticate header.
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.
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.
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).
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).
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).
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?
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 a listener that:
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.
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
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?
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
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 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
),
)
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.
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
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;
}
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.
Since the merge of the PR #61 the zf-apigility-admin
trown an Exception when consuming the getAuthenticationTypes()
of ZF\MvcAuth\Authentication\DefaultAuthenticationListener
.
Basically if no oauth2 configuration is provided the zf-mvc-auth
cannot be used, previously it was possible thanks to this check.
My #92 fix was wrong after all... It prevent challenge of clients when needed...
...
'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:
basic
)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.
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.
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.
What better way to customize the authorization depending on the feature called on route?
example:
on the route: / car / 1
I want to get the car 1 and see if I can change it
I have noticed when using the latest version of bshaffer/oauth2-server-php, the GrantType/RefreshToken construct method allows a parameter of 'unset_refresh_token_after_use' to be passed.
This is not currently being passed from the injectGrantTypes method of OAuth2ServerFactory, even after being set correctly in the application configs.
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.
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
Don't know if this is more related to this module so ;)
See zfcampus/zf-apigility-skeleton#104:
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
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?
Hi there,
for some reason in "DefaultAuthenticationListener"
$request->getHeader('Authorization') returns null.
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
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)
Hello,
I always getting 401, if try Bearer Header based, but if try POST using access_token it works fine. am I missing anything?
{
"type": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html",
"title": "Unauthorized",
"status": 401,
"detail": null
}
Thanks,
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.
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();
}
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.
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?
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.
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.
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
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:
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
}
}
}
}
}
}
}
It could be great to add support for multiple authentication adapter per API at it is possible in Github API.
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. :)
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!
"bin": [
"bin/zf-mvc-auth-oauth2-override.php"
],
This file is missing.
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 a factory that can create an AuthenticationService instance composing an adapter based on the configuration provided.
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.
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:
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.
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?
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:
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
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
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?
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.