From ea98eff8cfc2f09f2a1fd99b4f3886ec2223628a Mon Sep 17 00:00:00 2001 From: Sebastian Marcet Date: Fri, 3 Jul 2015 17:43:14 -0300 Subject: [PATCH] OIDC - OpenId Connect Implementation DB refactoring Client Admin Rectoring upgraded layout to use latest bootstrap Added bower support Added Behat support OIDC Discovery suuport added OIDC JWKS endpoint added Refactored OpenId workflows Refactored OAuth2 workflows Server Keys Admin Added Authorization Code Flow refactored to support OIDC Allow native apps to use auth code grant Allow native apps to use "TokenEndpoint_AuthMethod_PrivateKeyJwt" Filter on UI public/private keys algs based on the key usage Set as default auth protocol for private clients "client_secret_basic" Added feature client_secret_expired Filtered content of Token Endpoint Authorization Signed Algorithm based on Token Endpoint Authorization Method Implemented OAuth 2.0 Multiple Response Type Encoding Practices Implemented OAuth 2.0 Form Post Response Mode Implicit Flow refactored to support OIDC UserInfo Endpoint (OIDC/Claims) Hybrid Flow OIDC Session Management Change-Id: If3d38666f3f7f56bd8c94b9df2e6340554512612 --- .bowerrc | 4 + .gitignore | 2 + app/config/app.php | 4 +- app/config/dev/mail.php | 124 - app/config/local/app.php | 187 - app/config/local/mail.php | 124 - app/config/server.php | 4 + app/config/testing/session.php | 2 +- app/controllers/AdminController.php | 168 +- .../apis/AbstractRESTController.php | 20 +- .../apis/ApiResourceServerController.php | 125 +- app/controllers/apis/ApiScopeController.php | 8 +- .../apis/ApiScopeGroupController.php | 278 + .../apis/AssymetricKeyApiController.php | 131 + app/controllers/apis/ClientApiController.php | 423 +- .../apis/ClientPublicKeyApiController.php | 154 + app/controllers/apis/ICRUDController.php | 25 +- app/controllers/apis/JsonController.php | 15 +- .../apis/ServerPrivateKeyApiController.php | 110 + .../protected/OAuth2ProtectedController.php | 15 +- .../protected/OAuth2UserApiController.php | 93 +- .../oauth2/OAuth2ProviderController.php | 258 +- .../openid/OpenIdProviderController.php | 38 +- app/controllers/openid/UserController.php | 264 +- ...nsert_marketplace_api_endpoints_scopes.php | 6 +- ...06_30_192410_update_oauth2_client_oidc.php | 95 + ...15_07_09_044730_create_assymetric_keys.php | 87 + ...015_07_23_151507_migrate_redirect_uris.php | 54 + ...15_07_23_151520_add_new_default_scopes.php | 50 + ..._23_151541_update_oauth2_client_scopes.php | 43 + ..._clients_update_secret_expiration_date.php | 36 + ..._10_205534_add_user_info_OIDC_endpoint.php | 66 + ...015_12_14_203852_add_api_scopes_groups.php | 75 + .../2015_12_15_201400_update_api_scope.php | 26 + app/database/seeds/ApiEndpointSeeder.php | 30 + app/database/seeds/ApiScopeSeeder.php | 27 + app/database/seeds/ApiSeeder.php | 2 +- app/database/seeds/TestSeeder.php | 577 +- app/filters.php | 109 +- .../OAuth2RequestAccessTokenValidator.php | 162 +- app/lang/en/validation.php | 202 +- app/libs/auth/AuthService.php | 261 +- .../auth/AuthenticationServiceProvider.php | 4 +- app/libs/auth/CustomAuthProvider.php | 105 +- app/libs/auth/IMemberRepository.php | 29 +- app/libs/auth/IUserRepository.php | 94 +- app/libs/auth/User.php | 102 +- app/libs/oauth2/AddressClaim.php | 66 + app/libs/oauth2/IOAuth2Protocol.php | 20 +- app/libs/oauth2/OAuth2Message.php | 42 +- app/libs/oauth2/OAuth2Protocol.php | 1408 +- app/libs/oauth2/StandardClaims.php | 157 + app/libs/oauth2/builders/IdTokenBuilder.php | 35 + .../discovery/DiscoveryDocumentBuilder.php | 260 + .../IOpenIDProviderConfigurationService.php | 67 + .../discovery/OpenIDProviderMetadata.php | 276 + .../endpoints/AuthorizationEndpoint.php | 14 +- app/libs/oauth2/endpoints/IOAuth2Endpoint.php | 7 +- app/libs/oauth2/endpoints/TokenEndpoint.php | 11 + .../endpoints/TokenIntrospectionEndpoint.php | 44 +- .../endpoints/TokenRevocationEndpoint.php | 36 +- .../exceptions/AbsentCurrentUserException.php | 32 + .../exceptions/AccessDeniedException.php | 18 +- .../BearerTokenDisclosureAttemptException.php | 16 +- .../exceptions/ConsentRequiredException.php | 33 + .../ExpiredAccessTokenException.php | 14 +- .../ExpiredAuthorizationCodeException.php | 16 +- .../InteractionRequiredException.php | 32 + .../InvalidAccessTokenException.php | 18 +- .../exceptions/InvalidApiScopeGroup.php} | 16 +- .../exceptions/InvalidApplicationType.php | 16 +- .../InvalidAuthenticationRequestException.php | 33 + .../InvalidAuthorizationCodeException.php | 17 +- ...validClientAssertionAlgorithmException.php | 33 + .../InvalidClientAssertionException.php | 33 + .../InvalidClientAssertionTypeException.php | 33 + .../InvalidClientAuthMethodException.php | 33 + ...idClientAuthenticationContextException.php | 33 + .../exceptions/InvalidClientCredentials.php | 16 +- .../exceptions/InvalidClientException.php | 17 +- .../oauth2/exceptions/InvalidClientType.php | 16 +- .../exceptions/InvalidGrantTypeException.php | 19 +- .../oauth2/exceptions/InvalidLoginHint.php | 32 + .../exceptions/InvalidOAuth2Request.php | 16 +- .../InvalidRedeemAuthCodeException.php | 18 +- ...nvalidTokenEndpointAuthMethodException.php | 30 + .../exceptions/LockedClientException.php | 16 +- .../exceptions/LoginRequiredException.php | 32 + .../MissingClientAuthorizationInfo.php | 17 +- .../exceptions/MissingClientIdParam.php | 17 +- .../oauth2/exceptions/OAuth2BaseException.php | 39 + .../exceptions/OAuth2GenericException.php | 18 +- .../OAuth2ResourceServerException.php | 37 +- .../RecipientKeyNotFoundException.php | 32 + .../exceptions/ReplayAttackException.php | 43 +- .../RevokedAccessTokenException.php | 32 + .../RevokedRefreshTokenException.php | 32 + .../exceptions/ScopeNotAllowedException.php | 17 +- .../exceptions/ServerKeyNotFoundException.php | 32 + .../UnAuthorizedClientException.php | 16 +- .../UnsupportedResponseTypeException.php | 16 +- .../exceptions/UriNotAllowedException.php | 15 +- .../exceptions/UseRefreshTokenException.php | 15 +- ...uth2AccessTokenFragmentResponseFactory.php | 110 + .../OAuth2AccessTokenResponseFactory.php | 140 + .../OAuth2AuthorizationRequestFactory.php | 71 + .../oauth2/grant_types/AbstractGrantType.php | 98 +- .../AuthorizationCodeGrantType.php | 491 +- .../ClientCredentialsGrantType.php | 105 +- .../oauth2/grant_types/HybridGrantType.php | 258 + app/libs/oauth2/grant_types/IGrantType.php | 8 +- .../oauth2/grant_types/ImplicitGrantType.php | 282 +- .../grant_types/InteractiveGrantType.php | 628 + .../RefreshBearerTokenGrantType.php | 202 +- .../RevokeBearerTokenGrantType.php | 219 +- .../ValidateBearerTokenGrantType.php | 245 +- .../heuristics/ClientEncryptionKeyFinder.php | 127 + .../heuristics/ClientSigningKeyFinder.php | 129 + app/libs/oauth2/heuristics/IKeyFinder.php | 34 + .../heuristics/ServerEncryptionKeyFinder.php | 111 + .../heuristics/ServerSigningKeyFinder.php | 122 + app/libs/oauth2/models/AuthorizationCode.php | 166 +- .../ClientAssertionAuthenticationContext.php | 100 + .../models/ClientAuthenticationContext.php | 89 + ...ClientCredentialsAuthenticationContext.php | 58 + app/libs/oauth2/models/IApiScope.php | 25 +- app/libs/oauth2/models/IApiScopeGroup.php | 55 + app/libs/oauth2/models/IAssymetricKey.php | 89 + app/libs/oauth2/models/IClient.php | 131 +- app/libs/oauth2/models/IClientPublicKey.php | 42 + app/libs/oauth2/models/IOAuth2User.php | 11 + app/libs/oauth2/models/IPrincipal.php | 38 + app/libs/oauth2/models/IScope.php | 56 + app/libs/oauth2/models/IServerPrivateKey.php | 41 + app/libs/oauth2/models/JWTResponseInfo.php | 83 + app/libs/oauth2/models/Principal.php | 74 + app/libs/oauth2/models/SecurityContext.php | 91 + app/libs/oauth2/models/Token.php | 11 +- .../oauth2/models/TokenEndpointAuthInfo.php | 59 + .../repositories/IApiScopeGroupRepository.php | 26 + .../repositories/IAssymetricKeyRepository.php | 89 + .../IClientPublicKeyRepository.php | 23 + .../oauth2/repositories/IClientRepository.php | 43 + .../IServerPrivateKeyRepository.php | 24 + .../requests/OAuth2AuthenticationRequest.php | 234 + .../requests/OAuth2AuthorizationRequest.php | 106 +- .../oauth2/requests/OAuth2LogoutRequest.php | 74 + app/libs/oauth2/requests/OAuth2Request.php | 41 +- .../oauth2/requests/OAuth2RequestMemento.php | 65 + .../oauth2/requests/OAuth2TokenRequest.php | 7 +- .../oauth2/resource_server/IUserService.php | 10 +- .../OAuth2ProtectedService.php | 11 +- .../OAuth2AccessTokenFragmentResponse.php | 25 +- .../responses/OAuth2AccessTokenResponse.php | 23 +- .../OAuth2AccessTokenValidationResponse.php | 39 +- .../responses/OAuth2AuthorizationResponse.php | 28 +- .../responses/OAuth2DirectErrorResponse.php | 51 +- .../oauth2/responses/OAuth2DirectResponse.php | 8 +- .../OAuth2HybridTokenFragmentResponse.php | 55 + .../OAuth2IDTokenFragmentResponse.php | 54 + .../responses/OAuth2IdTokenResponse.php | 47 + .../responses/OAuth2IndirectErrorResponse.php | 54 +- .../OAuth2IndirectFragmentErrorResponse.php | 32 +- .../OAuth2IndirectFragmentResponse.php | 6 +- .../responses/OAuth2IndirectResponse.php | 35 +- .../oauth2/responses/OAuth2LogoutResponse.php | 39 + .../oauth2/responses/OAuth2PostResponse.php | 103 + .../OAuth2TokenRevocationResponse.php | 4 +- .../OAuth2WWWAuthenticateErrorResponse.php | 4 +- .../oauth2/services/IAllowedOriginService.php | 17 - .../oauth2/services/IApiScopeGroupService.php | 48 + app/libs/oauth2/services/IApiScopeService.php | 16 +- .../oauth2/services/IAssymetricKeyService.php | 41 + .../services/IClienPublicKeyService.php | 26 + .../services/IClientCrendentialGenerator.php | 31 + .../oauth2/services/IClientJWKSetReader.php | 31 + app/libs/oauth2/services/IClientService.php | 61 +- ...entoOAuth2AuthenticationRequestService.php | 25 - .../IMementoOAuth2SerializerService.php | 34 + .../oauth2/services/IPrincipalService.php | 49 + .../services/ISecurityContextService.php | 40 + .../services/IServerPrivateKeyService.php | 24 + app/libs/oauth2/services/ITokenService.php | 44 +- .../oauth2/services/IUserConsentService.php | 7 +- .../oauth2/services/OAuth2ServiceCatalog.php | 27 +- .../ClientAssertionAuthContextValidator.php | 110 + .../ClientAuthContextValidatorFactory.php | 86 + ...ntPlainCredentialsAuthContextValidator.php | 53 + ...rivateKeyAssertionAuthContextValidator.php | 108 + ...redSecretAssertionAuthContextValidator.php | 61 + .../IClientAuthContextValidator.php | 30 + ...uth2IndirectErrorResponseFactoryMethod.php | 66 +- .../OAuth2ResponseStrategyFactoryMethod.php | 56 +- app/libs/openid/OpenIdMessage.php | 42 +- app/libs/openid/OpenIdProtocol.php | 42 +- app/libs/openid/OpenIdServiceProvider.php | 10 +- .../OpenIdAuthenticationExtension.php | 38 +- .../OpenIdAuthenticationRequestHandler.php | 298 +- .../openid/handlers/OpenIdMessageHandler.php | 3 +- app/libs/openid/model/IOpenIdUser.php | 77 +- .../requests/OpenIdAuthenticationRequest.php | 161 +- .../OpenIdCheckAuthenticationRequest.php | 70 +- .../openid/requests/OpenIdMessageMemento.php | 63 + app/libs/openid/requests/OpenIdRequest.php | 7 +- .../openid/responses/OpenIdDirectResponse.php | 4 +- .../services/IMementoOpenIdRequestService.php | 25 - .../IMementoOpenIdSerializerService.php | 32 + app/libs/openid/services/IUserService.php | 18 +- .../openid/services/OpenIdServiceCatalog.php | 7 +- app/libs/utils/ArrayUtils.php | 42 + app/libs/utils/IHttpResponseStrategy.php | 3 +- app/libs/utils/db/IBaseRepository.php | 56 + .../exceptions/ConfigurationException.php | 26 + .../exceptions/EntityNotFoundException.php | 25 + app/libs/utils/http/HttpContentType.php | 44 + app/libs/utils/http/HttpMessage.php | 8 +- app/libs/utils/http/HttpUtils.php | 44 + app/libs/utils/model/BaseModelEloquent.php | 4 +- app/{models => libs/utils/model}/IEntity.php | 11 +- app/libs/utils/model/Identifier.php | 21 +- app/libs/utils/services/IAuthService.php | 83 +- .../services/IServerConfigurationService.php | 5 + app/libs/utils/services/ServiceLocator.php | 9 +- .../services/UniqueIdentifierGenerator.php | 10 +- app/models/Member.php | 19 +- app/models/oauth2/Api.php | 58 +- app/models/oauth2/ApiScope.php | 92 +- app/models/oauth2/ApiScopeGroup.php | 75 + app/models/oauth2/AssymetricKey.php | 158 + app/models/oauth2/Client.php | 404 +- app/models/oauth2/ClientAllowedOrigin.php | 12 - app/models/oauth2/ClientAuthorizedUri.php | 13 - app/models/oauth2/ClientPublicKey.php | 84 + app/models/oauth2/ResourceServer.php | 24 +- app/models/oauth2/ServerPrivateKey.php | 145 + app/providers/BehatSessionServiceProvider.php | 26 + .../AbstractEloquentEntityRepository.php | 79 + .../EloquentApiScopeGroupRepository.php | 43 + .../EloquentAssymetricKeyRepository.php | 160 + .../EloquentClientPublicKeyRepository.php | 35 + app/repositories/EloquentClientRepository.php | 73 + app/repositories/EloquentMemberRepository.php | 20 +- .../EloquentOpenIdAssociationRepository.php | 57 +- .../EloquentServerPrivateKeyRepository.php | 33 + app/repositories/EloquentUserRepository.php | 156 +- app/repositories/RepositoriesProvider.php | 26 +- app/routes.php | 254 +- .../exceptions/ValidationException.php | 17 + app/services/oauth2/AllowedOriginService.php | 76 - app/services/oauth2/ApiScopeGroupService.php | 203 + app/services/oauth2/ApiScopeService.php | 205 +- app/services/oauth2/AssymetricKeyService.php | 112 + .../oauth2/AuthorizationCodeRedeemPolicy.php | 48 +- app/services/oauth2/CORS/CORSMiddleware.php | 164 +- app/services/oauth2/CORS/CORSProvider.php | 12 +- app/services/oauth2/ClienPublicKeyService.php | 102 + .../oauth2/ClientCrendentialGenerator.php | 49 + app/services/oauth2/ClientService.php | 571 +- .../oauth2/HttpIClientJWKSetReader.php | 51 + app/services/oauth2/IdTokenBuilderImpl.php | 182 + ...entoOAuth2AuthenticationRequestService.php | 93 - .../OAuth2MementoSessionSerializerService.php | 68 + app/services/oauth2/OAuth2ServiceProvider.php | 39 +- .../OpenIDProviderConfigurationService.php | 99 + app/services/oauth2/PrincipalService.php | 103 + app/services/oauth2/ResourceServerService.php | 155 +- .../oauth2/SecurityContextService.php | 70 + .../oauth2/ServerPrivateKeyService.php | 109 + app/services/oauth2/TokenService.php | 840 +- app/services/oauth2/UserConsentService.php | 109 +- .../oauth2/resource_server/UserService.php | 195 +- .../openid/AuthenticationStrategy.php | 26 - app/services/openid/MementoRequestService.php | 83 - .../OpenIdMementoSessionSerializerService.php | 58 + app/services/openid/OpenIdProvider.php | 3 +- app/services/openid/UserService.php | 356 +- .../AbstractBlacklistSecurityPolicy.php | 54 +- .../BlacklistSecurityPolicy.php | 146 +- .../security_policies/DelayCounterMeasure.php | 4 + app/services/utils/BannedIPService.php | 90 +- .../utils/ServerConfigurationService.php | 186 +- app/start/global.php | 6 +- app/strategies/DefaultLoginStrategy.php | 1 + .../IndirectResponseQueryStringStrategy.php | 2 +- .../IndirectResponseUrlFragmentStrategy.php | 2 +- app/strategies/OAuth2ConsentStrategy.php | 58 +- app/strategies/OAuth2LoginStrategy.php | 90 +- .../OpenIdAuthenticationStrategy.php | 42 + app/strategies/OpenIdConsentStrategy.php | 56 +- app/strategies/OpenIdLoginStrategy.php | 62 +- app/strategies/PostResponseStrategy.php | 35 + app/strategies/StrategyProvider.php | 16 +- app/tests/ClientApiTest.php | 76 + app/tests/ClientPublicKeyApiTest.php | 63 + app/tests/OAuth2ProtectedApiTest.php | 2 +- app/tests/OAuth2ProtocolTest.php | 161 +- app/tests/OAuth2UserServiceApiTest.php | 1 + app/tests/OIDCProtocolTest.php | 2520 +++ app/tests/OpenIdProtocolTest.php | 81 +- app/tests/OpenStackIDBaseTest.php | 2 + app/tests/UserServiceTest.php | 40 + app/tests/UserTest.php | 10 +- .../features/bootstrap/FeatureContext.php | 110 + .../features/bootstrap/LaravelContext.php | 285 + app/tests/features/get_auth_code.feature | 17 + app/validators/CustomValidator.php | 289 +- app/views/400.blade.php | 15 + app/views/admin/banned-ips.blade.php | 10 +- app/views/admin/server-config.blade.php | 68 +- app/views/admin/users.blade.php | 10 +- app/views/extensions/ax.blade.php | 2 +- app/views/extensions/oauth2.blade.php | 18 +- app/views/extensions/sreg.blade.php | 2 +- app/views/home.blade.php | 29 +- app/views/identity.blade.php | 30 +- app/views/layout.blade.php | 42 +- app/views/login.blade.php | 54 +- app/views/menu.blade.php | 86 +- app/views/modal.blade.php | 18 + app/views/oauth2/consent.blade.php | 37 +- .../oauth2/profile/add-client-form.blade.php | 37 + .../admin/api-scope-group-add-form.blade.php | 19 + .../profile/admin/api-scope-groups.blade.php | 99 + .../oauth2/profile/admin/clients.blade.php | 2 +- .../admin/edit-api-scope-group.blade.php | 94 + .../oauth2/profile/admin/edit-api.blade.php | 194 +- .../profile/admin/edit-endpoint.blade.php | 180 +- .../admin/edit-resource-server.blade.php | 215 +- .../oauth2/profile/admin/edit-scope.blade.php | 121 +- .../profile/admin/endpoint-add-form.blade.php | 45 + .../admin/resource-server-add-form.blade.php | 20 + .../resource-server-api-add-form.blade.php | 15 + .../profile/admin/resource-servers.blade.php | 53 +- .../profile/admin/scope-add-form.blade.php | 30 + .../server-private-key-add-form.blade.php | 45 + .../admin/server-private-keys.blade.php | 104 + app/views/oauth2/profile/clients.blade.php | 204 +- .../edit-client-allowed-origins.blade.php | 49 - .../oauth2/profile/edit-client-data.blade.php | 177 +- .../edit-client-public-key-add-form.blade.php | 32 + .../profile/edit-client-public-keys.blade.php | 74 + .../edit-client-redirect-uris.blade.php | 47 - .../profile/edit-client-scopes.blade.php | 17 +- .../edit-client-security-logout.blade.php | 33 + ...it-client-security-main-settings.blade.php | 81 + .../profile/edit-client-tokens.blade.php | 137 +- .../oauth2/profile/edit-client.blade.php | 153 +- .../oauth2/profile/edit-user-grants.blade.php | 19 +- .../oauth2/session/check-session.blade.php | 13 + .../oauth2/session/session-ended.blade.php | 15 + .../oauth2/session/session-logout.blade.php | 38 + app/views/openid/consent.blade.php | 6 +- app/views/profile.blade.php | 47 +- behat.yml | 13 + bower.json | 44 + composer.json | 118 +- public/assets/css/edit-client-public-keys.css | 28 + public/{ => assets}/css/main.css | 93 +- public/assets/css/private-keys.css | 28 + .../smoothness/images/animated-overlay.gif | Bin .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin .../images/ui-bg_flat_75_ffffff_40x100.png | Bin .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin .../images/ui-bg_glass_65_ffffff_1x400.png | Bin .../images/ui-bg_glass_75_dadada_1x400.png | Bin .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin .../images/ui-icons_222222_256x240.png | Bin .../images/ui-icons_2e83ff_256x240.png | Bin .../images/ui-icons_454545_256x240.png | Bin .../images/ui-icons_888888_256x240.png | Bin .../images/ui-icons_cd0a0a_256x240.png | Bin .../smoothness/jquery-ui-1.10.3.custom.css | 0 .../jquery-ui-1.10.3.custom.min.css | 0 public/{ => assets}/img/apis/server.png | Bin .../img/generic-profile-photo-small.png | Bin .../img/generic-profile-photo.png | Bin .../img/glyphicons-halflings-white.png | Bin .../{ => assets}/img/glyphicons-halflings.png | Bin .../{ => assets}/img/oauth2.default.logo.png | Bin .../img/open-stack-cloud-computing-logo.png | Bin public/{ => assets}/js/admin/banned-ips.js | 0 public/{ => assets}/js/admin/server-config.js | 6 +- public/{ => assets}/js/admin/users.js | 0 public/assets/js/ajax.utils.js | 66 + public/{ => assets}/js/jquery.cleanform.js | 0 public/{ => assets}/js/jquery.serialize.js | 0 ...uery.validate.additional.custom.methods.js | 93 + public/{ => assets}/js/oauth2/consent.js | 4 +- .../oauth2/profile/admin/api-scope-groups.js | 216 + .../js/oauth2/profile/admin/clients.js | 0 .../profile/admin/edit-api-scope-group.js | 106 + .../js/oauth2/profile/admin/edit-api.js | 22 +- .../js/oauth2/profile/admin/edit-endpoint.js | 0 .../profile/admin/edit-resource-server.js | 13 +- .../js/oauth2/profile/admin/edit-scope.js | 0 .../oauth2/profile/admin/resource-servers.js | 8 +- .../profile/admin/server-private-keys.js | 318 + .../{ => assets}/js/oauth2/profile/clients.js | 9 +- .../js/oauth2/profile/edit-client-data.js | 161 + .../oauth2/profile/edit-client-public-keys.js | 270 + .../js/oauth2/profile/edit-client-scopes.js | 4 +- .../profile/edit-client-security-logout.js | 55 + .../edit-client-security-main-settings.js | 86 + .../js/oauth2/profile/edit-client-tokens.js | 4 +- .../js/oauth2/profile/edit-user-grants.js | 0 .../assets/js/oauth2/session/check.session.js | 89 + public/{ => assets}/js/openid/consent.js | 0 public/css/bootstrap.css | 6315 ------- public/css/bootstrap.min.css | 874 - public/js/additional-methods.js | 617 - public/js/additional-methods.min.js | 2 - public/js/ajax.utils.js | 46 - public/js/bootstrap.js | 2291 --- public/js/bootstrap.min.js | 7 - public/js/jquery-2.1.0.js | 9111 ---------- public/js/jquery-2.1.0.min.js | 4 - public/js/jquery-migrate-1.2.1.min.js | 2 - public/js/jquery-ui-1.10.3.custom.js | 14971 ---------------- public/js/jquery-ui-1.10.3.custom.min.js | 7 - ...uery.validate.additional.custom.methods.js | 109 - public/js/jquery.validate.js | 1231 -- public/js/jquery.validate.min.js | 2 - .../profile/edit-client-allowed-origins.js | 114 - public/js/oauth2/profile/edit-client-data.js | 70 - .../profile/edit-client-redirect-uris.js | 107 - public/js/pure.js | 861 - public/js/pure.min.js | 36 - readme.md | 7 +- 430 files changed, 26305 insertions(+), 42535 deletions(-) create mode 100644 .bowerrc delete mode 100644 app/config/dev/mail.php delete mode 100644 app/config/local/app.php delete mode 100644 app/config/local/mail.php create mode 100644 app/controllers/apis/ApiScopeGroupController.php create mode 100644 app/controllers/apis/AssymetricKeyApiController.php create mode 100644 app/controllers/apis/ClientPublicKeyApiController.php create mode 100644 app/controllers/apis/ServerPrivateKeyApiController.php create mode 100644 app/database/migrations/2015_06_30_192410_update_oauth2_client_oidc.php create mode 100644 app/database/migrations/2015_07_09_044730_create_assymetric_keys.php create mode 100644 app/database/migrations/2015_07_23_151507_migrate_redirect_uris.php create mode 100644 app/database/migrations/2015_07_23_151520_add_new_default_scopes.php create mode 100644 app/database/migrations/2015_07_23_151541_update_oauth2_client_scopes.php create mode 100644 app/database/migrations/2015_07_30_221758_oauth2_clients_update_secret_expiration_date.php create mode 100644 app/database/migrations/2015_08_10_205534_add_user_info_OIDC_endpoint.php create mode 100644 app/database/migrations/2015_12_14_203852_add_api_scopes_groups.php create mode 100644 app/database/migrations/2015_12_15_201400_update_api_scope.php create mode 100644 app/libs/oauth2/AddressClaim.php create mode 100644 app/libs/oauth2/StandardClaims.php create mode 100644 app/libs/oauth2/builders/IdTokenBuilder.php create mode 100644 app/libs/oauth2/discovery/DiscoveryDocumentBuilder.php create mode 100644 app/libs/oauth2/discovery/IOpenIDProviderConfigurationService.php create mode 100644 app/libs/oauth2/discovery/OpenIDProviderMetadata.php create mode 100644 app/libs/oauth2/exceptions/AbsentCurrentUserException.php create mode 100644 app/libs/oauth2/exceptions/ConsentRequiredException.php create mode 100644 app/libs/oauth2/exceptions/InteractionRequiredException.php rename app/{models/IBaseRepository.php => libs/oauth2/exceptions/InvalidApiScopeGroup.php} (74%) create mode 100644 app/libs/oauth2/exceptions/InvalidAuthenticationRequestException.php create mode 100644 app/libs/oauth2/exceptions/InvalidClientAssertionAlgorithmException.php create mode 100644 app/libs/oauth2/exceptions/InvalidClientAssertionException.php create mode 100644 app/libs/oauth2/exceptions/InvalidClientAssertionTypeException.php create mode 100644 app/libs/oauth2/exceptions/InvalidClientAuthMethodException.php create mode 100644 app/libs/oauth2/exceptions/InvalidClientAuthenticationContextException.php create mode 100644 app/libs/oauth2/exceptions/InvalidLoginHint.php create mode 100644 app/libs/oauth2/exceptions/InvalidTokenEndpointAuthMethodException.php create mode 100644 app/libs/oauth2/exceptions/LoginRequiredException.php create mode 100644 app/libs/oauth2/exceptions/OAuth2BaseException.php create mode 100644 app/libs/oauth2/exceptions/RecipientKeyNotFoundException.php create mode 100644 app/libs/oauth2/exceptions/RevokedAccessTokenException.php create mode 100644 app/libs/oauth2/exceptions/RevokedRefreshTokenException.php create mode 100644 app/libs/oauth2/exceptions/ServerKeyNotFoundException.php create mode 100644 app/libs/oauth2/factories/OAuth2AccessTokenFragmentResponseFactory.php create mode 100644 app/libs/oauth2/factories/OAuth2AccessTokenResponseFactory.php create mode 100644 app/libs/oauth2/factories/OAuth2AuthorizationRequestFactory.php create mode 100644 app/libs/oauth2/grant_types/HybridGrantType.php create mode 100644 app/libs/oauth2/grant_types/InteractiveGrantType.php create mode 100644 app/libs/oauth2/heuristics/ClientEncryptionKeyFinder.php create mode 100644 app/libs/oauth2/heuristics/ClientSigningKeyFinder.php create mode 100644 app/libs/oauth2/heuristics/IKeyFinder.php create mode 100644 app/libs/oauth2/heuristics/ServerEncryptionKeyFinder.php create mode 100644 app/libs/oauth2/heuristics/ServerSigningKeyFinder.php create mode 100644 app/libs/oauth2/models/ClientAssertionAuthenticationContext.php create mode 100644 app/libs/oauth2/models/ClientAuthenticationContext.php create mode 100644 app/libs/oauth2/models/ClientCredentialsAuthenticationContext.php create mode 100644 app/libs/oauth2/models/IApiScopeGroup.php create mode 100644 app/libs/oauth2/models/IAssymetricKey.php create mode 100644 app/libs/oauth2/models/IClientPublicKey.php create mode 100644 app/libs/oauth2/models/IPrincipal.php create mode 100644 app/libs/oauth2/models/IScope.php create mode 100644 app/libs/oauth2/models/IServerPrivateKey.php create mode 100644 app/libs/oauth2/models/JWTResponseInfo.php create mode 100644 app/libs/oauth2/models/Principal.php create mode 100644 app/libs/oauth2/models/SecurityContext.php create mode 100644 app/libs/oauth2/models/TokenEndpointAuthInfo.php create mode 100644 app/libs/oauth2/repositories/IApiScopeGroupRepository.php create mode 100644 app/libs/oauth2/repositories/IAssymetricKeyRepository.php create mode 100644 app/libs/oauth2/repositories/IClientPublicKeyRepository.php create mode 100644 app/libs/oauth2/repositories/IClientRepository.php create mode 100644 app/libs/oauth2/repositories/IServerPrivateKeyRepository.php create mode 100644 app/libs/oauth2/requests/OAuth2AuthenticationRequest.php create mode 100644 app/libs/oauth2/requests/OAuth2LogoutRequest.php create mode 100644 app/libs/oauth2/requests/OAuth2RequestMemento.php create mode 100644 app/libs/oauth2/responses/OAuth2HybridTokenFragmentResponse.php create mode 100644 app/libs/oauth2/responses/OAuth2IDTokenFragmentResponse.php create mode 100644 app/libs/oauth2/responses/OAuth2IdTokenResponse.php create mode 100644 app/libs/oauth2/responses/OAuth2LogoutResponse.php create mode 100644 app/libs/oauth2/responses/OAuth2PostResponse.php delete mode 100644 app/libs/oauth2/services/IAllowedOriginService.php create mode 100644 app/libs/oauth2/services/IApiScopeGroupService.php create mode 100644 app/libs/oauth2/services/IAssymetricKeyService.php create mode 100644 app/libs/oauth2/services/IClienPublicKeyService.php create mode 100644 app/libs/oauth2/services/IClientCrendentialGenerator.php create mode 100644 app/libs/oauth2/services/IClientJWKSetReader.php delete mode 100644 app/libs/oauth2/services/IMementoOAuth2AuthenticationRequestService.php create mode 100644 app/libs/oauth2/services/IMementoOAuth2SerializerService.php create mode 100644 app/libs/oauth2/services/IPrincipalService.php create mode 100644 app/libs/oauth2/services/ISecurityContextService.php create mode 100644 app/libs/oauth2/services/IServerPrivateKeyService.php create mode 100644 app/libs/oauth2/strategies/ClientAssertionAuthContextValidator.php create mode 100644 app/libs/oauth2/strategies/ClientAuthContextValidatorFactory.php create mode 100644 app/libs/oauth2/strategies/ClientPlainCredentialsAuthContextValidator.php create mode 100644 app/libs/oauth2/strategies/ClientPrivateKeyAssertionAuthContextValidator.php create mode 100644 app/libs/oauth2/strategies/ClientSharedSecretAssertionAuthContextValidator.php create mode 100644 app/libs/oauth2/strategies/IClientAuthContextValidator.php create mode 100644 app/libs/openid/requests/OpenIdMessageMemento.php delete mode 100644 app/libs/openid/services/IMementoOpenIdRequestService.php create mode 100644 app/libs/openid/services/IMementoOpenIdSerializerService.php create mode 100644 app/libs/utils/ArrayUtils.php create mode 100644 app/libs/utils/db/IBaseRepository.php create mode 100644 app/libs/utils/exceptions/ConfigurationException.php create mode 100644 app/libs/utils/exceptions/EntityNotFoundException.php create mode 100644 app/libs/utils/http/HttpContentType.php create mode 100644 app/libs/utils/http/HttpUtils.php rename app/{models => libs/utils/model}/IEntity.php (81%) create mode 100644 app/models/oauth2/ApiScopeGroup.php create mode 100644 app/models/oauth2/AssymetricKey.php delete mode 100644 app/models/oauth2/ClientAllowedOrigin.php delete mode 100644 app/models/oauth2/ClientAuthorizedUri.php create mode 100644 app/models/oauth2/ClientPublicKey.php create mode 100644 app/models/oauth2/ServerPrivateKey.php create mode 100644 app/providers/BehatSessionServiceProvider.php create mode 100644 app/repositories/AbstractEloquentEntityRepository.php create mode 100644 app/repositories/EloquentApiScopeGroupRepository.php create mode 100644 app/repositories/EloquentAssymetricKeyRepository.php create mode 100644 app/repositories/EloquentClientPublicKeyRepository.php create mode 100644 app/repositories/EloquentClientRepository.php create mode 100644 app/repositories/EloquentServerPrivateKeyRepository.php create mode 100644 app/services/exceptions/ValidationException.php delete mode 100644 app/services/oauth2/AllowedOriginService.php create mode 100644 app/services/oauth2/ApiScopeGroupService.php create mode 100644 app/services/oauth2/AssymetricKeyService.php create mode 100644 app/services/oauth2/ClienPublicKeyService.php create mode 100644 app/services/oauth2/ClientCrendentialGenerator.php create mode 100644 app/services/oauth2/HttpIClientJWKSetReader.php create mode 100644 app/services/oauth2/IdTokenBuilderImpl.php delete mode 100644 app/services/oauth2/MementoOAuth2AuthenticationRequestService.php create mode 100644 app/services/oauth2/OAuth2MementoSessionSerializerService.php create mode 100644 app/services/oauth2/OpenIDProviderConfigurationService.php create mode 100644 app/services/oauth2/PrincipalService.php create mode 100644 app/services/oauth2/SecurityContextService.php create mode 100644 app/services/oauth2/ServerPrivateKeyService.php delete mode 100644 app/services/openid/AuthenticationStrategy.php delete mode 100644 app/services/openid/MementoRequestService.php create mode 100644 app/services/openid/OpenIdMementoSessionSerializerService.php create mode 100644 app/strategies/OpenIdAuthenticationStrategy.php create mode 100644 app/strategies/PostResponseStrategy.php create mode 100644 app/tests/ClientApiTest.php create mode 100644 app/tests/ClientPublicKeyApiTest.php create mode 100644 app/tests/OIDCProtocolTest.php create mode 100644 app/tests/UserServiceTest.php create mode 100644 app/tests/features/bootstrap/FeatureContext.php create mode 100644 app/tests/features/bootstrap/LaravelContext.php create mode 100644 app/tests/features/get_auth_code.feature create mode 100644 app/views/400.blade.php create mode 100644 app/views/modal.blade.php create mode 100644 app/views/oauth2/profile/add-client-form.blade.php create mode 100644 app/views/oauth2/profile/admin/api-scope-group-add-form.blade.php create mode 100644 app/views/oauth2/profile/admin/api-scope-groups.blade.php create mode 100644 app/views/oauth2/profile/admin/edit-api-scope-group.blade.php create mode 100644 app/views/oauth2/profile/admin/endpoint-add-form.blade.php create mode 100644 app/views/oauth2/profile/admin/resource-server-add-form.blade.php create mode 100644 app/views/oauth2/profile/admin/resource-server-api-add-form.blade.php create mode 100644 app/views/oauth2/profile/admin/scope-add-form.blade.php create mode 100644 app/views/oauth2/profile/admin/server-private-key-add-form.blade.php create mode 100644 app/views/oauth2/profile/admin/server-private-keys.blade.php delete mode 100644 app/views/oauth2/profile/edit-client-allowed-origins.blade.php create mode 100644 app/views/oauth2/profile/edit-client-public-key-add-form.blade.php create mode 100644 app/views/oauth2/profile/edit-client-public-keys.blade.php delete mode 100644 app/views/oauth2/profile/edit-client-redirect-uris.blade.php create mode 100644 app/views/oauth2/profile/edit-client-security-logout.blade.php create mode 100644 app/views/oauth2/profile/edit-client-security-main-settings.blade.php create mode 100644 app/views/oauth2/session/check-session.blade.php create mode 100644 app/views/oauth2/session/session-ended.blade.php create mode 100644 app/views/oauth2/session/session-logout.blade.php create mode 100644 behat.yml create mode 100644 bower.json create mode 100644 public/assets/css/edit-client-public-keys.css rename public/{ => assets}/css/main.css (57%) create mode 100644 public/assets/css/private-keys.css rename public/{ => assets}/css/smoothness/images/animated-overlay.gif (100%) rename public/{ => assets}/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png (100%) rename public/{ => assets}/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png (100%) rename public/{ => assets}/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png (100%) rename public/{ => assets}/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png (100%) rename public/{ => assets}/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png (100%) rename public/{ => assets}/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png (100%) rename public/{ => assets}/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png (100%) rename public/{ => assets}/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png (100%) rename public/{ => assets}/css/smoothness/images/ui-icons_222222_256x240.png (100%) rename public/{ => assets}/css/smoothness/images/ui-icons_2e83ff_256x240.png (100%) rename public/{ => assets}/css/smoothness/images/ui-icons_454545_256x240.png (100%) rename public/{ => assets}/css/smoothness/images/ui-icons_888888_256x240.png (100%) rename public/{ => assets}/css/smoothness/images/ui-icons_cd0a0a_256x240.png (100%) rename public/{ => assets}/css/smoothness/jquery-ui-1.10.3.custom.css (100%) rename public/{ => assets}/css/smoothness/jquery-ui-1.10.3.custom.min.css (100%) rename public/{ => assets}/img/apis/server.png (100%) rename public/{ => assets}/img/generic-profile-photo-small.png (100%) rename public/{ => assets}/img/generic-profile-photo.png (100%) rename public/{ => assets}/img/glyphicons-halflings-white.png (100%) rename public/{ => assets}/img/glyphicons-halflings.png (100%) rename public/{ => assets}/img/oauth2.default.logo.png (100%) rename public/{ => assets}/img/open-stack-cloud-computing-logo.png (100%) rename public/{ => assets}/js/admin/banned-ips.js (100%) rename public/{ => assets}/js/admin/server-config.js (90%) rename public/{ => assets}/js/admin/users.js (100%) create mode 100644 public/assets/js/ajax.utils.js rename public/{ => assets}/js/jquery.cleanform.js (100%) rename public/{ => assets}/js/jquery.serialize.js (100%) create mode 100644 public/assets/js/jquery.validate.additional.custom.methods.js rename public/{ => assets}/js/oauth2/consent.js (88%) create mode 100644 public/assets/js/oauth2/profile/admin/api-scope-groups.js rename public/{ => assets}/js/oauth2/profile/admin/clients.js (100%) create mode 100644 public/assets/js/oauth2/profile/admin/edit-api-scope-group.js rename public/{ => assets}/js/oauth2/profile/admin/edit-api.js (94%) rename public/{ => assets}/js/oauth2/profile/admin/edit-endpoint.js (100%) rename public/{ => assets}/js/oauth2/profile/admin/edit-resource-server.js (93%) rename public/{ => assets}/js/oauth2/profile/admin/edit-scope.js (100%) rename public/{ => assets}/js/oauth2/profile/admin/resource-servers.js (92%) create mode 100644 public/assets/js/oauth2/profile/admin/server-private-keys.js rename public/{ => assets}/js/oauth2/profile/clients.js (93%) create mode 100644 public/assets/js/oauth2/profile/edit-client-data.js create mode 100644 public/assets/js/oauth2/profile/edit-client-public-keys.js rename public/{ => assets}/js/oauth2/profile/edit-client-scopes.js (86%) create mode 100644 public/assets/js/oauth2/profile/edit-client-security-logout.js create mode 100644 public/assets/js/oauth2/profile/edit-client-security-main-settings.js rename public/{ => assets}/js/oauth2/profile/edit-client-tokens.js (94%) rename public/{ => assets}/js/oauth2/profile/edit-user-grants.js (100%) create mode 100644 public/assets/js/oauth2/session/check.session.js rename public/{ => assets}/js/openid/consent.js (100%) delete mode 100644 public/css/bootstrap.css delete mode 100644 public/css/bootstrap.min.css delete mode 100644 public/js/additional-methods.js delete mode 100644 public/js/additional-methods.min.js delete mode 100644 public/js/ajax.utils.js delete mode 100644 public/js/bootstrap.js delete mode 100644 public/js/bootstrap.min.js delete mode 100644 public/js/jquery-2.1.0.js delete mode 100644 public/js/jquery-2.1.0.min.js delete mode 100644 public/js/jquery-migrate-1.2.1.min.js delete mode 100644 public/js/jquery-ui-1.10.3.custom.js delete mode 100644 public/js/jquery-ui-1.10.3.custom.min.js delete mode 100644 public/js/jquery.validate.additional.custom.methods.js delete mode 100644 public/js/jquery.validate.js delete mode 100644 public/js/jquery.validate.min.js delete mode 100644 public/js/oauth2/profile/edit-client-allowed-origins.js delete mode 100644 public/js/oauth2/profile/edit-client-data.js delete mode 100644 public/js/oauth2/profile/edit-client-redirect-uris.js delete mode 100644 public/js/pure.js delete mode 100644 public/js/pure.min.js diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 00000000..278532f0 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,4 @@ +{ + "directory": "public/bower_assets", + "interactive": false +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index a727b5d7..a6f153c1 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ ChangeLog doc/build *.egg *.egg-info +public/bower_assets +public/bower_assets/* \ No newline at end of file diff --git a/app/config/app.php b/app/config/app.php index 78e8a57c..6f1ecb64 100644 --- a/app/config/app.php +++ b/app/config/app.php @@ -26,7 +26,7 @@ return array( | */ - 'url' => 'http://localhost', + 'url' => '', /* |-------------------------------------------------------------------------- @@ -65,7 +65,7 @@ return array( | */ - 'key' => '2mikPhsqqnw9qfEtp3XxHmtkZqoTTvWl', + 'key' => '', /* |-------------------------------------------------------------------------- diff --git a/app/config/dev/mail.php b/app/config/dev/mail.php deleted file mode 100644 index ac813553..00000000 --- a/app/config/dev/mail.php +++ /dev/null @@ -1,124 +0,0 @@ - 'mail', - - /* - |-------------------------------------------------------------------------- - | SMTP Host Address - |-------------------------------------------------------------------------- - | - | Here you may provide the host address of the SMTP server used by your - | applications. A default option is provided that is compatible with - | the Postmark mail service, which will provide reliable delivery. - | - */ - - 'host' => 'smtp.mailgun.org', - - /* - |-------------------------------------------------------------------------- - | SMTP Host Port - |-------------------------------------------------------------------------- - | - | This is the SMTP port used by your application to delivery e-mails to - | users of your application. Like the host we have set this value to - | stay compatible with the Postmark e-mail application by default. - | - */ - - 'port' => 587, - - /* - |-------------------------------------------------------------------------- - | Global "From" Address - |-------------------------------------------------------------------------- - | - | You may wish for all e-mails sent by your application to be sent from - | the same address. Here, you may specify a name and address that is - | used globally for all e-mails that are sent by your application. - | - */ - - 'from' => array('address' => null, 'name' => null), - - /* - |-------------------------------------------------------------------------- - | E-Mail Encryption Protocol - |-------------------------------------------------------------------------- - | - | Here you may specify the encryption protocol that should be used when - | the application send e-mail messages. A sensible default using the - | transport layer security protocol should provide great security. - | - */ - - 'encryption' => 'tls', - - /* - |-------------------------------------------------------------------------- - | SMTP Server Username - |-------------------------------------------------------------------------- - | - | If your SMTP server requires a username for authentication, you should - | set it here. This will get used to authenticate with your server on - | connection. You may also set the "password" value below this one. - | - */ - - 'username' => null, - - /* - |-------------------------------------------------------------------------- - | SMTP Server Password - |-------------------------------------------------------------------------- - | - | Here you may set the password required by your SMTP server to send out - | messages from your application. This will be given to the server on - | connection so that the application will be able to send messages. - | - */ - - 'password' => null, - - /* - |-------------------------------------------------------------------------- - | Sendmail System Path - |-------------------------------------------------------------------------- - | - | When using the "sendmail" driver to send e-mails, we will need to know - | the path to where Sendmail lives on this server. A default path has - | been provided here, which will work well on most of your systems. - | - */ - - 'sendmail' => '/usr/sbin/sendmail -bs', - - /* - |-------------------------------------------------------------------------- - | Mail "Pretend" - |-------------------------------------------------------------------------- - | - | When this option is enabled, e-mail will not actually be sent over the - | web and will instead be written to your application's logs files so - | you may inspect the message. This is great for local development. - | - */ - - 'pretend' => true, - -); \ No newline at end of file diff --git a/app/config/local/app.php b/app/config/local/app.php deleted file mode 100644 index a7f7d174..00000000 --- a/app/config/local/app.php +++ /dev/null @@ -1,187 +0,0 @@ - true, - - /* - |-------------------------------------------------------------------------- - | Application URL - |-------------------------------------------------------------------------- - | - | This URL is used by the console to properly generate URLs when using - | the Artisan command line tool. You should set this to the root of - | your application so that it is used when running Artisan tasks. - | - */ - - 'url' => 'https://local.openstackid.openstack.org', - - /* - |-------------------------------------------------------------------------- - | Application Timezone - |-------------------------------------------------------------------------- - | - | Here you may specify the default timezone for your application, which - | will be used by the PHP date and date-time functions. We have gone - | ahead and set this to a sensible default for you out of the box. - | - */ - - 'timezone' => 'UTC', - - /* - |-------------------------------------------------------------------------- - | Application Locale Configuration - |-------------------------------------------------------------------------- - | - | The application locale determines the default locale that will be used - | by the translation service provider. You are free to set this value - | to any of the locales which will be supported by the application. - | - */ - - 'locale' => 'en', - - /* - |-------------------------------------------------------------------------- - | Encryption Key - |-------------------------------------------------------------------------- - | - | This key is used by the Illuminate encrypter service and should be set - | to a random, 32 character string, otherwise these encrypted strings - | will not be safe. Please do this before deploying an application! - | - */ - - 'key' => '2mikPhsqqnw9qfEtp3XxHmtkZqoTTvWl', - - /* - |-------------------------------------------------------------------------- - | Autoloaded Service Providers - |-------------------------------------------------------------------------- - | - | The service providers listed here will be automatically loaded on the - | request to your application. Feel free to add your own services to - | this array to grant expanded functionality to your applications. - | - */ - - 'providers' => array( - 'Illuminate\Foundation\Providers\ArtisanServiceProvider', - 'Illuminate\Auth\AuthServiceProvider', - 'Illuminate\Cache\CacheServiceProvider', - 'Illuminate\Session\CommandsServiceProvider', - 'Illuminate\Foundation\Providers\ConsoleSupportServiceProvider', - 'Illuminate\Routing\ControllerServiceProvider', - 'Illuminate\Cookie\CookieServiceProvider', - 'Illuminate\Database\DatabaseServiceProvider', - 'Illuminate\Encryption\EncryptionServiceProvider', - 'Illuminate\Filesystem\FilesystemServiceProvider', - 'Illuminate\Hashing\HashServiceProvider', - 'Illuminate\Html\HtmlServiceProvider', - 'Illuminate\Log\LogServiceProvider', - 'Illuminate\Mail\MailServiceProvider', - 'Illuminate\Database\MigrationServiceProvider', - 'Illuminate\Pagination\PaginationServiceProvider', - 'Illuminate\Queue\QueueServiceProvider', - 'Illuminate\Remote\RemoteServiceProvider', - 'Illuminate\Auth\Reminders\ReminderServiceProvider', - 'Illuminate\Database\SeedServiceProvider', - 'Illuminate\Session\SessionServiceProvider', - 'Illuminate\Translation\TranslationServiceProvider', - 'Illuminate\Validation\ValidationServiceProvider', - 'Illuminate\View\ViewServiceProvider', - 'Illuminate\Workbench\WorkbenchServiceProvider', - 'Illuminate\Redis\RedisServiceProvider', - 'services\utils\UtilsProvider', - 'repositories\RepositoriesProvider', - 'services\oauth2\OAuth2ServiceProvider', - 'services\openid\OpenIdProvider', - 'auth\AuthenticationServiceProvider', - 'services\ServicesProvider', - 'strategies\StrategyProvider', - 'oauth2\OAuth2ServiceProvider', - 'openid\OpenIdServiceProvider', - 'Greggilbert\Recaptcha\RecaptchaServiceProvider', - 'services\oauth2\CORS\CORSProvider', - ), - - /* - |-------------------------------------------------------------------------- - | Service Provider Manifest - |-------------------------------------------------------------------------- - | - | The service provider manifest is used by Laravel to lazy load service - | providers which are not needed for each request, as well to keep a - | list of all of the services. Here, you may set its storage spot. - | - */ - - 'manifest' => storage_path().'/meta', - - /* - |-------------------------------------------------------------------------- - | Class Aliases - |-------------------------------------------------------------------------- - | - | This array of class aliases will be registered when this application - | is started. However, feel free to register as many as you wish as - | the aliases are "lazy" loaded so they don't hinder performance. - | - */ - - 'aliases' => array( - - 'App' => 'Illuminate\Support\Facades\App', - 'Artisan' => 'Illuminate\Support\Facades\Artisan', - 'Auth' => 'Illuminate\Support\Facades\Auth', - 'Blade' => 'Illuminate\Support\Facades\Blade', - 'Cache' => 'Illuminate\Support\Facades\Cache', - 'ClassLoader' => 'Illuminate\Support\ClassLoader', - 'Config' => 'Illuminate\Support\Facades\Config', - 'Controller' => 'Illuminate\Routing\Controller', - 'Cookie' => 'Illuminate\Support\Facades\Cookie', - 'Crypt' => 'Illuminate\Support\Facades\Crypt', - 'DB' => 'Illuminate\Support\Facades\DB', - 'Eloquent' => 'Illuminate\Database\Eloquent\Model', - 'Event' => 'Illuminate\Support\Facades\Event', - 'File' => 'Illuminate\Support\Facades\File', - 'Form' => 'Illuminate\Support\Facades\Form', - 'Hash' => 'Illuminate\Support\Facades\Hash', - 'HTML' => 'Illuminate\Support\Facades\HTML', - 'Input' => 'Illuminate\Support\Facades\Input', - 'Lang' => 'Illuminate\Support\Facades\Lang', - 'Log' => 'Illuminate\Support\Facades\Log', - 'Mail' => 'Illuminate\Support\Facades\Mail', - 'Paginator' => 'Illuminate\Support\Facades\Paginator', - 'Password' => 'Illuminate\Support\Facades\Password', - 'Queue' => 'Illuminate\Support\Facades\Queue', - 'Redirect' => 'Illuminate\Support\Facades\Redirect', - 'Request' => 'Illuminate\Support\Facades\Request', - 'Response' => 'Illuminate\Support\Facades\Response', - 'Route' => 'Illuminate\Support\Facades\Route', - 'Schema' => 'Illuminate\Support\Facades\Schema', - 'Seeder' => 'Illuminate\Database\Seeder', - 'Session' => 'Illuminate\Support\Facades\Session', - 'SSH' => 'Illuminate\Support\Facades\SSH', - 'Str' => 'Illuminate\Support\Str', - 'URL' => 'Illuminate\Support\Facades\URL', - 'Validator' => 'Illuminate\Support\Facades\Validator', - 'View' => 'Illuminate\Support\Facades\View', - 'RedisLV4' => 'Illuminate\Support\Facades\Redis', - - ), - -); diff --git a/app/config/local/mail.php b/app/config/local/mail.php deleted file mode 100644 index ac813553..00000000 --- a/app/config/local/mail.php +++ /dev/null @@ -1,124 +0,0 @@ - 'mail', - - /* - |-------------------------------------------------------------------------- - | SMTP Host Address - |-------------------------------------------------------------------------- - | - | Here you may provide the host address of the SMTP server used by your - | applications. A default option is provided that is compatible with - | the Postmark mail service, which will provide reliable delivery. - | - */ - - 'host' => 'smtp.mailgun.org', - - /* - |-------------------------------------------------------------------------- - | SMTP Host Port - |-------------------------------------------------------------------------- - | - | This is the SMTP port used by your application to delivery e-mails to - | users of your application. Like the host we have set this value to - | stay compatible with the Postmark e-mail application by default. - | - */ - - 'port' => 587, - - /* - |-------------------------------------------------------------------------- - | Global "From" Address - |-------------------------------------------------------------------------- - | - | You may wish for all e-mails sent by your application to be sent from - | the same address. Here, you may specify a name and address that is - | used globally for all e-mails that are sent by your application. - | - */ - - 'from' => array('address' => null, 'name' => null), - - /* - |-------------------------------------------------------------------------- - | E-Mail Encryption Protocol - |-------------------------------------------------------------------------- - | - | Here you may specify the encryption protocol that should be used when - | the application send e-mail messages. A sensible default using the - | transport layer security protocol should provide great security. - | - */ - - 'encryption' => 'tls', - - /* - |-------------------------------------------------------------------------- - | SMTP Server Username - |-------------------------------------------------------------------------- - | - | If your SMTP server requires a username for authentication, you should - | set it here. This will get used to authenticate with your server on - | connection. You may also set the "password" value below this one. - | - */ - - 'username' => null, - - /* - |-------------------------------------------------------------------------- - | SMTP Server Password - |-------------------------------------------------------------------------- - | - | Here you may set the password required by your SMTP server to send out - | messages from your application. This will be given to the server on - | connection so that the application will be able to send messages. - | - */ - - 'password' => null, - - /* - |-------------------------------------------------------------------------- - | Sendmail System Path - |-------------------------------------------------------------------------- - | - | When using the "sendmail" driver to send e-mails, we will need to know - | the path to where Sendmail lives on this server. A default path has - | been provided here, which will work well on most of your systems. - | - */ - - 'sendmail' => '/usr/sbin/sendmail -bs', - - /* - |-------------------------------------------------------------------------- - | Mail "Pretend" - |-------------------------------------------------------------------------- - | - | When this option is enabled, e-mail will not actually be sent over the - | web and will instead be written to your application's logs files so - | you may inspect the message. This is great for local development. - | - */ - - 'pretend' => true, - -); \ No newline at end of file diff --git a/app/config/server.php b/app/config/server.php index 6e2fa4e0..e2c7f322 100644 --- a/app/config/server.php +++ b/app/config/server.php @@ -17,6 +17,7 @@ return array( */ 'BlacklistSecurityPolicy_BannedIpLifeTimeSeconds' => 21600, 'BlacklistSecurityPolicy_MinutesWithoutExceptions' => 5, + 'BlacklistSecurityPolicy_MaxReplayAttackExceptionAttempts' => 3, 'BlacklistSecurityPolicy_ReplayAttackExceptionInitialDelay' => 10, 'BlacklistSecurityPolicy_MaxInvalidNonceAttempts' => 10, 'BlacklistSecurityPolicy_InvalidNonceInitialDelay' => 10, @@ -41,6 +42,8 @@ return array( //oauth2 default config values 'OAuth2_AuthorizationCode_Lifetime' => 240, 'OAuth2_AccessToken_Lifetime' => 3600, + // in seconds , should be equal to session.lifetime (120 minutes) + 'OAuth2_IdToken_Lifetime' => 7200, 'OAuth2_RefreshToken_Lifetime' => 0, 'OAuth2_Enable' => true, //oauth2 security policy configuration @@ -49,4 +52,5 @@ return array( 'OAuth2SecurityPolicy_MaxInvalidClientExceptionAttempts' => 10, 'OAuth2SecurityPolicy_MaxInvalidRedeemAuthCodeAttempts' => 10, 'OAuth2SecurityPolicy_MaxInvalidInvalidClientCredentialsAttempts' => 5, + 'Banning_Enable' => true, ); \ No newline at end of file diff --git a/app/config/testing/session.php b/app/config/testing/session.php index a18c1b9f..d92f8515 100644 --- a/app/config/testing/session.php +++ b/app/config/testing/session.php @@ -16,6 +16,6 @@ return array( | */ - 'driver' => 'array', + 'driver' => 'redis', ); \ No newline at end of file diff --git a/app/controllers/AdminController.php b/app/controllers/AdminController.php index 100b829e..449a35c6 100644 --- a/app/controllers/AdminController.php +++ b/app/controllers/AdminController.php @@ -9,23 +9,64 @@ use oauth2\services\IApiEndpointService; use utils\services\IAuthService; use openid\services\IUserService; use utils\services\IServerConfigurationService; -use \utils\services\IBannedIPService; +use utils\services\IBannedIPService; +use oauth2\repositories\IServerPrivateKeyRepository; +use oauth2\repositories\IApiScopeGroupRepository; +use auth\User; + /** * Class AdminController */ class AdminController extends BaseController { + /** + * @var IClientService + */ private $client_service; + /** + * @var IApiScopeService + */ private $scope_service; + /** + * @var ITokenService + */ private $token_service; + /** + * @var IResourceServerService + */ private $resource_server_service; + /** + * @var IApiService + */ private $api_service; + /** + * @var IApiEndpointService + */ private $endpoint_service; + /** + * @var IAuthService + */ private $auth_service; + /** + * @var IUserService + */ private $user_service; + /** + * @var IServerConfigurationService + */ private $configuration_service; + /** + * @var IBannedIPService + */ private $banned_ips_service; + private $private_keys_repository; + + /** + * @var IApiScopeGroupRepository + */ + private $group_repository; + public function __construct( IClientService $client_service, IApiScopeService $scope_service, ITokenService $token_service, @@ -35,7 +76,10 @@ class AdminController extends BaseController { IAuthService $auth_service, IUserService $user_service, IServerConfigurationService $configuration_service, - IBannedIPService $banned_ips_service){ + IBannedIPService $banned_ips_service, + IServerPrivateKeyRepository $private_keys_repository, + IApiScopeGroupRepository $group_repository) + { $this->client_service = $client_service; $this->scope_service = $scope_service; @@ -47,6 +91,8 @@ class AdminController extends BaseController { $this->user_service = $user_service; $this->configuration_service = $configuration_service; $this->banned_ips_service = $banned_ips_service; + $this->private_keys_repository = $private_keys_repository; + $this->group_repository = $group_repository; } public function editRegisteredClient($id) @@ -59,8 +105,6 @@ class AdminController extends BaseController { return View::make("404"); } - $allowed_uris = $client->getClientRegisteredUris(); - $allowed_origins = $client->getClientAllowedOrigins(); $selected_scopes = $client->getClientScopes(); $aux_scopes = array(); @@ -68,7 +112,8 @@ class AdminController extends BaseController { array_push($aux_scopes, $scope->id); } - $scopes = $this->scope_service->getAvailableScopes($user->canUseSystemScopes()); + $scopes = $this->scope_service->getAvailableScopes(); + $group_scopes = $user->getGroupScopes(); $access_tokens = $this->token_service->getAccessTokenByClient($client->client_id); @@ -86,32 +131,69 @@ class AdminController extends BaseController { return View::make("oauth2.profile.edit-client", array( - 'client' => $client, - 'allowed_uris' => $allowed_uris, - 'allowed_origins' => $allowed_origins, - 'selected_scopes' => $aux_scopes, - 'scopes' => $scopes, - 'access_tokens' => $access_tokens, - "is_oauth2_admin" => $user->isOAuth2ServerAdmin(), + 'client' => $client, + 'selected_scopes' => $aux_scopes, + 'scopes' => array_merge($scopes, $group_scopes), + 'access_tokens' => $access_tokens, + "is_oauth2_admin" => $user->isOAuth2ServerAdmin(), "is_openstackid_admin" => $user->isOpenstackIdAdmin(), - "use_system_scopes" => $user->canUseSystemScopes(), - 'refresh_tokens' => $refresh_tokens, + "use_system_scopes" => $user->canUseSystemScopes(), + 'refresh_tokens' => $refresh_tokens, )); } - public function listResourceServers() { + // Api Scope Groups + + public function listApiScopeGroups() + { + $user = $this->auth_service->getCurrentUser(); + $groups = $this->group_repository->getAll(1,1000); + $non_selected_scopes = $this->scope_service->getAssignedByGroups(); + $non_selected_users = User::where('active', '=', true)->get(); + return View::make("oauth2.profile.admin.api-scope-groups",array + ( + "is_oauth2_admin" => $user->isOAuth2ServerAdmin(), + "is_openstackid_admin" => $user->isOpenstackIdAdmin(), + 'groups' => $groups, + 'non_selected_scopes' => $non_selected_scopes, + 'non_selected_users' => $non_selected_users, + )); + } + + public function editApiScopeGroup($id){ + $group = $this->group_repository->get($id); + + if(is_null($group)) + return Response::view('404', array(), 404); $user = $this->auth_service->getCurrentUser(); + $non_selected_scopes = $this->scope_service->getAssignedByGroups(); + $non_selected_users = User::where('active', '=', true)->get(); + return View::make("oauth2.profile.admin.edit-api-scope-group", + array + ( + 'is_oauth2_admin' => $user->isOAuth2ServerAdmin(), + 'is_openstackid_admin' => $user->isOpenstackIdAdmin(), + 'group' => $group, + 'non_selected_scopes' => $non_selected_scopes, + 'non_selected_users' => $non_selected_users, + ) + ); + } + + // Resource servers + public function listResourceServers() { + $user = $this->auth_service->getCurrentUser(); $resource_servers = $this->resource_server_service->getAll(1,1000); return View::make("oauth2.profile.admin.resource-servers",array( "is_oauth2_admin" => $user->isOAuth2ServerAdmin(), "is_openstackid_admin" => $user->isOpenstackIdAdmin(), - 'resource_servers'=>$resource_servers)); + 'resource_servers' => $resource_servers)); } public function editResourceServer($id){ $resource_server = $this->resource_server_service->get($id); if(is_null($resource_server)) - return View::make('404'); + return Response::view('404', array(), 404); $user = $this->auth_service->getCurrentUser(); return View::make("oauth2.profile.admin.edit-resource-server",array( "is_oauth2_admin" => $user->isOAuth2ServerAdmin(), @@ -123,7 +205,7 @@ class AdminController extends BaseController { public function editApi($id){ $api = $this->api_service->get($id); if(is_null($api)) - return View::make('404'); + return Response::view('404', array(), 404); $user = $this->auth_service->getCurrentUser(); return View::make("oauth2.profile.admin.edit-api",array( "is_oauth2_admin" => $user->isOAuth2ServerAdmin(), @@ -134,7 +216,7 @@ class AdminController extends BaseController { public function editScope($id){ $scope = $this->scope_service->get($id); if(is_null($scope)) - return View::make('404'); + return Response::view('404', array(), 404); $user = $this->auth_service->getCurrentUser(); return View::make("oauth2.profile.admin.edit-scope",array( "is_oauth2_admin" => $user->isOAuth2ServerAdmin(), @@ -145,7 +227,7 @@ class AdminController extends BaseController { public function editEndpoint($id){ $endpoint = $this->endpoint_service->get($id); if(is_null($endpoint)) - return View::make('404'); + return Response::view('404', array(), 404); $user = $this->auth_service->getCurrentUser(); $selected_scopes = array(); $list = $endpoint->scopes()->get(array('id')); @@ -235,31 +317,32 @@ class AdminController extends BaseController { )); } - - public function listServerConfig(){ $user = $this->auth_service->getCurrentUser(); $config_values = array(); - $config_values['MaxFailed.Login.Attempts'] = $this->configuration_service->getConfigValue('MaxFailed.Login.Attempts'); + $config_values['MaxFailed.Login.Attempts'] = $this->configuration_service->getConfigValue('MaxFailed.Login.Attempts'); $config_values['MaxFailed.LoginAttempts.2ShowCaptcha'] = $this->configuration_service->getConfigValue('MaxFailed.LoginAttempts.2ShowCaptcha'); - $config_values['OpenId.Private.Association.Lifetime'] = $this->configuration_service->getConfigValue('OpenId.Private.Association.Lifetime'); - $config_values['OpenId.Session.Association.Lifetime'] = $this->configuration_service->getConfigValue('OpenId.Session.Association.Lifetime'); - $config_values['OpenId.Nonce.Lifetime'] = $this->configuration_service->getConfigValue('OpenId.Nonce.Lifetime'); + $config_values['OpenId.Private.Association.Lifetime'] = $this->configuration_service->getConfigValue('OpenId.Private.Association.Lifetime'); + $config_values['OpenId.Session.Association.Lifetime'] = $this->configuration_service->getConfigValue('OpenId.Session.Association.Lifetime'); + $config_values['OpenId.Nonce.Lifetime'] = $this->configuration_service->getConfigValue('OpenId.Nonce.Lifetime'); - $config_values['OAuth2.AuthorizationCode.Lifetime'] = $this->configuration_service->getConfigValue('OAuth2.AuthorizationCode.Lifetime'); - $config_values['OAuth2.AccessToken.Lifetime'] = $this->configuration_service->getConfigValue('OAuth2.AccessToken.Lifetime'); - $config_values['OAuth2.RefreshToken.Lifetime'] = $this->configuration_service->getConfigValue('OAuth2.RefreshToken.Lifetime'); + $config_values['OAuth2.AuthorizationCode.Lifetime'] = $this->configuration_service->getConfigValue('OAuth2.AuthorizationCode.Lifetime'); + $config_values['OAuth2.AccessToken.Lifetime'] = $this->configuration_service->getConfigValue('OAuth2.AccessToken.Lifetime'); + $config_values['OAuth2.IdToken.Lifetime'] = $this->configuration_service->getConfigValue('OAuth2.IdToken.Lifetime'); + $config_values['OAuth2.RefreshToken.Lifetime'] = $this->configuration_service->getConfigValue('OAuth2.RefreshToken.Lifetime'); - return View::make("admin.server-config", array( - "username" => $user->getFullName(), - "user_id" => $user->getId(), - "is_oauth2_admin" => $user->isOAuth2ServerAdmin(), - "is_openstackid_admin" => $user->isOpenstackIdAdmin(), - 'config_values' => $config_values, - )); + return View::make("admin.server-config", array + ( + "username" => $user->getFullName(), + "user_id" => $user->getId(), + "is_oauth2_admin" => $user->isOAuth2ServerAdmin(), + "is_openstackid_admin" => $user->isOpenstackIdAdmin(), + 'config_values' => $config_values, + ) + ); } public function saveServerConfig(){ @@ -275,6 +358,7 @@ class AdminController extends BaseController { 'oauth2-auth-code-lifetime' => 'required|integer', 'oauth2-refresh-token-lifetime' => 'required|integer', 'oauth2-access-token-lifetime' => 'required|integer', + 'oauth2-id-token-lifetime' => 'required|integer', ); $dictionary = array( @@ -285,6 +369,7 @@ class AdminController extends BaseController { 'openid-nonce-lifetime' => 'OpenId.Nonce.Lifetime', 'oauth2-auth-code-lifetime' => 'OAuth2.AuthorizationCode.Lifetime', 'oauth2-access-token-lifetime' => 'OAuth2.AccessToken.Lifetime', + 'oauth2-id-token-lifetime' => 'OAuth2.IdToken.Lifetime', 'oauth2-refresh-token-lifetime' => 'OAuth2.RefreshToken.Lifetime', ); @@ -314,4 +399,15 @@ class AdminController extends BaseController { "ips" =>$ips )); } + + public function listServerPrivateKeys(){ + + $user = $this->auth_service->getCurrentUser(); + + return View::make("oauth2.profile.admin.server-private-keys", array( + 'private_keys' => $this->private_keys_repository->getAll(1,4294967296), + "is_oauth2_admin" => $user->isOAuth2ServerAdmin(), + "is_openstackid_admin" => $user->isOpenstackIdAdmin(), + )); + } } \ No newline at end of file diff --git a/app/controllers/apis/AbstractRESTController.php b/app/controllers/apis/AbstractRESTController.php index daa4c726..3375481b 100644 --- a/app/controllers/apis/AbstractRESTController.php +++ b/app/controllers/apis/AbstractRESTController.php @@ -2,23 +2,30 @@ use utils\services\ILogService; -abstract class AbstractRESTController extends JsonController { +/** + * Class AbstractRESTController + */ +abstract class AbstractRESTController extends JsonController +{ + protected $allowed_filter_fields; protected $allowed_projection_fields; - private $filter_delimiter; - private $field_delimiter; + protected $filter_delimiter; + protected $field_delimiter; - public function __construct(ILogService $log_service){ + public function __construct(ILogService $log_service) + { parent::__construct($log_service); $this->filter_delimiter = '+'; $this->field_delimiter = ','; } - protected function getProjection($fields){ + protected function getProjection($fields) + { if(!is_string($fields)) return array('*'); if(empty($fields)) return array('*'); $fields_args = explode($this->field_delimiter,$fields); @@ -33,7 +40,8 @@ abstract class AbstractRESTController extends JsonController { return $res; } - protected function getFilters($filters){ + protected function getFilters($filters) + { if(!is_array($filters)) return array(); $res = array(); foreach($filters as $fieldname=>$value){ diff --git a/app/controllers/apis/ApiResourceServerController.php b/app/controllers/apis/ApiResourceServerController.php index 78e0c79e..9400744f 100644 --- a/app/controllers/apis/ApiResourceServerController.php +++ b/app/controllers/apis/ApiResourceServerController.php @@ -1,8 +1,9 @@ resource_server_service = $resource_server_service; - $this->allowed_filter_fields = array(''); + $this->allowed_filter_fields = array(''); $this->allowed_projection_fields = array('*'); } @@ -29,18 +30,20 @@ class ApiResourceServerController extends AbstractRESTController implements ICRU return $this->error404(array('error' => 'resource server not found')); } - $data = $resource_server->toArray(); - $apis = $resource_server->apis()->get(array('id','name')); + $data = $resource_server->toArray(); + $apis = $resource_server->apis()->get(array('id', 'name')); $data['apis'] = $apis->toArray(); - $client = $resource_server->getClient(); - if(!is_null($client)){ - $data['client_id'] = $client->getClientId(); - $data['client_secret'] = $client->getClientSecret(); + $client = $resource_server->getClient(); + if (!is_null($client)) { + $data['client_id'] = $client->getClientId(); + $data['client_secret'] = $client->getClientSecret(); } + return $this->ok($data); } catch (Exception $ex) { $this->log_service->error($ex); + return $this->error500($ex); } } @@ -48,22 +51,24 @@ class ApiResourceServerController extends AbstractRESTController implements ICRU public function getByPage() { try { - $fields = $this->getProjection(Input::get('fields',null)); - $filters = $this->getFilters(Input::except('fields','limit','offset')); - $page_nbr = intval(Input::get('offset',1)); - $page_size = intval(Input::get('limit',10)); + $fields = $this->getProjection(Input::get('fields', null)); + $filters = $this->getFilters(Input::except('fields', 'limit', 'offset')); + $page_nbr = intval(Input::get('offset', 1)); + $page_size = intval(Input::get('limit', 10)); - $list = $this->resource_server_service->getAll($page_nbr, $page_size,$filters,$fields); + $list = $this->resource_server_service->getAll($page_nbr, $page_size, $filters, $fields); $items = array(); foreach ($list->getItems() as $rs) { array_push($items, $rs->toArray()); } - return $this->ok( array( + + return $this->ok(array( 'page' => $items, 'total_items' => $list->getTotal() )); } catch (Exception $ex) { $this->log_service->error($ex); + return $this->error500($ex); } } @@ -74,17 +79,18 @@ class ApiResourceServerController extends AbstractRESTController implements ICRU $values = Input::all(); $rules = array( - 'host' => 'required|host|max:255', - 'ip' => 'required|ip|max:16', + 'host' => 'required|host|max:255', + 'ip' => 'required|ip|max:16', 'friendly_name' => 'required|text|max:512', - 'active' => 'required|boolean', + 'active' => 'required|boolean', ); // Creates a Validator instance and validates the data. $validation = Validator::make($values, $rules); if ($validation->fails()) { $messages = $validation->messages()->toArray(); - return $this->error400(array('error'=>'validation','messages' => $messages)); + + return $this->error400(array('error' => 'validation', 'messages' => $messages)); } $new_resource_server_model = $this->resource_server_service->add( @@ -94,13 +100,13 @@ class ApiResourceServerController extends AbstractRESTController implements ICRU $values['active']); return $this->created(array('resource_server_id' => $new_resource_server_model->id)); - } - catch(InvalidResourceServer $ex1){ + } catch (InvalidResourceServer $ex1) { $this->log_service->error($ex1); - return $this->error400(array('error'=>$ex1->getMessage())); - } - catch (Exception $ex) { + + return $this->error400(array('error' => $ex1->getMessage())); + } catch (Exception $ex) { $this->log_service->error($ex); + return $this->error500($ex); } } @@ -109,9 +115,11 @@ class ApiResourceServerController extends AbstractRESTController implements ICRU { try { $res = $this->resource_server_service->delete($id); - return $res?$this->deleted():$this->error404(array('error'=>'operation failed')); + + return $res ? $this->deleted() : $this->error404(array('error' => 'operation failed')); } catch (Exception $ex) { $this->log_service->error($ex); + return $this->error500($ex); } } @@ -120,9 +128,11 @@ class ApiResourceServerController extends AbstractRESTController implements ICRU { try { $res = $this->resource_server_service->regenerateClientSecret($id); - return !is_null($res)?$this->ok(array('new_secret'=>$res)):$this->error404(array('error'=>'operation failed')); + + return !is_null($res) ? $this->ok(array('new_secret' => $res)) : $this->error404(array('error' => 'operation failed')); } catch (Exception $ex) { $this->log_service->error($ex); + return $this->error500($ex); } } @@ -134,48 +144,55 @@ class ApiResourceServerController extends AbstractRESTController implements ICRU $values = Input::all(); $rules = array( - 'id' => 'required|integer', - 'host' => 'sometimes|required|host|max:255', - 'ip' => 'sometimes|required|ip|max:16', + 'id' => 'required|integer', + 'host' => 'sometimes|required|host|max:255', + 'ip' => 'sometimes|required|ip|max:16', 'friendly_name' => 'sometimes|required|text|max:512', ); // Creates a Validator instance and validates the data. $validation = Validator::make($values, $rules); if ($validation->fails()) { $messages = $validation->messages()->toArray(); - return $this->error400(array('error'=>'validation','messages' => $messages)); + + return $this->error400(array('error' => 'validation', 'messages' => $messages)); } - $res = $this->resource_server_service->update(intval($values['id']),$values); - return $res?$this->ok():$this->error400(array('error'=>'operation failed')); - } - catch(InvalidResourceServer $ex1){ + $res = $this->resource_server_service->update(intval($values['id']), $values); + + return $res ? $this->ok() : $this->error400(array('error' => 'operation failed')); + } catch (InvalidResourceServer $ex1) { $this->log_service->error($ex1); - return $this->error404(array('error'=>$ex1->getMessage())); - } - catch (Exception $ex) { - $this->log_service->error($ex); - return $this->error500($ex); - } - } - - public function activate($id){ - try { - $res = $this->resource_server_service->setStatus($id,true); - return $res?$this->ok():$this->error400(array('error'=>'operation failed')); + return $this->error404(array('error' => $ex1->getMessage())); } catch (Exception $ex) { $this->log_service->error($ex); + return $this->error500($ex); } } - public function deactivate($id){ - try { - $res = $this->resource_server_service->setStatus($id,false); - return $res?$this->ok():$this->error400(array('error'=>'operation failed')); - } catch (Exception $ex) { - $this->log_service->error($ex); - return $this->error500($ex); - } - } + public function activate($id) + { + try { + $res = $this->resource_server_service->setStatus($id, true); + + return $res ? $this->ok() : $this->error400(array('error' => 'operation failed')); + } catch (Exception $ex) { + $this->log_service->error($ex); + + return $this->error500($ex); + } + } + + public function deactivate($id) + { + try { + $res = $this->resource_server_service->setStatus($id, false); + + return $res ? $this->ok() : $this->error400(array('error' => 'operation failed')); + } catch (Exception $ex) { + $this->log_service->error($ex); + + return $this->error500($ex); + } + } } \ No newline at end of file diff --git a/app/controllers/apis/ApiScopeController.php b/app/controllers/apis/ApiScopeController.php index 78cb2f0f..306290b0 100644 --- a/app/controllers/apis/ApiScopeController.php +++ b/app/controllers/apis/ApiScopeController.php @@ -10,6 +10,9 @@ use oauth2\exceptions\InvalidApiScope; */ class ApiScopeController extends AbstractRESTController implements ICRUDController { + /** + * @var IApiScopeService + */ private $api_scope_service; public function __construct(IApiScopeService $api_scope_service, ILogService $log_service) @@ -74,6 +77,7 @@ class ApiScopeController extends AbstractRESTController implements ICRUDControll 'default' => 'required|boolean', 'system' => 'required|boolean', 'api_id' => 'required|integer', + 'assigned_by_groups' => 'required|boolean', ); // Creates a Validator instance and validates the data. @@ -91,7 +95,8 @@ class ApiScopeController extends AbstractRESTController implements ICRUDControll $values['active'], $values['default'], $values['system'], - $values['api_id'] + $values['api_id'], + $values['assigned_by_groups'] ); return $this->created(array('scope_id' => $new_scope->id)); @@ -140,6 +145,7 @@ class ApiScopeController extends AbstractRESTController implements ICRUDControll 'active' => 'sometimes|required|boolean', 'system' => 'sometimes|required|boolean', 'default' => 'sometimes|required|boolean', + 'assigned_by_groups' => 'sometimes|boolean', ); // Creates a Validator instance and validates the data. diff --git a/app/controllers/apis/ApiScopeGroupController.php b/app/controllers/apis/ApiScopeGroupController.php new file mode 100644 index 00000000..36842ff3 --- /dev/null +++ b/app/controllers/apis/ApiScopeGroupController.php @@ -0,0 +1,278 @@ +repository = $repository; + $this->user_repository = $user_repository; + $this->scope_service = $scope_service; + $this->service = $service; + $this->allowed_filter_fields = array(''); + $this->allowed_projection_fields = array('*'); + } + + /** + * @param $id + * @return mixed + */ + public function get($id) + { + // TODO: Implement get() method. + } + + /** + * @return mixed + */ + public function create() + { + try + { + $values = Input::all(); + + $rules = array + ( + 'name' => 'required|text|max:512', + 'active' => 'required|boolean', + 'scopes' => 'required', + 'users' => 'required', + ); + // Creates a Validator instance and validates the data. + $validation = Validator::make($values, $rules); + + if ($validation->fails()) { + $messages = $validation->messages()->toArray(); + + return $this->error400(array('error' => 'validation', 'messages' => $messages)); + } + + $new_group = $this->service->register + ( + $values['name'], + $values['active'], + $values['scopes'], + $values['users'] + ); + + return $this->created(array('group_id' => $new_group->id)); + } catch (InvalidApiScopeGroup $ex1) { + $this->log_service->error($ex1); + + return $this->error400(array('error' => $ex1->getMessage())); + } catch (Exception $ex) { + $this->log_service->error($ex); + + return $this->error500($ex); + } + } + + /** + * @return mixed + */ + public function getByPage() + { + try + { + $fields = $this->getProjection(Input::get('fields', null)); + $filters = $this->getFilters(Input::except('fields', 'limit', 'offset')); + $page_nbr = intval(Input::get('offset', 1)); + $page_size = intval(Input::get('limit', 10)); + + $list = $this->repository->getAll($page_nbr, $page_size, $filters, $fields); + $items = array(); + + foreach ($list->getItems() as $g) + { + array_push($items, $g->toArray()); + } + + return $this->ok( + array + ( + 'page' => $items, + 'total_items' => $list->getTotal() + ) + ); + } catch (Exception $ex) { + $this->log_service->error($ex); + return $this->error500($ex); + } + } + + /** + * @param $id + * @return mixed + */ + public function delete($id) + { + try { + $group = $this->repository->get(intval($id)); + if(is_null($group)) return $this->error404(); + foreach($group->users()->get() as $user) + { + foreach($user->clients()->get() as $client) + { + foreach($group->scopes()->get() as $scope) + $client->scopes()->detach(intval($scope->id)); + } + } + $this->repository->delete($group); + return $this->deleted(); + } + catch (Exception $ex) + { + $this->log_service->error($ex); + + return $this->error500($ex); + } + } + + /** + * @return mixed + */ + public function update() + { + try { + + $values = Input::all(); + + $rules = array + ( + 'id' => 'required|integer', + 'name' => 'required|text|max:512', + 'active' => 'required|boolean', + 'scopes' => 'required', + 'users' => 'required', + ); + // Creates a Validator instance and validates the data. + $validation = Validator::make($values, $rules); + if ($validation->fails()) { + $messages = $validation->messages()->toArray(); + + return $this->error400(array('error' => 'validation', 'messages' => $messages)); + } + + $res = $this->service->update(intval($values['id']), $values); + + return $res ? $this->ok() : $this->error400(array('error' => 'operation failed')); + } + catch (InvalidApiScopeGroup $ex1) + { + $this->log_service->error($ex1); + return $this->error404(array('error' => $ex1->getMessage())); + } + catch (Exception $ex) { + $this->log_service->error($ex); + return $this->error500($ex); + } + } + + public function activate($id){ + try + { + $res = $this->service->setStatus($id, true); + return $res?$this->ok():$this->error400(array('error'=>'operation failed')); + } + catch (Exception $ex) { + $this->log_service->error($ex); + return $this->error500($ex); + } + } + + public function deactivate($id){ + try + { + $res = $this->service->setStatus($id, false); + return $res?$this->ok():$this->error400(array('error'=>'operation failed')); + } + catch (Exception $ex) { + $this->log_service->error($ex); + return $this->error500($ex); + } + } + + public function fetchUsers() + { + $values = Input::all(); + if(!isset($values['t'])) return $this->error404(); + $term = $values['t']; + $users = $this->user_repository->getByEmailOrName($term); + if(count($users) > 0) + { + $list = array(); + foreach($users as $u) + { + array_push($list, array + ( + 'id' => $u->id, + 'value' => sprintf('%s (%s)', $u->getFullName(), $u->getEmail()) + ) + ); + } + return $this->ok($list); + } + return $this->updated(); + } +} \ No newline at end of file diff --git a/app/controllers/apis/AssymetricKeyApiController.php b/app/controllers/apis/AssymetricKeyApiController.php new file mode 100644 index 00000000..6ecb87d6 --- /dev/null +++ b/app/controllers/apis/AssymetricKeyApiController.php @@ -0,0 +1,131 @@ +repository = $repository; + $this->service = $service; + //set filters allowed values + $this->allowed_filter_fields = array('*'); + $this->allowed_projection_fields = array('*'); + } + + /** + * @param $id + * @return mixed + */ + protected function _delete($id) + { + try { + $res = $this->service->delete($id); + + return $res ? $this->deleted() : $this->error404(array('error' => 'operation failed')); + } catch (Exception $ex) { + $this->log_service->error($ex); + return $this->error500($ex); + } + } + + + protected function _update($id) + { + try { + + $values = Input::all(); + + $rules = array( + 'id' => 'required|integer', + 'active' => 'required|boolean', + ); + + // Creates a Validator instance and validates the data. + $validation = Validator::make($values, $rules); + + if ($validation->fails()) { + $messages = $validation->messages()->toArray(); + + return $this->error400(array('error' => 'validation', 'messages' => $messages)); + } + + $res = $this->service->update(intval($id), $values); + + return $res ? $this->ok() : $this->error400(array('error' => 'operation failed')); + + } catch (AbsentClientException $ex1) { + $this->log_service->error($ex1); + + return $this->error404(array('error' => $ex1->getMessage())); + } catch (Exception $ex) { + $this->log_service->error($ex); + return $this->error500($ex); + } + } + + /** + * @return mixed + */ + protected function _getByPage() + { + try { + //check for optional filters param on querystring + $fields = $this->getProjection(Input::get('fields', null)); + $filters = $this->getFilters(Input::except('fields', 'limit', 'offset')); + $page_nbr = intval(Input::get('offset', 1)); + $page_size = intval(Input::get('limit', 10)); + + $list = $this->repository->getAll($page_nbr, $page_size, $filters, $fields); + $items = array(); + foreach ($list->getItems() as $private_key) { + $data = $private_key->toArray(); + $data['sha_256'] = $private_key->getSHA_256_Thumbprint(); + array_push($items, $data); + } + + return $this->ok(array( + 'page' => $items, + 'total_items' => $list->getTotal() + )); + } catch (Exception $ex) { + $this->log_service->error($ex); + + return $this->error500($ex); + } + } + +} \ No newline at end of file diff --git a/app/controllers/apis/ClientApiController.php b/app/controllers/apis/ClientApiController.php index 319181b0..be5a1167 100644 --- a/app/controllers/apis/ClientApiController.php +++ b/app/controllers/apis/ClientApiController.php @@ -1,21 +1,32 @@ client_service = $client_service; - $this->scope_service = $scope_service; - $this->token_service = $token_service; + $this->scope_service = $scope_service; + $this->token_service = $token_service; //set filters allowed values - $this->allowed_filter_fields = array('user_id'); + $this->allowed_filter_fields = array('user_id'); $this->allowed_projection_fields = array('*'); } + public function get($id) + { + try { + $client = $this->client_service->get($id); + if (is_null($client)) + { + return $this->error404(array('error' => 'client not found')); + } + $data = $client->toArray(); + + return $this->ok($data); + } catch (Exception $ex) { + $this->log_service->error($ex); + + return $this->error500($ex); + } + } + /** * Deletes an existing client - * @param $id client id + * @param $id * @return mixed */ public function delete($id) { try { $res = $this->client_service->deleteClientByIdentifier($id); + return $res ? $this->deleted() : $this->error404(array('error' => 'operation failed')); } catch (Exception $ex) { $this->log_service->error($ex); + return $this->error500($ex); } } @@ -65,11 +101,11 @@ class ClientApiController extends AbstractRESTController implements ICRUDControl // Build the validation constraint set. $rules = array( - 'user_id' => 'required|integer', - 'app_name' => 'required|alpha_dash|max:255', - 'app_description' => 'required|freetext', - 'website' => 'required|url', - 'application_type' => 'required|applicationtype', + 'user_id' => 'required|integer', + 'app_name' => 'required|alpha_dash|max:255', + 'app_description' => 'required|freetext', + 'website' => 'url', + 'application_type' => 'required|applicationtype', ); // Create a new validator instance. @@ -77,41 +113,26 @@ class ClientApiController extends AbstractRESTController implements ICRUDControl if ($validation->fails()) { $messages = $validation->messages()->toArray(); - return $this->error400(array('error'=>'validation','messages' => $messages)); + + return $this->error400(array('error' => 'validation', 'messages' => $messages)); } if ($this->client_service->existClientAppName($values['app_name'])) { return $this->error400(array('error' => 'application Name already exists!.')); } - $new_client = $this->client_service->addClient($values['application_type'], intval($values['user_id']), trim($values['app_name']), trim($values['app_description']), trim($values['website'])); + $new_client = $this->client_service->addClient($values['application_type'], intval($values['user_id']), + trim($values['app_name']), trim($values['app_description']), trim($values['website'])); return $this->created(array('client_id' => $new_client->id)); } catch (Exception $ex) { $this->log_service->error($ex); + return $this->error500($ex); } } - /** - * @param $id - * @return mixed - */ - public function get($id) - { - try { - $client = $this->client_service->get($id); - if (is_null($client)) { - return $this->error404(array('error' => 'client not found')); - } - $data = $client->toArray(); - return $this->ok($data); - } catch (Exception $ex) { - $this->log_service->error($ex); - return $this->error500($ex); - } - } /** * @return mixed @@ -120,24 +141,26 @@ class ClientApiController extends AbstractRESTController implements ICRUDControl { try { //check for optional filters param on querystring - $fields = $this->getProjection(Input::get('fields',null)); - $filters = $this->getFilters(Input::except('fields','limit','offset')); - $page_nbr = intval(Input::get('offset',1)); - $page_size = intval(Input::get('limit',10)); + $fields = $this->getProjection(Input::get('fields', null)); + $filters = $this->getFilters(Input::except('fields', 'limit', 'offset')); + $page_nbr = intval(Input::get('offset', 1)); + $page_size = intval(Input::get('limit', 10)); - $list = $this->client_service->getAll($page_nbr, $page_size,$filters,$fields); + $list = $this->client_service->getAll($page_nbr, $page_size, $filters, $fields); $items = array(); foreach ($list->getItems() as $client) { $data = $client->toArray(); $data['application_type'] = $client->getFriendlyApplicationType(); array_push($items, $data); } + return $this->ok(array( 'page' => $items, 'total_items' => $list->getTotal() )); } catch (Exception $ex) { $this->log_service->error($ex); + return $this->error500($ex); } } @@ -152,14 +175,38 @@ class ClientApiController extends AbstractRESTController implements ICRUDControl $values = Input::all(); $rules = array( - 'id' => 'required|integer', - 'app_name' => 'sometimes|required|alpha_dash|max:255', - 'app_description' => 'sometimes|required|freetext', - 'website' => 'sometimes|required|url', - 'active' => 'sometimes|required|boolean', - 'locked' => 'sometimes|required|boolean', - 'use_refresh_token' => 'sometimes|required|boolean', + 'id' => 'required|integer', + 'application_type' =>'required|application_type', + 'app_name' => 'sometimes|required|alpha_dash|max:255', + 'app_description' => 'sometimes|required|freetext', + 'website' => 'url', + 'active' => 'sometimes|required|boolean', + 'locked' => 'sometimes|required|boolean', + 'use_refresh_token' => 'sometimes|required|boolean', 'rotate_refresh_token' => 'sometimes|required|boolean', + 'contacts' => 'email_set', + 'logo_uri' => 'url', + 'tos_uri' => 'url', + 'redirect_uris' => 'custom_url_set:application_type', + 'post_logout_redirect_uris' => 'ssl_url_set', + 'allowed_origins' => 'ssl_url_set', + 'logout_uri' => 'url', + 'logout_session_required' => 'sometimes|required|boolean', + 'logout_use_iframe' => 'sometimes|required|boolean', + 'policy_uri' => 'url', + 'jwks_uri' => 'url', + 'default_max_age' => 'sometimes|required|integer', + 'logout_use_iframe' => 'sometimes|required|boolean', + 'require_auth_time' => 'sometimes|required|boolean', + 'token_endpoint_auth_method' => 'sometimes|required|token_endpoint_auth_method', + 'token_endpoint_auth_signing_alg' => 'sometimes|required|signing_alg', + 'subject_type' => 'sometimes|required|subject_type', + 'userinfo_signed_response_alg' => 'sometimes|required|signing_alg', + 'userinfo_encrypted_response_alg' => 'sometimes|required|encrypted_alg', + 'userinfo_encrypted_response_enc' => 'sometimes|required|encrypted_enc', + 'id_token_signed_response_alg' => 'sometimes|required|signing_alg', + 'id_token_encrypted_response_alg' => 'sometimes|required|encrypted_alg', + 'id_token_encrypted_response_enc' => 'sometimes|required|encrypted_enc', ); // Creates a Validator instance and validates the data. @@ -167,115 +214,70 @@ class ClientApiController extends AbstractRESTController implements ICRUDControl if ($validation->fails()) { $messages = $validation->messages()->toArray(); - return $this->error400(array('error'=>'validation','messages' => $messages)); + + return $this->error400(array('error' => 'validation', 'messages' => $messages)); } $res = $this->client_service->update(intval($values['id']), $values); return $res ? $this->ok() : $this->error400(array('error' => 'operation failed')); - } catch (AbsentClientException $ex1) { + } + catch (AbsentClientException $ex1) + { $this->log_service->error($ex1); return $this->error404(array('error' => $ex1->getMessage())); - } catch (Exception $ex) { - $this->log_service->error($ex); - return $this->error500($ex); } - } - - /** - * @param $id - * @return mixed - */ - public function getRegisteredUris($id) - { - try { - $client = $this->client_service->getClientByIdentifier($id); - $allowed_uris = $client->authorized_uris()->get(array('id', 'uri')); - - $data = array(); - foreach ($allowed_uris as $uri) { - array_push($data, $uri->toArray()); - } - - return $this->ok(array('allowed_uris' => $data)); - } catch (Exception $ex) { - $this->log_service->error($ex); - return $this->error500($ex); - } - } - - /** - * @param $id - * @return mixed - */ - public function addAllowedRedirectUri($id) - { - try { - $values = Input::All(); - // Build the validation constraint set. - $rules = array( - 'redirect_uri' => 'sslurl|required', - ); - // Creates a Validator instance and validates the data. - $validation = Validator::make($values, $rules); - if ($validation->fails()) { - $messages = $validation->messages()->toArray(); - return $this->error400(array('error'=>'validation','messages' => $messages)); - } - $res = $this->client_service->addClientAllowedUri($id, $values['redirect_uri']); - return $res ? $this->ok(): $this->error404(array('error' => 'operation failed')); - } catch (AllowedClientUriAlreadyExistsException $ex1) { - $this->log_service->error($ex1); - return $this->error400(array('error' => $ex1->getMessage())); - } catch (AbsentClientException $ex2) { + catch(ValidationException $ex2) + { $this->log_service->error($ex2); - return $this->error404(array('error' => $ex2->getMessage())); - } catch (Exception $ex) { + return $this->error412(array($ex2->getMessage())); + } + catch (Exception $ex) { $this->log_service->error($ex); + return $this->error500($ex); } } - /** - * @param $id - * @param $uri_id - * @return mixed - */ - public function deleteClientAllowedUri($id, $uri_id) + + public function addAllowedScope($id, $scope_id) { - try { - $res = $this->client_service->deleteClientAllowedUri($id, $uri_id); - return $res ? $this->ok() : $this->error404(array('error' => 'operation failed')); - } catch (Exception $ex) { - $this->log_service->error($ex); - return $this->error500($ex); - } - } - - - public function addAllowedScope($id,$scope_id){ - try { + try + { $this->client_service->addClientScope($id, $scope_id); return $this->ok(); - } catch (AbsentClientException $ex1) { + } + catch (EntityNotFoundException $ex1) + { $this->log_service->error($ex1); return $this->error404(array('error' => $ex1->getMessage())); - } catch (Exception $ex) { + } + catch (InvalidApiScope $ex2) + { + $this->log_service->error($ex2); + return $this->error412(array('messages' => $ex2->getMessage())); + } + catch (Exception $ex) + { $this->log_service->error($ex); return $this->error500($ex); } } - public function removeAllowedScope($id,$scope_id){ - try { + public function removeAllowedScope($id, $scope_id) + { + try + { $res = $this->client_service->deleteClientScope($id, $scope_id); return $res ? $this->ok() : $this->error404(array('error' => 'operation failed')); } catch (AbsentClientException $ex1) { $this->log_service->error($ex1); + return $this->error404(array('error' => $ex1->getMessage())); } catch (Exception $ex) { $this->log_service->error($ex); + return $this->error500($ex); } } @@ -285,39 +287,56 @@ class ClientApiController extends AbstractRESTController implements ICRUDControl { try { $res = $this->client_service->activateClient($id, true); + return $res ? $this->ok() : $this->error404(array('error' => 'operation failed')); } catch (AbsentClientException $ex1) { $this->log_service->error($ex1); + return $this->error404(array('error' => $ex1->getMessage())); } catch (Exception $ex) { $this->log_service->error($ex); + return $this->error500($ex); } } - public function deactivate($id) - { - try { - $res = $this->client_service->activateClient($id, false); - return $res ? $this->ok() : $this->error404(array('error' => 'operation failed')); - } catch (AbsentClientException $ex1) { - $this->log_service->error($ex1); - return $this->error404(array('error' => $ex1->getMessage())); - } catch (Exception $ex) { - $this->log_service->error($ex); - return $this->error500($ex); - } - } - - - public function regenerateClientSecret($id) + public function deactivate($id) { try { - $res = $this->client_service->regenerateClientSecret($id); - return !empty($res) ? - $this->ok(array('new_secret' => $res)): $this->error404(array('error' => 'operation failed')); + $res = $this->client_service->activateClient($id, false); + + return $res ? $this->ok() : $this->error404(array('error' => 'operation failed')); + } catch (AbsentClientException $ex1) { + $this->log_service->error($ex1); + + return $this->error404(array('error' => $ex1->getMessage())); } catch (Exception $ex) { $this->log_service->error($ex); + + return $this->error500($ex); + } + } + + + public function regenerateClientSecret($id) + { + try + { + $client = $this->client_service->regenerateClientSecret($id); + + return !is_null($client) ? + $this->ok + ( + array + ( + 'new_secret' => $client->getClientSecret(), + 'new_expiration_date' => $client->getClientSecretExpiration(), + ) + ) : $this->error404(array('error' => 'operation failed')); + } + catch (Exception $ex) + { + $this->log_service->error($ex); return $this->error500($ex); } } @@ -336,7 +355,8 @@ class ClientApiController extends AbstractRESTController implements ICRUDControl $validation = Validator::make($values, $rules); if ($validation->fails()) { $messages = $validation->messages()->toArray(); - return $this->error400(array('error'=>'validation','messages' => $messages)); + + return $this->error400(array('error' => 'validation', 'messages' => $messages)); } $res = $this->client_service->setRefreshTokenUsage($id, $values['use_refresh_token']); @@ -345,9 +365,11 @@ class ClientApiController extends AbstractRESTController implements ICRUDControl } catch (AbsentClientException $ex1) { $this->log_service->error($ex1); + return $this->error404(array('error' => $ex1->getMessage())); } catch (Exception $ex) { $this->log_service->error($ex); + return $this->error500($ex); } } @@ -365,16 +387,20 @@ class ClientApiController extends AbstractRESTController implements ICRUDControl $validation = Validator::make($values, $rules); if ($validation->fails()) { $messages = $validation->messages()->toArray(); - return $this->error400(array('error'=>'validation','messages' => $messages)); + + return $this->error400(array('error' => 'validation', 'messages' => $messages)); } $res = $this->client_service->setRotateRefreshTokenPolicy($id, $values['rotate_refresh_token']); + return $res ? $this->ok() : $this->error404(array('error' => 'operation failed')); } catch (AbsentClientException $ex1) { $this->log_service->error($ex1); + return $this->error404(array('error' => $ex1->getMessage())); } catch (Exception $ex) { $this->log_service->error($ex); + return $this->error500($ex); } } @@ -385,26 +411,28 @@ class ClientApiController extends AbstractRESTController implements ICRUDControl $res = false; $client = $this->client_service->getClientByIdentifier($id); switch ($hint) { - case 'access-token': - { - $token = $this->token_service->getAccessToken($value,true); + case 'access-token': { + $token = $this->token_service->getAccessToken($value, true); if (is_null($token)) { return $this->error404(array('error' => sprintf('access token %s does not exists!', $value))); } if ($token->getClientId() !== $client->client_id) { - return $this->error404(array('error' => sprintf('access token %s does not belongs to client id !', $value, $id))); + return $this->error404(array( + 'error' => sprintf('access token %s does not belongs to client id !', $value, $id) + )); } $res = $this->token_service->revokeAccessToken($value, true); } break; - case 'refresh-token': - { - $token = $this->token_service->getRefreshToken($value,true); + case 'refresh-token': { + $token = $this->token_service->getRefreshToken($value, true); if (is_null($token)) { return $this->error404(array('error' => sprintf('refresh token %s does not exists!', $value))); } if ($token->getClientId() !== $client->client_id) { - return $this->error404(array('error' => sprintf('refresh token %s does not belongs to client id !', $value, $id))); + return $this->error404(array( + 'error' => sprintf('refresh token %s does not belongs to client id !', $value, $id) + )); } $res = $this->token_service->revokeRefreshToken($value, true); } @@ -416,6 +444,7 @@ class ClientApiController extends AbstractRESTController implements ICRUDControl return $res ? $this->ok() : $this->error404(array('error' => 'operation failed')); } catch (Exception $ex) { $this->log_service->error($ex); + return $this->error500($ex); } } @@ -427,17 +456,18 @@ class ClientApiController extends AbstractRESTController implements ICRUDControl $access_tokens = $this->token_service->getAccessTokenByClient($client->client_id); $res = array(); foreach ($access_tokens as $token) { - $friendly_scopes = $this->scope_service->getFriendlyScopesByName(explode(' ', $token->scope)); array_push($res, array( - 'value' => $token->value, - 'scope' => implode(',', $friendly_scopes), + 'value' => $token->value, + 'scope' => $token->scope, 'lifetime' => $token->getRemainingLifetime(), - 'issued' => $token->created_at->format('Y-m-d H:i:s') + 'issued' => $token->created_at->format('Y-m-d H:i:s') )); } + return $this->ok(array('access_tokens' => $res)); } catch (Exception $ex) { $this->log_service->error($ex); + return $this->error500($ex); } } @@ -449,17 +479,18 @@ class ClientApiController extends AbstractRESTController implements ICRUDControl $refresh_tokens = $this->token_service->getRefreshTokenByClient($client->client_id); $res = array(); foreach ($refresh_tokens as $token) { - $friendly_scopes = $this->scope_service->getFriendlyScopesByName(explode(' ', $token->scope)); array_push($res, array( 'value' => $token->value, - 'scope' => implode(',', $friendly_scopes), + 'scope' => $token->scope, 'lifetime' => $token->getRemainingLifetime(), 'issued' => $token->created_at->format('Y-m-d H:i:s') )); } + return $this->ok(array('refresh_tokens' => $res)); } catch (Exception $ex) { $this->log_service->error($ex); + return $this->error500($ex); } } @@ -468,87 +499,19 @@ class ClientApiController extends AbstractRESTController implements ICRUDControl * @param $id * @return mixed */ - public function unlock($id){ + public function unlock($id) + { try { $res = $this->client_service->unlockClient($id); + return $res ? $this->ok() : $this->error404(array('error' => 'operation failed')); - } - catch (AbsentClientException $ex1) { + } catch (AbsentClientException $ex1) { $this->log_service->error($ex1); + return $this->error404(array('error' => $ex1->getMessage())); - } - catch (Exception $ex) { - $this->log_service->error($ex); - return $this->error500($ex); - } - } - - - - /** - * @param $id - * @return mixed - */ - public function geAllowedOrigins($id) - { - try { - $client = $this->client_service->getClientByIdentifier($id); - $allowed_origins = $client->allowed_origins()->get(array('id', 'allowed_origin')); - $data = array(); - foreach ($allowed_origins as $origin) { - array_push($data, $origin->toArray()); - } - return $this->ok(array('allowed_origins' => $data)); } catch (Exception $ex) { $this->log_service->error($ex); - return $this->error500($ex); - } - } - /** - * @param $id - * @return mixed - */ - public function addAllowedOrigin($id) - { - try { - $values = Input::All(); - // Build the validation constraint set. - $rules = array( - 'origin' => 'sslorigin|required', - ); - // Creates a Validator instance and validates the data. - $validation = Validator::make($values, $rules); - if ($validation->fails()) { - $messages = $validation->messages()->toArray(); - return $this->error400(array('error'=>'validation','messages' => $messages)); - } - $res = $this->client_service->addClientAllowedOrigin($id, $values['origin']); - return $res ? $this->ok(): $this->error404(array('error' => 'operation failed')); - } catch (AllowedClientUriAlreadyExistsException $ex1) { - $this->log_service->error($ex1); - return $this->error400(array('error' => $ex1->getMessage())); - } catch (AbsentClientException $ex2) { - $this->log_service->error($ex2); - return $this->error404(array('error' => $ex2->getMessage())); - } catch (Exception $ex) { - $this->log_service->error($ex); - return $this->error500($ex); - } - } - - /** - * @param $id - * @param $origin_id - * @return mixed - */ - public function deleteClientAllowedOrigin($id, $origin_id) - { - try { - $res = $this->client_service->deleteClientAllowedOrigin($id, $origin_id); - return $res ? $this->ok() : $this->error404(array('error' => 'operation failed')); - } catch (Exception $ex) { - $this->log_service->error($ex); return $this->error500($ex); } } diff --git a/app/controllers/apis/ClientPublicKeyApiController.php b/app/controllers/apis/ClientPublicKeyApiController.php new file mode 100644 index 00000000..3a9c1bf5 --- /dev/null +++ b/app/controllers/apis/ClientPublicKeyApiController.php @@ -0,0 +1,154 @@ +error404(); + } + + /** + * @param int $client_id + * @return mixed + */ + public function create($client_id) + { + try + { + + $values = Input::All(); + $values['client_id'] = $client_id; + // Build the validation constraint set. + $rules = array( + 'client_id' => 'required|integer', + 'kid' => 'required|text|max:255', + 'active' => 'required|boolean', + 'valid_from' => 'date_format:m/d/Y', + 'valid_to' => 'date_format:m/d/Y|after:valid_from', + 'pem_content' => 'required|public_key_pem|public_key_pem_length', + 'usage' => 'required|public_key_usage', + 'type' => 'required|public_key_type', + 'alg' => 'required|key_alg:usage', + ); + + // Create a new validator instance. + $validation = Validator::make($values, $rules); + + if ($validation->fails()) + { + $messages = $validation->messages()->toArray(); + return $this->error400(array('error' => 'validation', 'messages' => $messages)); + } + + $public_key = $this->service->register($values); + + return $this->created(array('id' => $public_key->getId())); + + } + catch(ValidationException $ex1) + { + return $this->error400(array('error' => $ex1->getMessage())); + } + catch (Exception $ex) + { + $this->log_service->error($ex); + + return $this->error500($ex); + } + } + + + /** + * @return mixed + */ + public function getByPage($client_id) + { + try { + //check for optional filters param on querystring + $fields = $this->getProjection(Input::get('fields', null)); + $filters = $this->getFilters(Input::except('fields', 'limit', 'offset')); + $page_nbr = intval(Input::get('offset', 1)); + $page_size = intval(Input::get('limit', 10)); + array_push($filters, array + ( + 'name' => 'oauth2_client_id', + 'op' => '=', + 'value' => $client_id + ) + ); + $list = $this->repository->getAll($page_nbr, $page_size, $filters, $fields); + $items = array(); + foreach ($list->getItems() as $private_key) { + $data = $private_key->toArray(); + $data['sha_256'] = $private_key->getSHA_256_Thumbprint(); + array_push($items, $data); + } + + return $this->ok(array( + 'page' => $items, + 'total_items' => $list->getTotal() + )); + } catch (Exception $ex) { + $this->log_service->error($ex); + + return $this->error500($ex); + } + } + + /** + * @param int $client_id + * @param int $public_key_id + * @return mixed + */ + public function update($client_id, $public_key_id) + { + return $this->_update($public_key_id); + } + + /** + * @param int $client_id + * @param int $public_key_id + * @return mixed + */ + public function delete($client_id, $public_key_id){ + return $this->_delete($public_key_id); + } + +} \ No newline at end of file diff --git a/app/controllers/apis/ICRUDController.php b/app/controllers/apis/ICRUDController.php index 51bb25dc..bfc96b6c 100644 --- a/app/controllers/apis/ICRUDController.php +++ b/app/controllers/apis/ICRUDController.php @@ -1,12 +1,35 @@ log_service->error($ex); - return Response::json(array('message' => 'server error'), 500); + return Response::json(array( 'error' => 'server error'), 500); } protected function created($data='ok'){ @@ -27,6 +27,15 @@ abstract class JsonController extends BaseController { return $res; } + protected function updated() + { + $res = Response::json($data, 204); + //jsonp + if(Input::has('callback')) + $res->setCallback(Input::get('callback')); + return $res; + } + protected function deleted($data='ok'){ $res = Response::json($data, 204); //jsonp @@ -35,7 +44,7 @@ abstract class JsonController extends BaseController { return $res; } - protected function ok($data='ok'){ + protected function ok($data = 'ok'){ $res = Response::json($data, 200); //jsonp if(Input::has('callback')) @@ -67,6 +76,6 @@ abstract class JsonController extends BaseController { */ protected function error412($messages){ - return Response::json(array('message' => 'Validation Failed', 'errors' => $messages), 412); + return Response::json(array('error'=>'validation' , 'messages' => $messages), 412); } } \ No newline at end of file diff --git a/app/controllers/apis/ServerPrivateKeyApiController.php b/app/controllers/apis/ServerPrivateKeyApiController.php new file mode 100644 index 00000000..5e3a786c --- /dev/null +++ b/app/controllers/apis/ServerPrivateKeyApiController.php @@ -0,0 +1,110 @@ + 'required|text|min:5|max:255', + 'active' => 'required|boolean', + 'valid_from' => 'date_format:m/d/Y', + 'valid_to' => 'date_format:m/d/Y|after:valid_from', + 'pem_content' => 'sometimes|required|private_key_pem:password|private_key_pem_length:password', + 'usage' => 'required|public_key_usage', + 'type' => 'required|public_key_type', + 'alg' => 'required|key_alg:usage', + 'password' => 'min:5|max:255|private_key_password:pem_content', + ); + + // Create a new validator instance. + $validation = Validator::make($values, $rules); + + if ($validation->fails()) + { + $messages = $validation->messages()->toArray(); + return $this->error400(array('error' => 'validation', 'messages' => $messages)); + } + + $private_key = $this->service->register($values); + + return $this->created(array('id' => $private_key->getId())); + + } + catch(ValidationException $ex1) + { + return $this->error400(array('error' => $ex1->getMessage())); + } + catch (Exception $ex) + { + $this->log_service->error($ex); + + return $this->error500($ex); + } + } + + public function getByPage() + { + return $this->_getByPage(); + } + + /** + * @param int $id + * @return mixed + */ + public function update($id) + { + return $this->_update($id); + } + + /** + * @param int $id + * @return mixed + */ + public function delete($id) + { + return $this->_delete($id); + } + +} \ No newline at end of file diff --git a/app/controllers/apis/protected/OAuth2ProtectedController.php b/app/controllers/apis/protected/OAuth2ProtectedController.php index 3e813285..8ad212ce 100644 --- a/app/controllers/apis/protected/OAuth2ProtectedController.php +++ b/app/controllers/apis/protected/OAuth2ProtectedController.php @@ -7,13 +7,24 @@ use utils\services\ILogService; * Class OAuth2ProtectedController * OAuth2 Protected Base API */ -abstract class OAuth2ProtectedController extends JsonController { +abstract class OAuth2ProtectedController extends JsonController +{ + /** + * @var IResourceServerContext + */ protected $resource_server_context; + /** + * @var + */ protected $repository; - public function __construct(IResourceServerContext $resource_server_context, ILogService $log_service) + public function __construct + ( + IResourceServerContext $resource_server_context, + ILogService $log_service + ) { parent::__construct($log_service); $this->resource_server_context = $resource_server_context; diff --git a/app/controllers/apis/protected/OAuth2UserApiController.php b/app/controllers/apis/protected/OAuth2UserApiController.php index f48894e5..3e9b1c2e 100644 --- a/app/controllers/apis/protected/OAuth2UserApiController.php +++ b/app/controllers/apis/protected/OAuth2UserApiController.php @@ -3,30 +3,111 @@ use oauth2\IResourceServerContext; use utils\services\ILogService; use oauth2\resource_server\IUserService; +use oauth2\services\IClientService; +use oauth2\heuristics\SigningKeyFinder; +use oauth2\heuristics\EncryptionKeyFinder; +use oauth2\builders\IdTokenBuilder; +use utils\http\HttpContentType; /** * Class OAuth2UserApiController * OAUTH2 Protected User REST API */ -class OAuth2UserApiController extends OAuth2ProtectedController { +class OAuth2UserApiController extends OAuth2ProtectedController +{ + /** + * @var IUserService + */ + private $user_service; - public function __construct (IUserService $user_service, IResourceServerContext $resource_server_context, ILogService $log_service){ + /** + * @var IClientService + */ + private $client_service; + + /** + * @var IdTokenBuilder + */ + private $id_token_builder; + + /** + * @param IUserService $user_service + * @param IResourceServerContext $resource_server_context + * @param ILogService $log_service + * @param IClientService $client_service + * @param IdTokenBuilder $id_token_builder + */ + public function __construct + ( + IUserService $user_service, + IResourceServerContext $resource_server_context, + ILogService $log_service, + IClientService $client_service, + IdTokenBuilder $id_token_builder + ) + { parent::__construct($resource_server_context,$log_service); - $this->user_service = $user_service; + + $this->user_service = $user_service; + $this->client_service = $client_service; + $this->id_token_builder = $id_token_builder; } /** * Gets User Basic Info * @return mixed */ - public function me(){ - try{ + public function me() + { + try + { $data = $this->user_service->getCurrentUserInfo(); return $this->ok($data); } - catch(Exception $ex){ + catch(Exception $ex) + { $this->log_service->error($ex); return $this->error500($ex); } } + + public function userInfo() + { + try + { + $claims = $this->user_service->getCurrentUserInfoClaims(); + $client_id = $this->resource_server_context->getCurrentClientId(); + $client = $this->client_service->getClientById($client_id); + + // The UserInfo Claims MUST be returned as the members of a JSON object unless a signed or encrypted response + // was requested during Client Registration. + $user_info_response_info = $client->getUserInfoResponseInfo(); + + $sig_alg = $user_info_response_info->getSigningAlgorithm(); + $enc_alg = $user_info_response_info->getEncryptionKeyAlgorithm(); + $enc = $user_info_response_info->getEncryptionContentAlgorithm(); + + if($sig_alg || ($enc_alg && $enc) ) + { + $jwt = $this->id_token_builder->buildJWT($claims, $user_info_response_info, $client); + $http_response = Response::make($jwt->toCompactSerialization(), 200); + $http_response->header('Content-Type', HttpContentType::JWT); + $http_response->header('Cache-Control','no-cache, no-store, max-age=0, must-revalidate'); + $http_response->header('Pragma','no-cache'); + return $http_response; + } + else + { + // return plain json + return $this->ok( $claims->toArray() ); + } + } + catch(Exception $ex) + { + $this->log_service->error($ex); + return $this->error500($ex); + } + } + + } \ No newline at end of file diff --git a/app/controllers/oauth2/OAuth2ProviderController.php b/app/controllers/oauth2/OAuth2ProviderController.php index c70a6e88..b8ef288b 100644 --- a/app/controllers/oauth2/OAuth2ProviderController.php +++ b/app/controllers/oauth2/OAuth2ProviderController.php @@ -1,56 +1,132 @@ oauth2_protocol = $oauth2_protocol; $this->memento_service = $memento_service; + $this->auth_service = $auth_service; + $this->client_service = $client_service; } /** * Authorize HTTP Endpoint + * The authorization server MUST support the use of the HTTP "GET" + * method [RFC2616] for the authorization endpoint and MAY support the + * use of the "POST" method as well. * @return mixed */ - public function authorize(){ - $request = $this->memento_service->getCurrentAuthorizationRequest(); - $response = $this->oauth2_protocol->authorize($request); - $reflector = new ReflectionClass($response); - if ($reflector->isSubclassOf('oauth2\\responses\\OAuth2Response')) { - $strategy = OAuth2ResponseStrategyFactoryMethod::buildStrategy($response); - return $strategy->handle($response); + public function authorize() + { + try + { + $msg = new OAuth2Message(Input::all()); + + if ($this->memento_service->exists()) { + $msg = OAuth2Message::buildFromMemento($this->memento_service->load()); + } + + $request = OAuth2AuthorizationRequestFactory::getInstance()->build($msg); + + $response = $this->oauth2_protocol->authorize($request); + + if ($response instanceof OAuth2Response) { + $strategy = OAuth2ResponseStrategyFactoryMethod::buildStrategy($request, $response); + + return $strategy->handle($response); + } + + return $response; + } + catch(UriNotAllowedException $ex1) + { + return Response::view + ( + '400', + array + ( + 'error_code' => $ex1->getError(), + 'error_description' => $ex1->getMessage() + ), + 400 + ); } - return $response; } /** * Token HTTP Endpoint * @return mixed */ - public function token(){ - $response = $this->oauth2_protocol->token(new OAuth2TokenRequest(new OAuth2Message(Input::all()))); - $reflector = new ReflectionClass($response); - if ($reflector->isSubclassOf('oauth2\\responses\\OAuth2Response')) { - $strategy = OAuth2ResponseStrategyFactoryMethod::buildStrategy($response); + public function token() + { + + $request = new OAuth2TokenRequest + ( + new OAuth2Message + ( + Input::all() + ) + ); + + $response = $this->oauth2_protocol->token($request); + + if ($response instanceof OAuth2Response) + { + $strategy = OAuth2ResponseStrategyFactoryMethod::buildStrategy($request, $response); return $strategy->handle($response); } + return $response; } @@ -58,13 +134,24 @@ class OAuth2ProviderController extends BaseController { * Revoke Token HTTP Endpoint * @return mixed */ - public function revoke(){ - $response = $this->oauth2_protocol->revoke(new OAuth2TokenRevocationRequest(new OAuth2Message(Input::all()))); - $reflector = new ReflectionClass($response); - if ($reflector->isSubclassOf('oauth2\\responses\\OAuth2Response')) { - $strategy = OAuth2ResponseStrategyFactoryMethod::buildStrategy($response); + public function revoke() + { + $request = new OAuth2TokenRevocationRequest + ( + new OAuth2Message + ( + Input::all() + ) + ); + + $response = $this->oauth2_protocol->revoke($request); + + if ($response instanceof OAuth2Response) + { + $strategy = OAuth2ResponseStrategyFactoryMethod::buildStrategy($request, $response); return $strategy->handle($response); } + return $response; } @@ -73,13 +160,124 @@ class OAuth2ProviderController extends BaseController { * Introspection Token HTTP Endpoint * @return mixed */ - public function introspection(){ - $response = $this->oauth2_protocol->introspection(new OAuth2AccessTokenValidationRequest(new OAuth2Message(Input::all()))); - $reflector = new ReflectionClass($response); - if ($reflector->isSubclassOf('oauth2\\responses\\OAuth2Response')) { - $strategy = OAuth2ResponseStrategyFactoryMethod::buildStrategy($response); + public function introspection() + { + $request = new OAuth2AccessTokenValidationRequest + ( + new OAuth2Message + ( + Input::all() + ) + ); + + $response = $this->oauth2_protocol->introspection($request); + + if ($response instanceof OAuth2Response) + { + $strategy = OAuth2ResponseStrategyFactoryMethod::buildStrategy($request, $response); return $strategy->handle($response); } + return $response; } + + /** + * OP's JSON Web Key Set [JWK] document. + * @return string + */ + public function certs() + { + + $doc = $this->oauth2_protocol->getJWKSDocument(); + $response = Response::make($doc, 200); + $response->header('Content-Type', HttpContentType::Json); + + return $response; + } + + public function discovery() + { + + $doc = $this->oauth2_protocol->getDiscoveryDocument(); + $response = Response::make($doc, 200); + $response->header('Content-Type', HttpContentType::Json); + + return $response; + } + + /** + * http://openid.net/specs/openid-connect-session-1_0.html#OPiframe + */ + public function checkSessionIFrame() + { + $data = array(); + return View::make("oauth2.session.check-session", $data); + } + + /** + * http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ + public function endSession() + { + if(!$this->auth_service->isUserLogged()) + return Response::view('404', array(), 404); + + $request = new OAuth2LogoutRequest + ( + new OAuth2Message + ( + Input::all() + ) + ); + + if(!$request->isValid()) + { + Log::error('invalid OAuth2LogoutRequest!'); + return Response::view('404', array(), 404); + } + + if(Request::isMethod('get') ) + { + $rps = $this->auth_service->getLoggedRPs(); + $clients = array(); + foreach($this->auth_service->getLoggedRPs() as $client_id) + { + $client = $this->client_service->getClientById($client_id); + if(!is_null($client)) array_push($clients, $client); + } + + // At the logout endpoint, the OP SHOULD ask the End-User whether he wants to log out of the OP as well. + // If the End-User says "yes", then the OP MUST log out the End-User. + return View::make('oauth2.session.session-logout', array + ( + 'clients' => $clients, + 'id_token_hint' => $request->getIdTokenHint(), + 'post_logout_redirect_uri' => $request->getPostLogoutRedirectUri(), + 'state' => $request->getState(), + )); + } + + $consent = Input::get('oidc_endsession_consent'); + + if($consent === '1') + { + $response = $this->oauth2_protocol->endSession($request); + + if (!is_null($response) && $response instanceof OAuth2Response) { + $strategy = OAuth2ResponseStrategyFactoryMethod::buildStrategy($request, $response); + + return $strategy->handle($response); + } + + return View::make('oauth2.session.session-ended'); + } + + Log::error('invalid consent response!'); + return Response::view('404', array(), 404); + } + + public function cancelLogout() + { + return Redirect::action('HomeController@index'); + } } \ No newline at end of file diff --git a/app/controllers/openid/OpenIdProviderController.php b/app/controllers/openid/OpenIdProviderController.php index ae1de420..3a36f653 100644 --- a/app/controllers/openid/OpenIdProviderController.php +++ b/app/controllers/openid/OpenIdProviderController.php @@ -3,34 +3,54 @@ use openid\exceptions\InvalidOpenIdMessageException; use openid\helpers\OpenIdErrorMessages; use openid\IOpenIdProtocol; -use openid\services\IMementoOpenIdRequestService; +use openid\services\IMementoOpenIdSerializerService; use openid\strategies\OpenIdResponseStrategyFactoryMethod; - +use openid\OpenIdMessage; +use openid\responses\OpenIdResponse; /** * Class OpenIdProviderController */ class OpenIdProviderController extends BaseController { + /** + * @var IOpenIdProtocol + */ private $openid_protocol; + /** + * @var IMementoOpenIdSerializerService + */ private $memento_service; - public function __construct(IOpenIdProtocol $openid_protocol, IMementoOpenIdRequestService $memento_service) + /** + * @param IOpenIdProtocol $openid_protocol + * @param IMementoOpenIdSerializerService $memento_service + */ + public function __construct(IOpenIdProtocol $openid_protocol, IMementoOpenIdSerializerService $memento_service) { $this->openid_protocol = $openid_protocol; $this->memento_service = $memento_service; } + /** + * @return OpenIdResponse + * @throws Exception + * @throws InvalidOpenIdMessageException + */ public function endpoint() { - $input = Input::all(); - Log::debug(print_r($input, true)); - $msg = $this->memento_service->getCurrentRequest(); - if (is_null($msg) || !$msg->isValid()) + $msg = new OpenIdMessage( Input::all() ); + + if($this->memento_service->exists()){ + $msg = OpenIdMessage::buildFromMemento( $this->memento_service->load()); + } + + if (!$msg->isValid()) throw new InvalidOpenIdMessageException(OpenIdErrorMessages::InvalidOpenIdMessage); + //get response and manage it taking in consideration its type (direct or indirect) $response = $this->openid_protocol->handleOpenIdMessage($msg); - $reflector = new ReflectionClass($response); - if ($reflector->isSubclassOf('openid\\responses\\OpenIdResponse')) { + + if ($response instanceof OpenIdResponse) { $strategy = OpenIdResponseStrategyFactoryMethod::buildStrategy($response); return $strategy->handle($response); } diff --git a/app/controllers/openid/UserController.php b/app/controllers/openid/UserController.php index 0672397f..4dfbdead 100644 --- a/app/controllers/openid/UserController.php +++ b/app/controllers/openid/UserController.php @@ -2,24 +2,24 @@ use oauth2\services\IApiScopeService; use oauth2\services\IClientService; -use oauth2\services\IMementoOAuth2AuthenticationRequestService; -use oauth2\services\ITokenService; use oauth2\services\IResourceServerService; -use openid\services\IMementoOpenIdRequestService; +use oauth2\services\ITokenService; +use openid\requests\OpenIdAuthenticationRequest; +use openid\services\IMementoOpenIdSerializerService; +use openid\services\IServerConfigurationService; use openid\services\ITrustedSitesService; use openid\services\IUserService; -use openid\services\IServerConfigurationService; -use openid\requests\OpenIdAuthenticationRequest; -use openid\XRDS\XRDSDocumentBuilder; -use strategies\OpenIdLoginStrategy; -use strategies\OpenIdConsentStrategy; -use utils\IPHelper; use services\IUserActionService; use strategies\DefaultLoginStrategy; use strategies\OAuth2ConsentStrategy; use strategies\OAuth2LoginStrategy; +use strategies\OpenIdConsentStrategy; +use strategies\OpenIdLoginStrategy; +use utils\IPHelper; use utils\services\IAuthService; use utils\services\IServerConfigurationService as IUtilsServerConfigurationService; +use oauth2\services\IMementoOAuth2SerializerService; +use oauth2\services\ISecurityContextService; /** * Class UserController @@ -27,36 +27,97 @@ use utils\services\IServerConfigurationService as IUtilsServerConfigurationServi class UserController extends OpenIdController { + /** + * @var IMementoOpenIdSerializerService + */ private $openid_memento_service; + /** + * @var IMementoOAuth2SerializerService + */ private $oauth2_memento_service; + /** + * @var IAuthService + */ private $auth_service; + /** + * @var IServerConfigurationService + */ private $server_configuration_service; + /** + * @var DiscoveryController + */ private $discovery; + /** + * @var IUserService + */ private $user_service; + /** + * @var IUserActionService + */ private $user_action_service; + /** + * @var DefaultLoginStrategy + */ private $login_strategy; + /** + * @var null + */ private $consent_strategy; + /** + * @var IClientService + */ private $client_service; + /** + * @var IApiScopeService + */ private $scope_service; + /** + * @var ITokenService + */ private $token_service; + /** + * @var IResourceServerService + */ private $resource_server_service; - private $utils_configuration_service; + /** + * @var IUtilsServerConfigurationService + */ + private $utils_configuration_service; - public function __construct(IMementoOpenIdRequestService $openid_memento_service, - IMementoOAuth2AuthenticationRequestService $oauth2_memento_service, - IAuthService $auth_service, - IServerConfigurationService $server_configuration_service, - ITrustedSitesService $trusted_sites_service, - DiscoveryController $discovery, - IUserService $user_service, - IUserActionService $user_action_service, - IClientService $client_service, - IApiScopeService $scope_service, - ITokenService $token_service, - IResourceServerService $resource_server_service, - IUtilsServerConfigurationService $utils_configuration_service - ) + /** + * @param IMementoOpenIdSerializerService $openid_memento_service + * @param IMementoOAuth2SerializerService $oauth2_memento_service + * @param IAuthService $auth_service + * @param IServerConfigurationService $server_configuration_service + * @param ITrustedSitesService $trusted_sites_service + * @param DiscoveryController $discovery + * @param IUserService $user_service + * @param IUserActionService $user_action_service + * @param IClientService $client_service + * @param IApiScopeService $scope_service + * @param ITokenService $token_service + * @param IResourceServerService $resource_server_service + * @param IUtilsServerConfigurationService $utils_configuration_service + */ + public function __construct + ( + IMementoOpenIdSerializerService $openid_memento_service, + IMementoOAuth2SerializerService $oauth2_memento_service, + IAuthService $auth_service, + IServerConfigurationService $server_configuration_service, + ITrustedSitesService $trusted_sites_service, + DiscoveryController $discovery, + IUserService $user_service, + IUserActionService $user_action_service, + IClientService $client_service, + IApiScopeService $scope_service, + ITokenService $token_service, + IResourceServerService $resource_server_service, + IUtilsServerConfigurationService $utils_configuration_service, + ISecurityContextService $security_context_service + ) { + $this->openid_memento_service = $openid_memento_service; $this->oauth2_memento_service = $oauth2_memento_service; $this->auth_service = $auth_service; @@ -69,25 +130,50 @@ class UserController extends OpenIdController $this->scope_service = $scope_service; $this->token_service = $token_service; $this->resource_server_service = $resource_server_service; - $this->utils_configuration_service = $utils_configuration_service; + $this->utils_configuration_service = $utils_configuration_service; //filters $this->beforeFilter('csrf', array('only' => array('postLogin', 'postConsent'))); - $openid_msg = $this->openid_memento_service->getCurrentRequest(); - $oauth2_msg = $this->oauth2_memento_service->getCurrentAuthorizationRequest(); - - if (!is_null($openid_msg) && $openid_msg->isValid() && OpenIdAuthenticationRequest::IsOpenIdAuthenticationRequest($openid_msg)) { + if ($this->openid_memento_service->exists()) + { //openid stuff - $this->beforeFilter('openid.save.request'); - $this->beforeFilter('openid.needs.auth.request', array('only' => array('getConsent'))); - $this->login_strategy = new OpenIdLoginStrategy($openid_memento_service, $user_action_service, $auth_service); - $this->consent_strategy = new OpenIdConsentStrategy($openid_memento_service, $auth_service, $server_configuration_service, $user_action_service); - } else if (!is_null($oauth2_msg) && $oauth2_msg->isValid()) { - $this->beforeFilter('oauth2.save.request'); - $this->beforeFilter('oauth2.needs.auth.request', array('only' => array('getConsent'))); - $this->login_strategy = new OAuth2LoginStrategy($auth_service, $oauth2_memento_service ,$user_action_service); - $this->consent_strategy = new OAuth2ConsentStrategy($auth_service, $oauth2_memento_service, $scope_service, $client_service); - } else { + $this->login_strategy = new OpenIdLoginStrategy + ( + $openid_memento_service, + $user_action_service, + $auth_service + ); + + $this->consent_strategy = new OpenIdConsentStrategy + ( + $openid_memento_service, + $auth_service, + $server_configuration_service, + $user_action_service + ); + + } + else if ($this->oauth2_memento_service->exists()) + { + + $this->login_strategy = new OAuth2LoginStrategy + ( + $auth_service, + $oauth2_memento_service, + $user_action_service, + $security_context_service + ); + + $this->consent_strategy = new OAuth2ConsentStrategy + ( + $auth_service, + $oauth2_memento_service, + $scope_service, + $client_service + ); + } + else + { //default stuff $this->login_strategy = new DefaultLoginStrategy($user_action_service, $auth_service); $this->consent_strategy = null; @@ -106,46 +192,54 @@ class UserController extends OpenIdController public function postLogin() { - try { + try + { $max_login_attempts_2_show_captcha = $this->server_configuration_service->getConfigValue("MaxFailed.LoginAttempts.2ShowCaptcha"); $data = Input::all(); $login_attempts = intval(Input::get('login_attempts')); // Build the validation constraint set. - $rules = array( + $rules = array + ( 'username' => 'required|email', 'password' => 'required', ); - if ($login_attempts >= $max_login_attempts_2_show_captcha) { - $rules['recaptcha_response_field'] = 'required|recaptcha'; + if ($login_attempts >= $max_login_attempts_2_show_captcha) + { + $rules['g-recaptcha-response'] = 'required|recaptcha'; } // Create a new validator instance. $validator = Validator::make($data, $rules); - - - if ($validator->passes()) { - $username = Input::get("username"); - $password = Input::get("password"); - $remember = Input::get("remember"); + if ($validator->passes()) + { + $username = Input::get("username"); + $password = Input::get("password"); + $remember = Input::get("remember"); $remember = !is_null($remember); - if ($this->auth_service->login($username, $password, $remember)) { + if ($this->auth_service->login($username, $password, $remember)) + { return $this->login_strategy->postLogin(); } //failed login attempt... $user = $this->auth_service->getUserByUsername($username); - if ($user) { + if ($user) + { $login_attempts = $user->login_failed_attempt; } + return Redirect::action('UserController@getLogin') - ->with('max_login_attempts_2_show_captcha', $max_login_attempts_2_show_captcha) - ->with('login_attempts', $login_attempts) - ->with('username',$username) - ->with('flash_notice', "We're sorry, your username or password does not match an existing record."); + ->with('max_login_attempts_2_show_captcha', $max_login_attempts_2_show_captcha) + ->with('login_attempts', $login_attempts) + ->with('username', $username) + ->with('flash_notice', "We're sorry, your username or password does not match an existing record."); } + return Redirect::action('UserController@getLogin') - ->withErrors($validator); - } catch (Exception $ex) { + ->withErrors($validator); + } + catch (Exception $ex) + { Log::error($ex); return Redirect::action('UserController@getLogin'); } @@ -154,19 +248,26 @@ class UserController extends OpenIdController public function getConsent() { if (is_null($this->consent_strategy)) + { return View::make("404"); + } + return $this->consent_strategy->getConsent(); } public function postConsent() { - try { + try + { $trust_action = input::get("trust"); - if (!is_null($trust_action) && !is_null($this->consent_strategy)) { + if (!is_null($trust_action) && !is_null($this->consent_strategy)) + { return $this->consent_strategy->postConsent($trust_action); } return Redirect::action('UserController@getConsent'); - } catch (Exception $ex) { + } + catch (Exception $ex) + { Log::error($ex); return Redirect::action('UserController@getConsent'); } @@ -174,12 +275,16 @@ class UserController extends OpenIdController public function getIdentity($identifier) { - try { + try + { $user = $this->auth_service->getUserByOpenId($identifier); if (is_null($user)) + { return View::make("404"); + } - if ($this->isDiscoveryRequest()) { + if ($this->isDiscoveryRequest()) + { /* * If the Claimed Identifier was not previously discovered by the Relying Party * (the "openid.identity" in the request was "http://specs.openid.net/auth/2.0/identifier_select" @@ -191,15 +296,17 @@ class UserController extends OpenIdController } $current_user = $this->auth_service->getCurrentUser(); $another_user = false; - if ($current_user && $current_user->getIdentifier() != $user->getIdentifier()) { + if ($current_user && $current_user->getIdentifier() != $user->getIdentifier()) + { $another_user = true; } - $assets_url = $this->utils_configuration_service->getConfigValue("Assets.Url"); - $pic_url = $user->getPic(); - $pic_url = str_contains($pic_url,'http')?$pic_url:$assets_url.$pic_url; + $assets_url = $this->utils_configuration_service->getConfigValue("Assets.Url"); + $pic_url = $user->getPic(); + $pic_url = str_contains($pic_url, 'http') ? $pic_url : $assets_url . $pic_url; - $params = array( + $params = array + ( 'show_fullname' => $user->getShowProfileFullName(), 'username' => $user->getFullName(), 'show_email' => $user->getShowProfileEmail(), @@ -209,8 +316,11 @@ class UserController extends OpenIdController 'pic' => $pic_url, 'another_user' => $another_user, ); + return View::make("identity", $params); - } catch (Exception $ex) { + } + catch (Exception $ex) + { Log::error($ex); return View::make("404"); } @@ -218,8 +328,15 @@ class UserController extends OpenIdController public function logout() { - $this->user_action_service->addUserAction($this->auth_service->getCurrentUser(), IPHelper::getUserIp(), IUserActionService::LogoutAction); - Auth::logout(); + $this->user_action_service->addUserAction + ( + $this->auth_service->getCurrentUser(), + IPHelper::getUserIp(), + IUserActionService::LogoutAction + ); + + $this->auth_service->logout(); + return Redirect::action("UserController@getLogin"); } @@ -229,7 +346,8 @@ class UserController extends OpenIdController $sites = $user->getTrustedSites(); $actions = $user->getActions(); - return View::make("profile", array( + return View::make("profile", array + ( "username" => $user->getFullName(), "user_id" => $user->getId(), "is_oauth2_admin" => $user->isOAuth2ServerAdmin(), @@ -252,13 +370,15 @@ class UserController extends OpenIdController $show_pic = Input::get("show_pic"); $user = $this->auth_service->getCurrentUser(); $this->user_service->saveProfileInfo($user->getId(), $show_pic, $show_full_name, $show_email); + return Redirect::action("UserController@getProfile"); } public function deleteTrustedSite($id) { $this->trusted_sites_service->delTrustedSite($id); + return Redirect::action("UserController@getProfile"); } -} +} \ No newline at end of file diff --git a/app/database/migrations/2015_03_19_190534_insert_marketplace_api_endpoints_scopes.php b/app/database/migrations/2015_03_19_190534_insert_marketplace_api_endpoints_scopes.php index a1873273..0318d9a4 100644 --- a/app/database/migrations/2015_03_19_190534_insert_marketplace_api_endpoints_scopes.php +++ b/app/database/migrations/2015_03_19_190534_insert_marketplace_api_endpoints_scopes.php @@ -24,7 +24,7 @@ class InsertMarketplaceApiEndpointsScopes extends Migration { 'active' => true, 'Description' => 'Marketplace Public Clouds', 'resource_server_id' => $resource_server->id, - 'logo' => asset('img/apis/server.png') + 'logo' => asset('/assets/img/apis/server.png') ) ); // private clouds @@ -35,7 +35,7 @@ class InsertMarketplaceApiEndpointsScopes extends Migration { 'active' => true, 'Description' => 'Marketplace Private Clouds', 'resource_server_id' => $resource_server->id, - 'logo' => asset('img/apis/server.png') + 'logo' => asset('/assets/img/apis/server.png') ) ); // consultants @@ -46,7 +46,7 @@ class InsertMarketplaceApiEndpointsScopes extends Migration { 'active' => true, 'Description' => 'Marketplace Consultants', 'resource_server_id' => $resource_server->id, - 'logo' => asset('img/apis/server.png') + 'logo' => asset('/assets/img/apis/server.png') ) ); diff --git a/app/database/migrations/2015_06_30_192410_update_oauth2_client_oidc.php b/app/database/migrations/2015_06_30_192410_update_oauth2_client_oidc.php new file mode 100644 index 00000000..a52256a2 --- /dev/null +++ b/app/database/migrations/2015_06_30_192410_update_oauth2_client_oidc.php @@ -0,0 +1,95 @@ +dateTime('client_secret_expires_at')->nullable(); + $table->text('contacts')->nullable(); + $table->text('allowed_origins')->nullable(); + $table->text('redirect_uris')->nullable(); + $table->string('logo_uri')->nullable(); + $table->string('tos_uri')->nullable(); + // http://openid.net/specs/openid-connect-session-1_0.html#ClientMetadata + $table->text('post_logout_redirect_uris')->nullable(); + + $table->text('logout_uri')->nullable(); + $table->boolean('logout_session_required')->default(false); + $table->boolean('logout_use_iframe')->default(false); + + $table->string('policy_uri')->nullable(); + $table->string('jwks_uri')->nullable(); + $table->integer('default_max_age')->default(-1); + $table->boolean('require_auth_time')->default(false); + // http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication + $table->enum('token_endpoint_auth_method', array_merge(OAuth2Protocol::$token_endpoint_auth_methods, array ( + OAuth2Protocol::TokenEndpoint_AuthMethod_None)))->default(OAuth2Protocol::TokenEndpoint_AuthMethod_None); + $table->enum("token_endpoint_auth_signing_alg", OAuth2Protocol::$supported_signing_algorithms)->default(JSONWebSignatureAndEncryptionAlgorithms::None); + $table->enum('subject_type', Client::$valid_subject_types)->default(IClient::SubjectType_Public); + + $table->enum("userinfo_signed_response_alg", OAuth2Protocol::$supported_signing_algorithms)->default(JSONWebSignatureAndEncryptionAlgorithms::None); + // encryption + $table->enum("userinfo_encrypted_response_alg", OAuth2Protocol::$supported_key_management_algorithms)->default(JSONWebSignatureAndEncryptionAlgorithms::None); + $table->enum("userinfo_encrypted_response_enc", OAuth2Protocol::$supported_content_encryption_algorithms)->default(JSONWebSignatureAndEncryptionAlgorithms::None); + + $table->enum("id_token_signed_response_alg", OAuth2Protocol::$supported_signing_algorithms)->default(JSONWebSignatureAndEncryptionAlgorithms::None); + // encryption + $table->enum("id_token_encrypted_response_alg", OAuth2Protocol::$supported_key_management_algorithms)->default(JSONWebSignatureAndEncryptionAlgorithms::None); + $table->enum("id_token_encrypted_response_enc", OAuth2Protocol::$supported_content_encryption_algorithms)->default(JSONWebSignatureAndEncryptionAlgorithms::None); + + }); + + DB::statement("ALTER TABLE oauth2_client MODIFY COLUMN client_type ENUM('PUBLIC','CONFIDENTIAL') default 'CONFIDENTIAL';"); + DB::statement("ALTER TABLE oauth2_client MODIFY COLUMN application_type ENUM('WEB_APPLICATION','JS_CLIENT','SERVICE', 'NATIVE') default 'WEB_APPLICATION';"); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::table('oauth2_client', function($table) { + $table->dropColumn('client_secret_expires_at'); + $table->dropColumn('contacts'); + $table->dropColumn('allowed_origins'); + $table->dropColumn('redirect_uris'); + $table->dropColumn('logo_uri'); + $table->dropColumn('tos_uri'); + $table->dropColumn('post_logout_redirect_uris'); + $table->dropColumn('logout_uri'); + $table->dropColumn('logout_session_required'); + $table->dropColumn('logout_use_iframe'); + $table->dropColumn('policy_uri'); + $table->dropColumn('jwks_uri'); + $table->dropColumn('default_max_age'); + $table->dropColumn('require_auth_time'); + $table->dropColumn('token_endpoint_auth_method'); + $table->dropColumn('token_endpoint_auth_signing_alg'); + $table->dropColumn('subject_type'); + $table->dropColumn('userinfo_signed_response_alg'); + $table->dropColumn('userinfo_encrypted_response_alg'); + $table->dropColumn('userinfo_encrypted_response_enc'); + $table->dropColumn('id_token_signed_response_alg'); + $table->dropColumn('id_token_encrypted_response_alg'); + $table->dropColumn('id_token_encrypted_response_enc'); + }); + + DB::statement("ALTER TABLE oauth2_client MODIFY COLUMN application_type ENUM('WEB_APPLICATION','JS_CLIENT','SERVICE') default 'WEB_APPLICATION';"); + } + +} diff --git a/app/database/migrations/2015_07_09_044730_create_assymetric_keys.php b/app/database/migrations/2015_07_09_044730_create_assymetric_keys.php new file mode 100644 index 00000000..05886e0b --- /dev/null +++ b/app/database/migrations/2015_07_09_044730_create_assymetric_keys.php @@ -0,0 +1,87 @@ +bigIncrements('id'); + $table->timestamps(); + $table->text('pem_content'); + $table->string('kid'); + $table->boolean('active')->default(true); + + $table->enum + ( + 'usage', + JSONWebKeyPublicKeyUseValues::$valid_uses + )->default(JSONWebKeyPublicKeyUseValues::Signature); + + $table->enum('class_name', array('ClientPublicKey', 'ServerPrivateKey'))->default('ClientPublicKey'); + + $table->enum( + 'type', + array + ( + JSONWebKeyTypes::RSA, + JSONWebKeyTypes::EllipticCurve + ) + )->default(JSONWebKeyTypes::RSA); + + $table->dateTime('last_use')->nullable(); + $table->text('password')->nullable(); + $table->dateTime('valid_from'); + $table->dateTime('valid_to'); + + $table->enum + ( + 'alg', + array_merge + ( + OAuth2Protocol::$supported_signing_algorithms_rsa, + OAuth2Protocol::$supported_key_management_algorithms + ) + ) + ->default + ( + JSONWebSignatureAndEncryptionAlgorithms::None + ); + + // FK + $table->bigInteger("oauth2_client_id")->unsigned()->nullable(); + $table->index('oauth2_client_id'); + $table->foreign('oauth2_client_id') + ->references('id') + ->on('oauth2_client') + ->onDelete('cascade') + ->onUpdate('no action'); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::table('oauth2_assymetric_keys', function ($table) { + $table->dropForeign('oauth2_client_id'); + }); + Schema::dropIfExists('oauth2_assymetric_keys'); + } + +} diff --git a/app/database/migrations/2015_07_23_151507_migrate_redirect_uris.php b/app/database/migrations/2015_07_23_151507_migrate_redirect_uris.php new file mode 100644 index 00000000..7da2b292 --- /dev/null +++ b/app/database/migrations/2015_07_23_151507_migrate_redirect_uris.php @@ -0,0 +1,54 @@ +orderBy('client_id', 'desc')->get(); + foreach($uris as $uri) + { + $client = Client::find($uri->client_id); + $redirect_uris = $client->redirect_uris; + if(!empty($redirect_uris)) + $redirect_uris = $redirect_uris.','.$uri->uri; + else + $redirect_uris = $uri->uri; + + $client->redirect_uris = $redirect_uris; + + $client->save(); + } + + $uris = DB::table('oauth2_client_allowed_origin')->orderBy('client_id', 'desc')->get(); + foreach($uris as $uri) + { + $client = Client::find($uri->client_id); + $allowed_origins = $client->allowed_origins; + if(!empty($allowed_origins)) + $allowed_origins = $redirect_uris.','.$uri->allowed_origin; + else + $allowed_origins = $uri->allowed_origin; + + $client->allowed_origins = $allowed_origins; + + $client->save(); + } + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + // + } + +} diff --git a/app/database/migrations/2015_07_23_151520_add_new_default_scopes.php b/app/database/migrations/2015_07_23_151520_add_new_default_scopes.php new file mode 100644 index 00000000..b9680e83 --- /dev/null +++ b/app/database/migrations/2015_07_23_151520_add_new_default_scopes.php @@ -0,0 +1,50 @@ + OAuth2Protocol::OpenIdConnect_Scope, + 'short_description' => 'OpenId Connect Protocol', + 'description' => 'OpenId Connect Protocol', + 'api_id' => null, + 'system' => true, + 'default' => true, + 'active' => true, + ) + ); + + ApiScope::create( + array( + 'name' => OAuth2Protocol::OfflineAccess_Scope, + 'short_description' => 'allow to emit refresh tokens (offline access without user presence)', + 'description' => 'allow to emit refresh tokens (offline access without user presence)', + 'api_id' => null, + 'system' => true, + 'default' => true, + 'active' => true, + ) + ); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + ApiScope::where('name', '=', OAuth2Protocol::OpenIdConnect_Scope)->first()-delete(); + ApiScope::where('name', '=', OAuth2Protocol::OfflineAccess_Scope)->first()-delete(); + } + +} diff --git a/app/database/migrations/2015_07_23_151541_update_oauth2_client_scopes.php b/app/database/migrations/2015_07_23_151541_update_oauth2_client_scopes.php new file mode 100644 index 00000000..3f75ba9a --- /dev/null +++ b/app/database/migrations/2015_07_23_151541_update_oauth2_client_scopes.php @@ -0,0 +1,43 @@ +first(); + $scope_offline = ApiScope::where('name', '=', OAuth2Protocol::OfflineAccess_Scope)->first(); + + foreach($clients as $client) + { + $client->scopes()->attach($scope_openid->id); + if($client->application_type === IClient::ApplicationType_Native || $client->application_type === IClient::ApplicationType_Web_App) + $client->scopes()->attach($scope_offline->id); + + if ($client->client_type === IClient::ClientType_Confidential) + { + $client->token_endpoint_auth_method = OAuth2Protocol::TokenEndpoint_AuthMethod_ClientSecretBasic; + $client->save(); + } + } + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + // + } + +} diff --git a/app/database/migrations/2015_07_30_221758_oauth2_clients_update_secret_expiration_date.php b/app/database/migrations/2015_07_30_221758_oauth2_clients_update_secret_expiration_date.php new file mode 100644 index 00000000..47ba9498 --- /dev/null +++ b/app/database/migrations/2015_07_30_221758_oauth2_clients_update_secret_expiration_date.php @@ -0,0 +1,36 @@ +client_type !== IClient::ClientType_Confidential) continue; + // default 6 months + $client->client_secret_expires_at = $now->add(new \DateInterval('P6M')); + $client->save(); + } + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + // + } + +} diff --git a/app/database/migrations/2015_08_10_205534_add_user_info_OIDC_endpoint.php b/app/database/migrations/2015_08_10_205534_add_user_info_OIDC_endpoint.php new file mode 100644 index 00000000..a5617c38 --- /dev/null +++ b/app/database/migrations/2015_08_10_205534_add_user_info_OIDC_endpoint.php @@ -0,0 +1,66 @@ +first(); + + if(is_null($users)) return; + + ApiEndpoint::create( + array( + 'name' => 'get-user-claims-get', + 'active' => true, + 'api_id' => $users->id, + 'route' => '/api/v1/users/info', + 'http_method' => 'GET', + 'allow_cors' => true, + ) + ); + + ApiEndpoint::create( + array( + 'name' => 'get-user-claims-post', + 'active' => true, + 'api_id' => $users->id, + 'route' => '/api/v1/users/info', + 'http_method' => 'POST', + 'allow_cors' => true, + ) + ); + + $profile_scope = ApiScope::where('name', '=', 'profile')->first(); + $email_scope = ApiScope::where('name', '=', 'email')->first(); + $address_scope = ApiScope::where('name', '=', 'address')->first(); + + + $get_user_info_endpoint = ApiEndpoint::where('name', '=', 'get-user-claims-get')->first(); + $get_user_info_endpoint->scopes()->attach($profile_scope->id); + $get_user_info_endpoint->scopes()->attach($email_scope->id); + $get_user_info_endpoint->scopes()->attach($address_scope->id); + + $get_user_info_endpoint = ApiEndpoint::where('name', '=', 'get-user-claims-post')->first(); + $get_user_info_endpoint->scopes()->attach($profile_scope->id); + $get_user_info_endpoint->scopes()->attach($email_scope->id); + $get_user_info_endpoint->scopes()->attach($address_scope->id); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + // + } + +} diff --git a/app/database/migrations/2015_12_14_203852_add_api_scopes_groups.php b/app/database/migrations/2015_12_14_203852_add_api_scopes_groups.php new file mode 100644 index 00000000..9495ef16 --- /dev/null +++ b/app/database/migrations/2015_12_14_203852_add_api_scopes_groups.php @@ -0,0 +1,75 @@ +bigIncrements('id')->unsigned(); + $table->string('name', 512); + $table->text('description'); + $table->boolean('active')->default(true); + $table->timestamps(); + }); + + Schema::create('oauth2_api_scope_group_scope', function($table) + { + $table->timestamps(); + + $table->bigInteger("group_id")->unsigned(); + $table->index('group_id'); + $table->foreign('group_id') + ->references('id') + ->on('oauth2_api_scope_group') + ->onDelete('cascade') + ->onUpdate('no action'); ; + + $table->bigInteger("scope_id")->unsigned(); + $table->index('scope_id'); + $table->foreign('scope_id') + ->references('id') + ->on('oauth2_api_scope') + ->onDelete('cascade') + ->onUpdate('no action'); + }); + + Schema::create('oauth2_api_scope_group_users', function($table) + { + $table->timestamps(); + + $table->bigInteger("group_id")->unsigned(); + $table->index('group_id'); + $table->foreign('group_id') + ->references('id') + ->on('oauth2_api_scope_group') + ->onDelete('cascade') + ->onUpdate('no action'); ; + + $table->bigInteger("user_id")->unsigned(); + $table->index('user_id'); + $table->foreign('user_id') + ->references('id') + ->on('openid_users') + ->onDelete('cascade') + ->onUpdate('no action'); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('oauth2_api_scope_group'); + Schema::dropIfExists('oauth2_api_scope_group_scope'); + Schema::dropIfExists('oauth2_api_scope_group_users'); + } +} diff --git a/app/database/migrations/2015_12_15_201400_update_api_scope.php b/app/database/migrations/2015_12_15_201400_update_api_scope.php new file mode 100644 index 00000000..cd77f7ec --- /dev/null +++ b/app/database/migrations/2015_12_15_201400_update_api_scope.php @@ -0,0 +1,26 @@ +boolean('assigned_by_groups')->default(false); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::table('oauth2_api_scope', function ($table) { + $table->dropColumn('assigned_by_groups'); + }); + } + +} diff --git a/app/database/seeds/ApiEndpointSeeder.php b/app/database/seeds/ApiEndpointSeeder.php index 03ef0c52..b03a3c96 100644 --- a/app/database/seeds/ApiEndpointSeeder.php +++ b/app/database/seeds/ApiEndpointSeeder.php @@ -37,6 +37,36 @@ class ApiEndpointSeeder extends Seeder $get_user_info_endpoint->scopes()->attach($profile_scope->id); $get_user_info_endpoint->scopes()->attach($email_scope->id); $get_user_info_endpoint->scopes()->attach($address_scope->id); + + ApiEndpoint::create( + array( + 'name' => 'get-user-claims-get', + 'active' => true, + 'api_id' => $users->id, + 'route' => '/api/v1/users/info', + 'http_method' => 'GET' + ) + ); + + ApiEndpoint::create( + array( + 'name' => 'get-user-claims-post', + 'active' => true, + 'api_id' => $users->id, + 'route' => '/api/v1/users/info', + 'http_method' => 'POST' + ) + ); + + $get_user_info_endpoint = ApiEndpoint::where('name','=','get-user-claims-get')->first(); + $get_user_info_endpoint->scopes()->attach($profile_scope->id); + $get_user_info_endpoint->scopes()->attach($email_scope->id); + $get_user_info_endpoint->scopes()->attach($address_scope->id); + + $get_user_info_endpoint = ApiEndpoint::where('name','=','get-user-claims-post')->first(); + $get_user_info_endpoint->scopes()->attach($profile_scope->id); + $get_user_info_endpoint->scopes()->attach($email_scope->id); + $get_user_info_endpoint->scopes()->attach($address_scope->id); } } \ No newline at end of file diff --git a/app/database/seeds/ApiScopeSeeder.php b/app/database/seeds/ApiScopeSeeder.php index 7e7ac270..7d320b96 100644 --- a/app/database/seeds/ApiScopeSeeder.php +++ b/app/database/seeds/ApiScopeSeeder.php @@ -1,5 +1,7 @@ OAuth2Protocol::OpenIdConnect_Scope, + 'short_description' => 'OpenId Connect Protocol', + 'description' => 'OpenId Connect Protocol', + 'api_id' => null, + 'system' => true, + 'default' => true, + 'active' => true, + ) + ); + + ApiScope::create( + array( + 'name' => OAuth2Protocol::OfflineAccess_Scope, + 'short_description' => 'allow to emit refresh tokens (offline access without user presence)', + 'description' => 'allow to emit refresh tokens (offline access without user presence)', + 'api_id' => null, + 'system' => true, + 'default' => true, + 'active' => true, + ) + ); + } } \ No newline at end of file diff --git a/app/database/seeds/ApiSeeder.php b/app/database/seeds/ApiSeeder.php index afea2c1c..4fc4d81f 100644 --- a/app/database/seeds/ApiSeeder.php +++ b/app/database/seeds/ApiSeeder.php @@ -22,7 +22,7 @@ class ApiSeeder extends Seeder { 'active' => true, 'Description' => 'User Info', 'resource_server_id' => $resource_server->id, - 'logo' => asset('img/apis/server.png') + 'logo' => asset('/assets/img/apis/server.png') ) ); } diff --git a/app/database/seeds/TestSeeder.php b/app/database/seeds/TestSeeder.php index 5d4cc395..46145fac 100644 --- a/app/database/seeds/TestSeeder.php +++ b/app/database/seeds/TestSeeder.php @@ -3,17 +3,248 @@ use oauth2\models\IClient; use auth\User; use utils\services\IAuthService; +use \jwk\JSONWebKeyPublicKeyUseValues; +use \jwk\JSONWebKeyTypes; +use \oauth2\OAuth2Protocol; +use \jwa\JSONWebSignatureAndEncryptionAlgorithms; /** * Class OAuth2ApplicationSeeder * This seeder is only for testing purposes */ class TestSeeder extends Seeder { +static $client_private_key_1 = <<statement($member_table); + + Member::create( + array( + 'ID' => 1, + 'FirstName' => 'Sebastian', + 'Surname' => 'Marcet', + 'Email' => 'sebastian@tipit.net', + 'Password' => '1qaz2wsx', + 'PasswordEncryption' => 'none', + 'Salt' => 'none', + 'Gender' => 'male', + 'Address' => 'Av. Siempre Viva 111', + 'Suburb' => 'Lanus Este', + 'State' => 'Buenos Aires', + 'City' => 'Lanus', + 'Postcode' => '1824', + 'Country' => 'AR', + 'Locale' => 'ESP', + ) + ); + + Member::create( + array( + 'ID' => 2, + 'FirstName' => 'Sebastian', + 'Surname' => 'Marcet', + 'Email' => 'sebastian+1@tipit.net', + 'Password' => '1qaz2wsx', + 'PasswordEncryption' => 'none', + 'Salt' => 'none', + 'Gender' => 'male', + 'Address' => 'Av. Siempre Viva 111', + 'Suburb' => 'Lanus Este', + 'State' => 'Buenos Aires', + 'City' => 'Lanus', + 'Postcode' => '1824', + 'Country' => 'AR', + 'Locale' => 'ESP', + ) + ); + + Member::create( + array( + 'ID' => 3, + 'FirstName' => 'Sebastian', + 'Surname' => 'Marcet', + 'Email' => 'sebastian+2@tipit.net', + 'Password' => '1qaz2wsx', + 'PasswordEncryption' => 'none', + 'Salt' => 'none', + 'Gender' => 'male', + 'Address' => 'Av. Siempre Viva 111', + 'Suburb' => 'Lanus Este', + 'State' => 'Buenos Aires', + 'City' => 'Lanus', + 'Postcode' => '1824', + 'Country' => 'AR', + 'Locale' => 'ESP', + ) + ); + DB::table('banned_ips')->delete(); DB::table('user_exceptions_trail')->delete(); DB::table('server_configuration')->delete(); @@ -23,10 +254,12 @@ class TestSeeder extends Seeder { DB::table('oauth2_client_authorized_uri')->delete(); DB::table('oauth2_access_token')->delete(); DB::table('oauth2_refresh_token')->delete(); + DB::table('oauth2_assymetric_keys')->delete(); DB::table('oauth2_client')->delete(); DB::table('openid_trusted_sites')->delete(); DB::table('openid_associations')->delete(); + DB::table('user_actions')->delete(); DB::table('openid_users')->delete(); DB::table('oauth2_api_endpoint_api_scope')->delete(); @@ -53,6 +286,31 @@ class TestSeeder extends Seeder { $this->seedApis(); //scopes + + ApiScope::create( + array( + 'name' => OAuth2Protocol::OpenIdConnect_Scope, + 'short_description' => 'OIDC', + 'description' => 'OIDC', + 'api_id' => null, + 'system' => true, + 'default' => true, + 'active' => true, + ) + ); + + ApiScope::create( + array( + 'name' => OAuth2Protocol::OfflineAccess_Scope, + 'short_description' => 'allow to emit refresh tokens (offline access without user presence)', + 'description' => 'allow to emit refresh tokens (offline access without user presence)', + 'api_id' => null, + 'system' => true, + 'default' => true, + 'active' => true, + ) + ); + $this->seedResourceServerScopes(); $this->seedApiScopes(); $this->seedApiEndpointScopes(); @@ -308,7 +566,7 @@ class TestSeeder extends Seeder { User::create( array( 'identifier' => 'sebastian.marcet', - 'external_identifier' => 13867, + 'external_identifier' => 1, 'last_login_date' => gmdate("Y-m-d H:i:s", time()) ) ); @@ -323,18 +581,72 @@ class TestSeeder extends Seeder { ) ); + $now = new \DateTime(); + Client::create( array( 'app_name' => 'oauth2_test_app', 'app_description' => 'oauth2_test_app', 'app_logo' => null, 'client_id' => 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client', - 'client_secret' => 'ITc/6Y5N7kOtGKhg', + 'client_secret' => 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg', 'client_type' => IClient::ClientType_Confidential, 'application_type' => IClient::ApplicationType_Web_App, + 'token_endpoint_auth_method' => OAuth2Protocol::TokenEndpoint_AuthMethod_ClientSecretBasic, 'user_id' => $user->id, 'rotate_refresh_token' => true, - 'use_refresh_token' => true + 'use_refresh_token' => true, + 'redirect_uris' => 'https://www.test.com/oauth2,https://op.certification.openid.net:60393/authz_cb', + 'id_token_signed_response_alg' => JSONWebSignatureAndEncryptionAlgorithms::HS512, + 'id_token_encrypted_response_alg' => JSONWebSignatureAndEncryptionAlgorithms::RSA_OAEP_256, + 'id_token_encrypted_response_enc' => JSONWebSignatureAndEncryptionAlgorithms::A256CBC_HS512, + 'client_secret_expires_at' => $now->add(new \DateInterval('P6M')), + ) + ); + + Client::create( + array + ( + 'app_name' => 'oauth2_test_app2', + 'app_description' => 'oauth2_test_app2', + 'app_logo' => null, + 'client_id' => 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x2.openstack.client', + 'client_secret' => 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg', + 'client_type' => IClient::ClientType_Confidential, + 'application_type' => IClient::ApplicationType_Web_App, + 'token_endpoint_auth_method' => OAuth2Protocol::TokenEndpoint_AuthMethod_ClientSecretJwt, + 'token_endpoint_auth_signing_alg' => JSONWebSignatureAndEncryptionAlgorithms::HS512, + 'subject_type' => IClient::SubjectType_Pairwise, + 'user_id' => $user->id, + 'rotate_refresh_token' => true, + 'use_refresh_token' => true, + 'redirect_uris' => 'https://www.test.com/oauth2', + 'id_token_signed_response_alg' => JSONWebSignatureAndEncryptionAlgorithms::HS512, + 'id_token_encrypted_response_alg' => JSONWebSignatureAndEncryptionAlgorithms::RSA_OAEP_256, + 'id_token_encrypted_response_enc' => JSONWebSignatureAndEncryptionAlgorithms::A256CBC_HS512, + + 'client_secret_expires_at' => $now->add(new \DateInterval('P6M')), + ) + ); + + Client::create + ( + array( + 'app_name' => 'oauth2_test_app3', + 'app_description' => 'oauth2_test_app3', + 'app_logo' => null, + 'client_id' => 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ33.openstack.client', + 'client_secret' => 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N585OtGKhg55', + 'client_type' => IClient::ClientType_Confidential, + 'application_type' => IClient::ApplicationType_Web_App, + 'token_endpoint_auth_method' => OAuth2Protocol::TokenEndpoint_AuthMethod_ClientSecretBasic, + 'user_id' => $user->id, + 'rotate_refresh_token' => true, + 'use_refresh_token' => true, + 'redirect_uris' => 'https://www.test.com/oauth2', + 'id_token_signed_response_alg' => JSONWebSignatureAndEncryptionAlgorithms::HS512, + 'userinfo_signed_response_alg' => JSONWebSignatureAndEncryptionAlgorithms::RS512, + 'client_secret_expires_at' => $now->add(new \DateInterval('P6M')), ) ); @@ -344,12 +656,15 @@ class TestSeeder extends Seeder { 'app_description' => 'oauth2.service', 'app_logo' => null, 'client_id' => '11z87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client', - 'client_secret' => '11c/6Y5N7kOtGKhg', + 'client_secret' => '11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhg', 'client_type' => IClient::ClientType_Confidential, 'application_type' => IClient::ApplicationType_Service, + 'token_endpoint_auth_method' => OAuth2Protocol::TokenEndpoint_AuthMethod_ClientSecretBasic, 'user_id' => $user->id, 'rotate_refresh_token' => true, - 'use_refresh_token' => true + 'use_refresh_token' => true, + 'redirect_uris' => 'https://www.test.com/oauth2', + 'client_secret_expires_at' => $now->add(new \DateInterval('P6M')), ) ); @@ -360,11 +675,49 @@ class TestSeeder extends Seeder { 'app_logo' => null, 'client_id' => 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.openstack.client', 'client_secret' => null, - 'client_type' => IClient::ClientType_Public, 'application_type' => IClient::ApplicationType_JS_Client, + 'token_endpoint_auth_method' => OAuth2Protocol::TokenEndpoint_AuthMethod_PrivateKeyJwt, + 'token_endpoint_auth_signing_alg' => JSONWebSignatureAndEncryptionAlgorithms::RS512, 'user_id' => $user->id, 'rotate_refresh_token' => false, - 'use_refresh_token' => false + 'use_refresh_token' => false, + 'redirect_uris' => 'https://www.test.com/oauth2', + + ) + ); + + Client::create( + array( + 'app_name' => 'oauth2_native_app', + 'app_description' => 'oauth2_native_app', + 'app_logo' => null, + 'client_id' => 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.android.openstack.client', + 'client_secret' => '11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhgfdfdfdf', + 'client_type' => IClient::ClientType_Confidential, + 'application_type' => IClient::ApplicationType_Native, + 'token_endpoint_auth_method' => OAuth2Protocol::TokenEndpoint_AuthMethod_ClientSecretBasic, + 'user_id' => $user->id, + 'rotate_refresh_token' => true, + 'use_refresh_token' => true, + 'redirect_uris' => 'androipapp://oidc_endpoint_callback', + ) + ); + + Client::create( + array( + 'app_name' => 'oauth2_native_app2', + 'app_description' => 'oauth2_native_app2', + 'app_logo' => null, + 'client_id' => 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.android2.openstack.client', + 'client_secret' => '11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhgfdfdfdf', + 'client_type' => IClient::ClientType_Confidential, + 'application_type' => IClient::ApplicationType_Native, + 'token_endpoint_auth_method' => OAuth2Protocol::TokenEndpoint_AuthMethod_PrivateKeyJwt, + 'token_endpoint_auth_signing_alg' => JSONWebSignatureAndEncryptionAlgorithms::RS512, + 'user_id' => $user->id, + 'rotate_refresh_token' => true, + 'use_refresh_token' => true, + 'redirect_uris' => 'androipapp://oidc_endpoint_callback2', ) ); @@ -379,7 +732,8 @@ class TestSeeder extends Seeder { 'application_type' => IClient::ApplicationType_JS_Client, 'user_id' => $user->id, 'rotate_refresh_token' => false, - 'use_refresh_token' => false + 'use_refresh_token' => false, + 'redirect_uris' => 'https://www.test.com/oauth2' ) ); @@ -389,47 +743,158 @@ class TestSeeder extends Seeder { 'app_description' => 'resource_server_client', 'app_logo' => null, 'client_id' => 'resource.server.1.openstack.client', - 'client_secret' => '123456789', + 'client_secret' => '123456789123456789123456789123456789123456789', 'client_type' => IClient::ClientType_Confidential, 'application_type' => IClient::ApplicationType_Service, + 'token_endpoint_auth_method' => OAuth2Protocol::TokenEndpoint_AuthMethod_ClientSecretBasic, 'resource_server_id' => $resource_server->id, 'rotate_refresh_token' => false, - 'use_refresh_token' => false + 'use_refresh_token' => false, + 'client_secret_expires_at' => $now->add(new \DateInterval('P6M')), ) ); - $client_confidential = Client::where('app_name','=','oauth2_test_app')->first(); - $client_public = Client::where('app_name','=','oauth2_test_app_public')->first(); - $client_service = Client::where('app_name','=','oauth2.service')->first(); - //attach scopes + $client_confidential = Client::where('app_name','=','oauth2_test_app')->first(); + $client_confidential2 = Client::where('app_name','=','oauth2_test_app2')->first(); + $client_confidential3 = Client::where('app_name','=','oauth2_test_app3')->first(); + $client_public = Client::where('app_name','=','oauth2_test_app_public')->first(); + $client_service = Client::where('app_name','=','oauth2.service')->first(); + $client_native = Client::where('app_name','=','oauth2_native_app')->first(); + $client_native2 = Client::where('app_name','=','oauth2_native_app2')->first(); + + //attach all scopes $scopes = ApiScope::get(); - foreach($scopes as $scope){ + foreach($scopes as $scope) + { $client_confidential->scopes()->attach($scope->id); + $client_confidential2->scopes()->attach($scope->id); + $client_confidential3->scopes()->attach($scope->id); $client_public->scopes()->attach($scope->id); $client_service->scopes()->attach($scope->id); + $client_native->scopes()->attach($scope->id); + $client_native2->scopes()->attach($scope->id); } - //add uris - ClientAuthorizedUri::create( - array( - 'uri' => 'https://www.test.com/oauth2', - 'client_id' => $client_confidential->id - ) + + $now = new \DateTime('now'); + $to = new \DateTime('now'); + $to->add(new \DateInterval('P31D')); + + $public_key_1 = ClientPublicKey::buildFromPEM( + 'public_key_1', + JSONWebKeyTypes::RSA, + JSONWebKeyPublicKeyUseValues::Encryption, + self::$client_public_key_1, + JSONWebSignatureAndEncryptionAlgorithms::RSA_OAEP_256, + true, + $now, + $to ); - //add uris - ClientAllowedOrigin::create( - array( - 'allowed_origin' => 'https://www.test.com/oauth2', - 'client_id' => $client_confidential->id - ) + $public_key_1->oauth2_client_id = $client_confidential->id; + $public_key_1->save(); + + $public_key_2 = ClientPublicKey::buildFromPEM( + 'public_key_2', + JSONWebKeyTypes::RSA, + JSONWebKeyPublicKeyUseValues::Signature, + self::$client_public_key_2, + JSONWebSignatureAndEncryptionAlgorithms::RS512, + true, + $now, + $to ); - ClientAuthorizedUri::create( - array( - 'uri'=>'https://www.test.com/oauth2', - 'client_id'=>$client_public->id - ) + $public_key_2->oauth2_client_id = $client_confidential->id; + $public_key_2->save(); + + // confidential client 2 + $public_key_11 = ClientPublicKey::buildFromPEM( + 'public_key_1', + JSONWebKeyTypes::RSA, + JSONWebKeyPublicKeyUseValues::Encryption, + self::$client_public_key_1, + JSONWebSignatureAndEncryptionAlgorithms::RSA_OAEP_256, + true, + $now, + $to ); + + $public_key_11->oauth2_client_id = $client_confidential2->id; + $public_key_11->save(); + + $public_key_22 = ClientPublicKey::buildFromPEM( + 'public_key_2', + JSONWebKeyTypes::RSA, + JSONWebKeyPublicKeyUseValues::Signature, + self::$client_public_key_2, + JSONWebSignatureAndEncryptionAlgorithms::RS512, + true, + $now, + $to + ); + + $public_key_22->oauth2_client_id = $client_confidential2->id; + $public_key_22->save(); + + // public native client + $public_key_33 = ClientPublicKey::buildFromPEM( + 'public_key_33', + JSONWebKeyTypes::RSA, + JSONWebKeyPublicKeyUseValues::Encryption, + self::$client_public_key_1, + JSONWebSignatureAndEncryptionAlgorithms::RSA_OAEP_256, + true, + $now, + $to + ); + + $public_key_33->oauth2_client_id = $client_native2->id; + $public_key_33->save(); + + $public_key_44 = ClientPublicKey::buildFromPEM( + 'public_key_44', + JSONWebKeyTypes::RSA, + JSONWebKeyPublicKeyUseValues::Signature, + self::$client_public_key_2, + JSONWebSignatureAndEncryptionAlgorithms::RS512, + true, + $now, + $to + ); + + $public_key_44->oauth2_client_id = $client_native2->id; + $public_key_44->save(); + + // server private keys + + $pkey_1 = ServerPrivateKey::build + ( + 'server_key_enc', + $now, + $to, + JSONWebKeyTypes::RSA, + JSONWebKeyPublicKeyUseValues::Encryption, + JSONWebSignatureAndEncryptionAlgorithms::RSA1_5, + true, + TestKeys::$private_key_pem + ); + + $pkey_1->save(); + + + $pkey_2 = ServerPrivateKey::build + ( + 'server_key_sig', + $now, + $to, + JSONWebKeyTypes::RSA, + JSONWebKeyPublicKeyUseValues::Signature, + JSONWebSignatureAndEncryptionAlgorithms::RS512, + true, + TestKeys::$private_key_pem + ); + + $pkey_2->save(); } private function seedApis(){ @@ -442,7 +907,7 @@ class TestSeeder extends Seeder { 'active' => true, 'Description' => 'Resource Server CRUD operations', 'resource_server_id' => $resource_server->id, - 'logo' => asset('img/apis/server.png') + 'logo' => asset('/assets/img/apis/server.png') ) ); @@ -453,7 +918,7 @@ class TestSeeder extends Seeder { 'active' => true, 'Description' => 'Api CRUD operations', 'resource_server_id' => $resource_server->id, - 'logo' => asset('img/apis/server.png') + 'logo' => asset('/assets/img/apis/server.png') ) ); @@ -465,7 +930,7 @@ class TestSeeder extends Seeder { 'active' => true, 'Description' => 'Api Endpoints CRUD operations', 'resource_server_id' => $resource_server->id, - 'logo' => asset('img/apis/server.png') + 'logo' => asset('/assets/img/apis/server.png') ) ); @@ -476,7 +941,7 @@ class TestSeeder extends Seeder { 'active' => true, 'Description' => 'Api Scopes CRUD operations', 'resource_server_id' => $resource_server->id, - 'logo' => asset('img/apis/server.png') + 'logo' => asset('/assets/img/apis/server.png') ) ); @@ -487,7 +952,7 @@ class TestSeeder extends Seeder { 'active' => true, 'Description' => 'User Info', 'resource_server_id' => $resource_server->id, - 'logo' => asset('img/apis/server.png') + 'logo' => asset('/assets/img/apis/server.png') ) ); @@ -498,7 +963,7 @@ class TestSeeder extends Seeder { 'active' => true, 'Description' => 'Marketplace Public Clouds', 'resource_server_id' => $resource_server->id, - 'logo' => asset('img/apis/server.png') + 'logo' => asset('/assets/img/apis/server.png') ) ); @@ -509,7 +974,7 @@ class TestSeeder extends Seeder { 'active' => true, 'Description' => 'Marketplace Private Clouds', 'resource_server_id' => $resource_server->id, - 'logo' => asset('img/apis/server.png') + 'logo' => asset('/assets/img/apis/server.png') ) ); @@ -520,7 +985,7 @@ class TestSeeder extends Seeder { 'active' => true, 'Description' => 'Marketplace Consultants', 'resource_server_id' => $resource_server->id, - 'logo' => asset('img/apis/server.png') + 'logo' => asset('/assets/img/apis/server.png') ) ); @@ -667,6 +1132,7 @@ class TestSeeder extends Seeder { ) ); + } private function seedApiEndpointScopes(){ @@ -1363,6 +1829,27 @@ class TestSeeder extends Seeder { 'http_method' => 'GET' ) ); + + ApiEndpoint::create( + array( + 'name' => 'get-user-claims-get', + 'active' => true, + 'api_id' => $users->id, + 'route' => '/api/v1/users/info', + 'http_method' => 'GET' + ) + ); + + ApiEndpoint::create( + array( + 'name' => 'get-user-claims-post', + 'active' => true, + 'api_id' => $users->id, + 'route' => '/api/v1/users/info', + 'http_method' => 'POST' + ) + ); + $profile_scope = ApiScope::where('name','=','profile')->first(); $email_scope = ApiScope::where('name','=','email')->first(); $address_scope = ApiScope::where('name','=','address')->first(); @@ -1371,6 +1858,16 @@ class TestSeeder extends Seeder { $get_user_info_endpoint->scopes()->attach($profile_scope->id); $get_user_info_endpoint->scopes()->attach($email_scope->id); $get_user_info_endpoint->scopes()->attach($address_scope->id); + + $get_user_info_endpoint = ApiEndpoint::where('name','=','get-user-claims-get')->first(); + $get_user_info_endpoint->scopes()->attach($profile_scope->id); + $get_user_info_endpoint->scopes()->attach($email_scope->id); + $get_user_info_endpoint->scopes()->attach($address_scope->id); + + $get_user_info_endpoint = ApiEndpoint::where('name','=','get-user-claims-post')->first(); + $get_user_info_endpoint->scopes()->attach($profile_scope->id); + $get_user_info_endpoint->scopes()->attach($email_scope->id); + $get_user_info_endpoint->scopes()->attach($address_scope->id); } private function seedPublicCloudsEndpoints(){ diff --git a/app/filters.php b/app/filters.php index ce811342..1e9af545 100644 --- a/app/filters.php +++ b/app/filters.php @@ -1,4 +1,5 @@ getService(UtilsServiceCatalog::CheckPointService); - if (!$checkpoint_service->check()) { - return View::make('404'); + + ClientAuthContextValidatorFactory::setTokenEndpointUrl + ( + URL::action("OAuth2ProviderController@token") + ); + + ClientAuthContextValidatorFactory::setJWKSetReader + ( + App::make('oauth2\services\IClientJWKSetReader') + ); + + if(Config::get('server.Banning_Enable', true)) + { + try { + //checkpoint security pattern entry point + $checkpoint_service = ServiceLocator::getInstance()->getService(UtilsServiceCatalog::CheckPointService); + if (!$checkpoint_service->check()) { + return Response::view('404', array(), 404); + } + } catch (Exception $ex) { + Log::error($ex); + + return Response::view('404', array(), 404); } - } catch (Exception $ex) { - Log::error($ex); - return View::make('404'); } $cors = ServiceLocator::getInstance()->getService('CORSMiddleware'); @@ -39,15 +58,6 @@ App::after(function($request, $response){ // https://www.owasp.org/index.php/List_of_useful_HTTP_headers $response->headers->set('X-content-type-options','nosniff'); $response->headers->set('X-xss-protection','1; mode=block'); - // http://tools.ietf.org/html/rfc6797 - /** - * The HSTS header field below stipulates that the HSTS Policy is to - * remain in effect for one year (there are approximately 31536000 - * seconds in a year) - * applies to the domain of the issuing HSTS Host and all of its - * subdomains: - */ - $response->headers->set('Strict-Transport-Security','max-age=31536000; includeSubDomains'); //cache $response->headers->set('pragma','no-cache'); $response->headers->set('Expires','-1'); @@ -70,11 +80,13 @@ App::after(function($request, $response){ Route::filter('auth', function () { if (Auth::guest()) { Session::put('url.intended', URL::full()); + Session::save(); return Redirect::action('HomeController@index'); } $redirect = Session::get('url.intended'); if (!empty($redirect)) { Session::forget('url.intended'); + Session::save(); return Redirect::to($redirect); } }); @@ -120,57 +132,15 @@ Route::filter('ajax', function() if (!Request::ajax()) App::abort(404); }); - -Route::filter("openid.needs.auth.request", function () { - - $memento_service = ServiceLocator::getInstance()->getService(OpenIdServiceCatalog::MementoService); - $openid_message = $memento_service->getCurrentRequest(); - - if ($openid_message == null || !$openid_message->isValid()) - throw new InvalidOpenIdMessageException(); - $configuration_service = ServiceLocator::getInstance()->getService(OpenIdServiceCatalog::ServerConfigurationService); - $auth_request = new OpenIdAuthenticationRequest($openid_message, $configuration_service->getUserIdentityEndpointURL('@identifier')); - if (!$auth_request->isValid()) - throw new InvalidOpenIdMessageException(); -}); - -Route::filter("openid.save.request", function () { - - $memento_service = ServiceLocator::getInstance()->getService(OpenIdServiceCatalog::MementoService); - $memento_service->saveCurrentRequest(); - -}); - -Route::filter("oauth2.save.request", function () { - - $memento_service = ServiceLocator::getInstance()->getService(OAuth2ServiceCatalog::MementoService); - $memento_service->saveCurrentAuthorizationRequest(); -}); - -Route::filter("oauth2.needs.auth.request", function () { - - $memento_service = ServiceLocator::getInstance()->getService(OAuth2ServiceCatalog::MementoService); - $oauth2_message = $memento_service->getCurrentAuthorizationRequest(); - - if ($oauth2_message == null || !$oauth2_message->isValid()) - throw new InvalidAuthorizationRequestException(); -}); - Route::filter("ssl", function () { if ((!Request::secure()) && (ServerConfigurationService::getConfigValue("SSL.Enable"))) { - $openid_memento_service = ServiceLocator::getInstance()->getService(OpenIdServiceCatalog::MementoService); - $openid_memento_service->saveCurrentRequest(); - - $oauth2_memento_service = ServiceLocator::getInstance()->getService(OAuth2ServiceCatalog::MementoService); - $oauth2_memento_service->saveCurrentAuthorizationRequest(); - return Redirect::secure(Request::getRequestUri()); } }); Route::filter("oauth2.enabled",function(){ if(!ServerConfigurationService::getConfigValue("OAuth2.Enable")){ - return View::make('404'); + return Response::view('404', array(), 404); } }); @@ -179,6 +149,13 @@ Route::filter('user.owns.client.policy',function($route, $request){ $authentication_service = ServiceLocator::getInstance()->getService(UtilsServiceCatalog::AuthenticationService); $client_service = ServiceLocator::getInstance()->getService(OAuth2ServiceCatalog::ClientService); $client_id = $route->getParameter('id'); + + if(is_null($client_id)) + $client_id = $route->getParameter('client_id'); + + if(is_null($client_id)) + $client_id =Input::get('client_id',null);; + $client = $client_service->getClientByIdentifier($client_id); $user = $authentication_service->getCurrentUser(); if (is_null($client) || intval($client->getUserId()) !== intval($user->getId())) @@ -215,8 +192,6 @@ Route::filter('is.current.user',function($route, $request){ }); - - // filter to protect an api endpoint with oauth2 Route::filter('oauth2.protected.endpoint','OAuth2BearerAccessTokenRequestValidator'); @@ -235,10 +210,10 @@ Route::filter('oauth2.server.admin.json',function(){ Route::filter('oauth2.server.admin',function(){ if (Auth::guest()) { - return View::make('404'); + return Response::view('404', array(), 404); } if(!Auth::user()->isOAuth2ServerAdmin()){ - return View::make('404'); + return Response::view('404', array(), 404); } }); @@ -257,9 +232,9 @@ Route::filter('openstackid.server.admin.json',function(){ Route::filter('openstackid.server.admin',function(){ if (Auth::guest()) { - return View::make('404'); + return Response::view('404', array(), 404); } if(!Auth::user()->isOpenstackIdAdmin()){ - return View::make('404'); + return Response::view('404', array(), 404); } -}); +}); \ No newline at end of file diff --git a/app/filters/OAuth2RequestAccessTokenValidator.php b/app/filters/OAuth2RequestAccessTokenValidator.php index 2e1511ff..37b22244 100644 --- a/app/filters/OAuth2RequestAccessTokenValidator.php +++ b/app/filters/OAuth2RequestAccessTokenValidator.php @@ -13,6 +13,8 @@ use utils\services\ICheckPointService; use oauth2\IResourceServerContext; use oauth2\services\IClientService; use oauth2\models\IClient; +use utils\http\HttpContentType; +use oauth2\exceptions\RevokedAccessTokenException; /** * Class OAuth2BearerAccessTokenRequestValidator @@ -20,8 +22,7 @@ use oauth2\models\IClient; * http://tools.ietf.org/html/rfc6750 * http://tools.ietf.org/html/rfc6749#section-7 */ -class OAuth2BearerAccessTokenRequestValidator { - +final class OAuth2BearerAccessTokenRequestValidator { protected function getHeaders() { @@ -49,15 +50,53 @@ class OAuth2BearerAccessTokenRequestValidator { return $headers; } + /** + * @var IApiEndpointService + */ private $api_endpoint_service; + /** + * @var ITokenService + */ private $token_service; + /** + * @var ILogService + */ private $log_service; + /** + * @var ICheckPointService + */ private $checkpoint_service; + /** + * @var IResourceServerContext + */ private $resource_server_context; + /** + * @var array + */ private $headers; + /** + * @var IClientService + */ private $client_service; - public function __construct(IResourceServerContext $resource_server_context, IApiEndpointService $api_endpoint_service, ITokenService $token_service, ILogService $log_service, ICheckPointService $checkpoint_service, IClientService $client_service){ + /** + * @param IResourceServerContext $resource_server_context + * @param IApiEndpointService $api_endpoint_service + * @param ITokenService $token_service + * @param ILogService $log_service + * @param ICheckPointService $checkpoint_service + * @param IClientService $client_service + */ + public function __construct + ( + IResourceServerContext $resource_server_context, + IApiEndpointService $api_endpoint_service, + ITokenService $token_service, + ILogService $log_service, + ICheckPointService $checkpoint_service, + IClientService $client_service + ) + { $this->api_endpoint_service = $api_endpoint_service; $this->token_service = $token_service; $this->log_service = $log_service; @@ -75,7 +114,8 @@ class OAuth2BearerAccessTokenRequestValidator { { $url = $route->getPath(); - if(strpos($url, '/') != 0){ + if(strpos($url, '/') != 0) + { $url = '/'.$url; } $method = $request->getMethod(); @@ -83,28 +123,54 @@ class OAuth2BearerAccessTokenRequestValidator { // http://tools.ietf.org/id/draft-abarth-origin-03.html $origin = $request->headers->has('Origin') ? $request->headers->get('Origin') : null; - try{ + try + { $endpoint = $this->api_endpoint_service->getApiEndpointByUrlAndMethod($url, $method); //api endpoint must be registered on db and active - if(is_null($endpoint) || !$endpoint->isActive()){ - throw new OAuth2ResourceServerException(400,OAuth2Protocol::OAuth2Protocol_Error_InvalidRequest,sprintf('API endpoint does not exits! (%s:%s)',$url,$method)); + if(is_null($endpoint) || !$endpoint->isActive()) + { + throw new OAuth2ResourceServerException + ( + 400, + OAuth2Protocol::OAuth2Protocol_Error_InvalidRequest, + sprintf + ( + 'API endpoint does not exits! (%s:%s)', + $url, + $method + ) + ); } //check first http basic auth header $auth_header = isset($this->headers['authorization'])?$this->headers['authorization']:null; + if(!is_null($auth_header) && !empty($auth_header)) $access_token_value = BearerAccessTokenAuthorizationHeaderParser::getInstance()->parse($auth_header); - else{ + else + { // http://tools.ietf.org/html/rfc6750#section-2- 2 // if access token is not on authorization header check on POST/GET params + if($request->headers->get('content-type') !== HttpContentType::Form) + throw new OAuth2ResourceServerException + ( + 400, + OAuth2Protocol::OAuth2Protocol_Error_InvalidRequest, + 'invalid content-type' + ); $access_token_value = Input::get(OAuth2Protocol::OAuth2Protocol_AccessToken, ''); } if(is_null($access_token_value) || empty($access_token_value)) { //if access token value is not set, then error - throw new OAuth2ResourceServerException(400,OAuth2Protocol::OAuth2Protocol_Error_InvalidRequest,'missing access token'); + throw new OAuth2ResourceServerException + ( + 400, + OAuth2Protocol::OAuth2Protocol_Error_InvalidRequest, + 'missing access token' + ); } // get access token from service @@ -112,36 +178,68 @@ class OAuth2BearerAccessTokenRequestValidator { if(is_null($access_token)) throw new ExpiredAccessTokenException(sprintf('Access token %s is expired!', $access_token_value)); //check token audience - $audience = explode(' ',$access_token->getAudience()); - if((!in_array($realm,$audience))) - throw new OAuth2ResourceServerException(401,OAuth2Protocol::OAuth2Protocol_Error_InvalidToken,'access token audience does not match'); + $audience = explode(' ', $access_token->getAudience()); + + if((!in_array($realm , $audience))) + throw new OAuth2ResourceServerException + ( + 401, + OAuth2Protocol::OAuth2Protocol_Error_InvalidToken, + sprintf('access token audience does not match - current_realm %s - access token audience %s',$realm, $access_token->getAudience()) + ); //check client existence $client_id = $access_token->getClientId(); $client = $this->client_service->getClientById($client_id); if(is_null($client)) - throw new OAuth2ResourceServerException(400,OAuth2Protocol::OAuth2Protocol_Error_InvalidRequest, 'invalid client'); + throw new OAuth2ResourceServerException + ( + 400, + OAuth2Protocol::OAuth2Protocol_Error_InvalidRequest, + 'invalid client' + ); //if js client , then check if the origin is allowed .... - if($client->getApplicationType() == IClient::ApplicationType_JS_Client){ + if($client->getApplicationType() == IClient::ApplicationType_JS_Client) + { if(!$client->isOriginAllowed($origin)) - throw new OAuth2ResourceServerException(403,OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient, 'invalid origin'); + throw new OAuth2ResourceServerException + ( + 403, + OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient, + 'invalid origin' + ); } //check scopes $endpoint_scopes = explode(' ',$endpoint->getScope()); $token_scopes = explode(' ',$access_token->getScope()); + //check token available scopes vs. endpoint scopes if (count(array_intersect($endpoint_scopes, $token_scopes)) == 0) { - $this->log_service->error_msg(sprintf('access token scopes (%s) does not allow to access to api url %s , needed scopes %s',$access_token->getScope(),$url,implode(' OR ',$endpoint_scopes) )); + $this->log_service->error_msg + ( + sprintf + ( + 'access token scopes (%s) does not allow to access to api url %s , needed scopes %s', + $access_token->getScope(), + $url, + implode(' OR ',$endpoint_scopes) + ) + ); - throw new OAuth2ResourceServerException(403,OAuth2Protocol::OAuth2Protocol_Error_InsufficientScope, + throw new OAuth2ResourceServerException + ( + 403, + OAuth2Protocol::OAuth2Protocol_Error_InsufficientScope, 'the request requires higher privileges than provided by the access token', - implode(' ',$endpoint_scopes)); + implode(' ',$endpoint_scopes) + ); } - $context = array( + $context = array + ( 'access_token' => $access_token_value, 'expires_in' => $access_token->getRemainingLifetime(), 'client_id' => $client_id, @@ -154,7 +252,8 @@ class OAuth2BearerAccessTokenRequestValidator { $this->resource_server_context->setAuthorizationContext($context); } - catch(OAuth2ResourceServerException $ex1){ + catch(OAuth2ResourceServerException $ex1) + { $this->log_service->error($ex1); $this->checkpoint_service->trackException($ex1); $response = new OAuth2WWWAuthenticateErrorResponse($realm, @@ -167,7 +266,8 @@ class OAuth2BearerAccessTokenRequestValidator { $http_response->header('WWW-Authenticate',$response->getWWWAuthenticateHeaderValue()); return $http_response; } - catch(InvalidGrantTypeException $ex2){ + catch(InvalidGrantTypeException $ex2) + { $this->log_service->error($ex2); $this->checkpoint_service->trackException($ex2); $response = new OAuth2WWWAuthenticateErrorResponse($realm, @@ -180,7 +280,8 @@ class OAuth2BearerAccessTokenRequestValidator { $http_response->header('WWW-Authenticate',$response->getWWWAuthenticateHeaderValue()); return $http_response; } - catch(ExpiredAccessTokenException $ex3){ + catch(ExpiredAccessTokenException $ex3) + { $this->log_service->error($ex3); $this->checkpoint_service->trackException($ex3); $response = new OAuth2WWWAuthenticateErrorResponse($realm, @@ -193,7 +294,22 @@ class OAuth2BearerAccessTokenRequestValidator { $http_response->header('WWW-Authenticate',$response->getWWWAuthenticateHeaderValue()); return $http_response; } - catch(Exception $ex){ + catch(RevokedAccessTokenException $ex4) + { + $this->log_service->error($ex4); + $this->checkpoint_service->trackException($ex4); + $response = new OAuth2WWWAuthenticateErrorResponse($realm, + OAuth2Protocol::OAuth2Protocol_Error_InvalidToken, + 'the access token provided is expired, revoked, malformed, or invalid for other reasons.', + null, + 401 + ); + $http_response = Response::json($response->getContent(), $response->getHttpCode()); + $http_response->header('WWW-Authenticate',$response->getWWWAuthenticateHeaderValue()); + return $http_response; + } + catch(Exception $ex) + { $this->log_service->error($ex); $this->checkpoint_service->trackException($ex); $response = new OAuth2WWWAuthenticateErrorResponse($realm, diff --git a/app/lang/en/validation.php b/app/lang/en/validation.php index 23bc2a16..bc024339 100644 --- a/app/lang/en/validation.php +++ b/app/lang/en/validation.php @@ -2,107 +2,113 @@ return array( - /* - |-------------------------------------------------------------------------- - | Validation Language Lines - |-------------------------------------------------------------------------- - | - | The following language lines contain the default error messages used by - | the validator class. Some of these rules have multiple versions such - | such as the size rules. Feel free to tweak each of these messages. - | - */ + /* + |-------------------------------------------------------------------------- + | Validation Language Lines + |-------------------------------------------------------------------------- + | + | The following language lines contain the default error messages used by + | the validator class. Some of these rules have multiple versions such + | such as the size rules. Feel free to tweak each of these messages. + | + */ - "accepted" => "The :attribute must be accepted.", - "active_url" => "The :attribute is not a valid URL.", - "after" => "The :attribute must be a date after :date.", - "alpha" => "The :attribute may only contain letters.", - "alpha_dash" => "The :attribute may only contain letters, numbers, and dashes.", - "alpha_num" => "The :attribute may only contain letters and numbers.", - "array" => "The :attribute must be an array.", - "before" => "The :attribute must be a date before :date.", - "between" => array( - "numeric" => "The :attribute must be between :min - :max.", - "file" => "The :attribute must be between :min - :max kilobytes.", - "string" => "The :attribute must be between :min - :max characters.", - "array" => "The :attribute must have between :min - :max items.", - ), - "confirmed" => "The :attribute confirmation does not match.", - "date" => "The :attribute is not a valid date.", - "date_format" => "The :attribute does not match the format :format.", - "different" => "The :attribute and :other must be different.", - "digits" => "The :attribute must be :digits digits.", - "digits_between" => "The :attribute must be between :min and :max digits.", - "email" => "The :attribute format is invalid.", - "exists" => "The selected :attribute is invalid.", - "image" => "The :attribute must be an image.", - "in" => "The selected :attribute is invalid.", - "integer" => "The :attribute must be an integer.", - "ip" => "The :attribute must be a valid IP address.", - "max" => array( - "numeric" => "The :attribute may not be greater than :max.", - "file" => "The :attribute may not be greater than :max kilobytes.", - "string" => "The :attribute may not be greater than :max characters.", - "array" => "The :attribute may not have more than :max items.", - ), - "mimes" => "The :attribute must be a file of type: :values.", - "min" => array( - "numeric" => "The :attribute must be at least :min.", - "file" => "The :attribute must be at least :min kilobytes.", - "string" => "The :attribute must be at least :min characters.", - "array" => "The :attribute must have at least :min items.", - ), - "not_in" => "The selected :attribute is invalid.", - "numeric" => "The :attribute must be a number.", - "regex" => "The :attribute format is invalid.", - "required" => "The :attribute field is required.", - "required_if" => "The :attribute field is required when :other is :value.", - "required_with" => "The :attribute field is required when :values is present.", - "required_without" => "The :attribute field is required when :values is not present.", - "same" => "The :attribute and :other must match.", - "size" => array( - "numeric" => "The :attribute must be :size.", - "file" => "The :attribute must be :size kilobytes.", - "string" => "The :attribute must be :size characters.", - "array" => "The :attribute must contain :size items.", - ), - "unique" => "The :attribute has already been taken.", - "url" => "The :attribute format is invalid.", + "accepted" => "The :attribute must be accepted.", + "active_url" => "The :attribute is not a valid URL.", + "after" => "The :attribute must be a date after :date.", + "alpha" => "The :attribute may only contain letters.", + "alpha_dash" => "The :attribute may only contain letters, numbers, and dashes.", + "alpha_num" => "The :attribute may only contain letters and numbers.", + "array" => "The :attribute must be an array.", + "before" => "The :attribute must be a date before :date.", + "between" => array( + "numeric" => "The :attribute must be between :min - :max.", + "file" => "The :attribute must be between :min - :max kilobytes.", + "string" => "The :attribute must be between :min - :max characters.", + "array" => "The :attribute must have between :min - :max items.", + ), + "confirmed" => "The :attribute confirmation does not match.", + "date" => "The :attribute is not a valid date.", + "date_format" => "The :attribute does not match the format :format.", + "different" => "The :attribute and :other must be different.", + "digits" => "The :attribute must be :digits digits.", + "digits_between" => "The :attribute must be between :min and :max digits.", + "email" => "The :attribute format is invalid.", + "exists" => "The selected :attribute is invalid.", + "image" => "The :attribute must be an image.", + "in" => "The selected :attribute is invalid.", + "integer" => "The :attribute must be an integer.", + "ip" => "The :attribute must be a valid IP address.", + "max" => array( + "numeric" => "The :attribute may not be greater than :max.", + "file" => "The :attribute may not be greater than :max kilobytes.", + "string" => "The :attribute may not be greater than :max characters.", + "array" => "The :attribute may not have more than :max items.", + ), + "mimes" => "The :attribute must be a file of type: :values.", + "min" => array( + "numeric" => "The :attribute must be at least :min.", + "file" => "The :attribute must be at least :min kilobytes.", + "string" => "The :attribute must be at least :min characters.", + "array" => "The :attribute must have at least :min items.", + ), + "not_in" => "The selected :attribute is invalid.", + "numeric" => "The :attribute must be a number.", + "regex" => "The :attribute format is invalid.", + "required" => "The :attribute field is required.", + "required_if" => "The :attribute field is required when :other is :value.", + "required_with" => "The :attribute field is required when :values is present.", + "required_without" => "The :attribute field is required when :values is not present.", + "same" => "The :attribute and :other must match.", + "size" => array( + "numeric" => "The :attribute must be :size.", + "file" => "The :attribute must be :size kilobytes.", + "string" => "The :attribute must be :size characters.", + "array" => "The :attribute must contain :size items.", + ), + "unique" => "The :attribute has already been taken.", + "url" => "The :attribute format is invalid.", + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ - /* - |-------------------------------------------------------------------------- - | Custom Validation Language Lines - |-------------------------------------------------------------------------- - | - | Here you may specify custom validation messages for attributes using the - | convention "attribute.rule" to name the lines. This makes it quick to - | specify a specific custom language line for a given attribute rule. - | - */ + 'custom' => array(), + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap attribute place-holders + | with something more reader friendly such as E-Mail Address instead + | of "email". This simply helps us make messages a little cleaner. + | + */ - 'custom' => array(), - - /* - |-------------------------------------------------------------------------- - | Custom Validation Attributes - |-------------------------------------------------------------------------- - | - | The following language lines are used to swap attribute place-holders - | with something more reader friendly such as E-Mail Address instead - | of "email". This simply helps us make messages a little cleaner. - | - */ - - 'attributes' => array(), + 'attributes' => array(), //custom messages - 'boolean' => "The :attribute must be a boolean.", - 'text' => "The :attribute may only contain text.", - 'httpmethod' => "The :attribute must be one of the following values 'GET', 'HEAD','POST','PUT','DELETE','TRACE','CONNECT' OR 'OPTIONS'.", - 'route' => "The :attribute may be a valid http route.", - 'host' => "The :attribute may be a valid host name.", - 'scopename' => "The :attribute may be a valid scope name.", + 'boolean' => "The :attribute must be a boolean.", + 'text' => "The :attribute may only contain text.", + 'httpmethod' => "The :attribute must be one of the following values 'GET', 'HEAD','POST','PUT','DELETE','TRACE','CONNECT' OR 'OPTIONS'.", + 'route' => "The :attribute may be a valid http route.", + 'host' => "The :attribute may be a valid host name.", + 'scopename' => "The :attribute may be a valid scope name.", 'applicationtype' => "The :attribute may be a valid application type.", - 'sslurl' => "The :attribute may be a valid URL under ssl schema.", - 'sslorigin' => "The :attribute may be a valid HTTP origin under ssl schema.", - 'freetext' => "The :attribute may only contain text." + 'sslurl' => "The :attribute may be a valid URL under ssl schema.", + 'sslorigin' => "The :attribute may be a valid HTTP origin under ssl schema.", + 'freetext' => "The :attribute may only contain text.", + 'public_key_pem' => "The :attribute has not a valid PCK#1/PCK#8 public key format.", + 'private_key_pem' => "The :attribute has not a valid PCK#1/PCK#8 private key format.", + 'public_key_usage' => "The :attribute has not a valid key usage (enc, sig).", + 'public_key_type'=> "The :attribute has not a valid key type (RSA).", + 'private_key_password' => "The :attribute is not the right password of the PEM private key.", + 'private_key_pem_length'=> "The :attribute has not a valid private key length ( at least 2048 bits ).", + 'public_key_pem_length'=> "The :attribute has not a valid public key length ( at least 2048 bits ).", + 'key_alg' => "The :attribute has not a valid alg parameter for the key usage.", ); diff --git a/app/libs/auth/AuthService.php b/app/libs/auth/AuthService.php index b894f076..37e9f9fe 100644 --- a/app/libs/auth/AuthService.php +++ b/app/libs/auth/AuthService.php @@ -3,42 +3,69 @@ namespace auth; use Auth; +use Config; +use Crypt; +use Cookie; +use jwe\compression_algorithms\CompressionAlgorithms_Registry; +use jwe\compression_algorithms\CompressionAlgorithmsNames; use Member; +use oauth2\models\IClient; +use oauth2\services\IPrincipalService; +use openid\model\IOpenIdUser; +use openid\services\IUserService; use Session; +use utils\Base64UrlRepresentation; use utils\services\IAuthService; +use utils\services\ICacheService; -class AuthService implements IAuthService +/** + * Class AuthService + * @package auth + */ +final class AuthService implements IAuthService { + /** + * @var IPrincipalService + */ + private $principal_service; + + /** + * @var IUserService + */ + private $user_service; + + + /** + * @var ICacheService + */ + private $cache_service; + + public function __construct + ( + IPrincipalService $principal_service, + IUserService $user_service, + ICacheService $cache_service + ) + { + $this->principal_service = $principal_service; + $this->user_service = $user_service; + $this->cache_service = $cache_service; + } /** * @return mixed */ public function isUserLogged() { - $res = Auth::check(); - if ($res) { - $user = $this->getCurrentUser(); - if (!$user->hasAssociatedMember()) { - $this->logout(); - $res = false; - } - } - - return $res; + return Auth::check(); } /** - * @return mixed + * @return IOpenIdUser */ public function getCurrentUser() { - $user = Auth::user(); - if (!is_null($user) && !$user->hasAssociatedMember()) { - $this->logout(); - $user = null; - } - - return $user; + return Auth::user(); } /** @@ -49,12 +76,26 @@ class AuthService implements IAuthService */ public function login($username, $password, $remember_me) { - return Auth::attempt(array('username' => $username, 'password' => $password), $remember_me); + $res = Auth::attempt(array('username' => $username, 'password' => $password), $remember_me); + + if ($res) + { + $this->principal_service->clear(); + $this->principal_service->register + ( + $this->getCurrentUser()->getId(), + time() + ); + } + + return $res; } public function logout() { Auth::logout(); + $this->principal_service->clear(); + Cookie::queue('rps', null, $minutes = -2628000, $path = '/', $domain = null, $secure = false, $httpOnly = false); } /** @@ -62,7 +103,8 @@ class AuthService implements IAuthService */ public function getUserAuthorizationResponse() { - if (Session::has("openid.authorization.response")) { + if (Session::has("openid.authorization.response")) + { $value = Session::get("openid.authorization.response"); return $value; @@ -73,16 +115,23 @@ class AuthService implements IAuthService public function clearUserAuthorizationResponse() { - if (Session::has("openid.authorization.response")) { + if (Session::has("openid.authorization.response")) + { Session::remove("openid.authorization.response"); + Session::save(); } } public function setUserAuthorizationResponse($auth_response) { Session::set("openid.authorization.response", $auth_response); + Session::save(); } + /** + * @param string $openid + * @return IOpenIdUser + */ public function getUserByOpenId($openid) { $user = User::where('identifier', '=', $openid)->first(); @@ -90,16 +139,32 @@ class AuthService implements IAuthService return $user; } + /** + * @param string $username + * @return bool|IOpenIdUser + */ public function getUserByUsername($username) { $member = Member::where('Email', '=', $username)->first(); - if (!is_null($member)) { - return User::where('external_identifier', '=', $member->ID)->first(); + if (!is_null($member)) + { + $user = User::where('external_identifier', '=', $member->ID)->first(); + + if(!$user) + { + $user = $this->user_service->buildUser($member); + } + + return $user; } return false; } + /** + * @param int $id + * @return IOpenIdUser + */ public function getUserById($id) { return User::find($id); @@ -111,22 +176,164 @@ class AuthService implements IAuthService { if (Session::has("openstackid.authentication.response")) { $value = Session::get("openstackid.authentication.response"); - return $value; } - return IAuthService::AuthenticationResponse_None; } public function setUserAuthenticationResponse($auth_response) { Session::set("openstackid.authentication.response", $auth_response); + Session::save(); } public function clearUserAuthenticationResponse() { - if (Session::has("openstackid.authentication.response")) { + if (Session::has("openstackid.authentication.response")) + { Session::remove("openstackid.authentication.response"); + Session::save(); + } + } + + /** + * @param int $user_id + * @return string + */ + public function unwrapUserId($user_id) + { + $user = $this->getUserByExternaldId($user_id); + if(!is_null($user)) + return $user_id; + + $unwrapped_name = $this->decrypt($user_id); + $parts = explode(':', $unwrapped_name); + return intval($parts[1]); + } + + /** + * @param int $user_id + * @param IClient $client + * @return string + */ + public function wrapUserId($user_id, IClient $client) + { + if($client->getSubjectType() === IClient::SubjectType_Public) + return $user_id; + else + { + $wrapped_name = sprintf('%s:%s', $client->getClientId(), $user_id); + return $this->encrypt($wrapped_name); + } + } + + /** + * @param string $value + * @return String + */ + private function encrypt($value) + { + return base64_encode(Crypt::encrypt($value)); + } + + /** + * @param string $value + * @return String + */ + private function decrypt($value) + { + $value = base64_decode($value); + return Crypt::decrypt($value); + } + + /** + * @param int $external_id + * @return IOpenIdUser + */ + public function getUserByExternaldId($external_id) + { + $member = Member::where('ID', '=', $external_id)->first(); + if (!is_null($member)) + { + $user = User::where('external_identifier', '=', $member->ID)->first(); + + if(!$user) + { + $user = $this->user_service->buildUser($member); + } + + return $user; + } + + return false; + } + + /** + * @return string + */ + public function getSessionId() + { + return Session::getId(); + } + + /** + * @param $client_id + * @return void + */ + public function registerRPLogin($client_id) + { + $rps = Cookie::get('rps'); + $zlib = CompressionAlgorithms_Registry::getInstance()->get(CompressionAlgorithmsNames::ZLib); + + if(!empty($rps)) + { + $rps = $this->decrypt($rps); + $rps = $zlib->uncompress($rps); + $rps .= '|'; + } + + if(!str_contains($rps, $client_id)) + $rps .= $client_id; + + $rps = $zlib->compress($rps); + $rps = $this->encrypt($rps); + + Cookie::queue('rps', $rps, $minutes = 2628000, $path = '/', $domain = null, $secure = false, $httpOnly = false); + } + + /** + * @return string[] + */ + public function getLoggedRPs() + { + $rps = Cookie::get('rps'); + $zlib = CompressionAlgorithms_Registry::getInstance()->get(CompressionAlgorithmsNames::ZLib); + + if(!empty($rps)) + { + $rps = $this->decrypt($rps); + $rps = $zlib->uncompress($rps); + return explode('|', $rps); + } + return null; + } + + /** + * @param string $jti + * @return void + */ + public function reloadSession($jti) + { + $session_id = $this->cache_service->getSingleValue($jti); + if(empty($session_id)) throw new \Exception('session not found!'); + Session::setId(Crypt::decrypt($session_id)); + Session::start(); + if(!Auth::check()) + { + $user_id = $this->principal_service->get()->getUserId(); + $user = $this->getUserById($user_id); + if(is_null($user)) throw new \Exception('user not found!'); + Auth::login($user); } } } \ No newline at end of file diff --git a/app/libs/auth/AuthenticationServiceProvider.php b/app/libs/auth/AuthenticationServiceProvider.php index 112b23a5..7bf13808 100644 --- a/app/libs/auth/AuthenticationServiceProvider.php +++ b/app/libs/auth/AuthenticationServiceProvider.php @@ -2,9 +2,9 @@ namespace auth; +use App; use Illuminate\Support\ServiceProvider; use utils\services\UtilsServiceCatalog; -use App; class AuthenticationServiceProvider extends ServiceProvider { @@ -16,7 +16,7 @@ class AuthenticationServiceProvider extends ServiceProvider public function register() { App::singleton(UtilsServiceCatalog::AuthenticationService, 'auth\\AuthService'); - App::singleton('auth\\IAuthenticationExtensionService', 'auth\\AuthenticationExtensionService'); + App::singleton('auth\\IAuthenticationExtensionService', 'auth\\AuthenticationExtensionService'); } public function provides() diff --git a/app/libs/auth/CustomAuthProvider.php b/app/libs/auth/CustomAuthProvider.php index 4572fb57..e42e8b7b 100644 --- a/app/libs/auth/CustomAuthProvider.php +++ b/app/libs/auth/CustomAuthProvider.php @@ -86,11 +86,10 @@ class CustomAuthProvider implements UserProviderInterface { try { //here we do the manuel join between 2 DB, (openid and SS db) - $user = $this->user_repository->getByExternalId($identifier); + $user = $this->user_repository->getByExternalId($identifier); $member = $this->member_repository->get($identifier); - if (!is_null($member) && !is_null($user)) { + if (!is_null($member) && $member->canLogin() && !is_null($user)) { $user->setMember($member); - return $user; } @@ -110,27 +109,35 @@ class CustomAuthProvider implements UserProviderInterface */ public function retrieveByCredentials(array $credentials) { - $user_service = $this->user_service; + $user_service = $this->user_service; $auth_extension_service = $this->auth_extension_service; - $user_repository = $this->user_repository; - $member_repository = $this->member_repository; - - try { + $user_repository = $this->user_repository; + $member_repository = $this->member_repository; + $log_service = $this->log_service; + $checkpoint_service = $this->checkpoint_service; - $user = $this->tx_service->transaction(function () use ( - $credentials, - $user_repository, - $member_repository, - $user_service, - $auth_extension_service - ) { + return $this->tx_service->transaction(function () use ( + $credentials, + $user_repository, + $member_repository, + $user_service, + $auth_extension_service, + $log_service, + $checkpoint_service + ) { - if (!isset($credentials['username']) || !isset($credentials['password'])) { + $user = null; + + try + { + + if (!isset($credentials['username']) || !isset($credentials['password'])) + { throw new AuthenticationException("invalid crendentials"); } - $email = $credentials['username']; + $email = $credentials['username']; $password = $credentials['password']; //get SS member @@ -142,10 +149,15 @@ class CustomAuthProvider implements UserProviderInterface throw new AuthenticationException(sprintf("member %s does not exists!", $email)); } + if(!$member->canLogin()) + { + throw new AuthenticationException(sprintf("member %s does not exists!", $email)); + } $valid_password = $member->checkPassword($password); - if (!$valid_password) { + if (!$valid_password) + { throw new AuthenticationInvalidPasswordAttemptException($email, sprintf("invalid login attempt for user %s ", $email)); } @@ -153,54 +165,44 @@ class CustomAuthProvider implements UserProviderInterface $user = $user_repository->getByExternalId($member->ID); - //if user does not exists, then create it - if (is_null($user)) { - //create user - $user = new User(); - $user->external_identifier = $member->ID; - $user->identifier = $member->ID; - $user->last_login_date = gmdate("Y-m-d H:i:s", time()); - $user->active = true; - $user->lock = false; - $user->login_failed_attempt = 0; - $user_repository->add($user); + if (!$user) { + $user = $user_service->buildUser($member); } //check user status... if ($user->lock || !$user->active) { Log::warning(sprintf("user %s is on lock state", $email)); - throw new AuthenticationLockedUserLoginAttempt($email, sprintf("user %s is on lock state", $email)); + throw new AuthenticationLockedUserLoginAttempt($email, + sprintf("user %s is on lock state", $email)); } - - $user_name = strtolower($member->FirstName . "." . $member->Surname); - //do association between user and member - $user_service->associateUser($user, $user_name); - //update user fields - $user->last_login_date = gmdate("Y-m-d H:i:s", time()); + $user->last_login_date = gmdate("Y-m-d H:i:s", time()); $user->login_failed_attempt = 0; - $user->active = true; - $user->lock = false; + $user->active = true; + $user->lock = false; $user_repository->update($user); $user->setMember($member); $auth_extensions = $auth_extension_service->getExtensions(); - foreach ($auth_extensions as $auth_extension) { + foreach ($auth_extensions as $auth_extension) + { $auth_extension->process($user); } - return $user; - }); - } catch (Exception $ex) { - $this->checkpoint_service->trackException($ex); - $this->log_service->error($ex); - $user = null; - } + } + catch (Exception $ex) + { + $checkpoint_service->trackException($ex); + $log_service->error($ex); + $user = null; + } + + return $user; + }); - return $user; } @@ -217,12 +219,12 @@ class CustomAuthProvider implements UserProviderInterface throw new AuthenticationException("invalid crendentials"); } try { - $email = $credentials['username']; + $email = $credentials['username']; $password = $credentials['password']; - $member = $this->member_repository->getByEmail($email); + $member = $this->member_repository->getByEmail($email); - if (!$member || !$member->checkPassword($password)) { + if (!$member || !$member->canLogin() || !$member->checkPassword($password)) { return false; } @@ -248,7 +250,6 @@ class CustomAuthProvider implements UserProviderInterface */ public function retrieveByToken($identifier, $token) { - return $this->user_repository->getByToken($identifier, $token); } @@ -263,5 +264,5 @@ class CustomAuthProvider implements UserProviderInterface $user->setAttribute($user->getRememberTokenName(), $token); $user->save(); - } + } } \ No newline at end of file diff --git a/app/libs/auth/IMemberRepository.php b/app/libs/auth/IMemberRepository.php index 506298b4..7e6f68a3 100644 --- a/app/libs/auth/IMemberRepository.php +++ b/app/libs/auth/IMemberRepository.php @@ -1,17 +1,24 @@ member = $member; } - private function getAssociatedMember() { if (is_null($this->member)) { @@ -70,15 +72,6 @@ class User extends BaseModelEloquent implements UserInterface, IOpenIdUser, IOAu return $this->member; } - /** - * @return bool - */ - public function hasAssociatedMember() - { - $this->getAssociatedMember(); - return !is_null($this->member); - } - /** * Get the unique identifier for the user. * the one that is saved as session id on vendor/laravel/framework/src/Illuminate/Auth/Guard.php @@ -110,10 +103,18 @@ class User extends BaseModelEloquent implements UserInterface, IOpenIdUser, IOAu public function getEmail() { $this->getAssociatedMember(); - + if(is_null($this->member)) return false; return $this->member->Email; } + /** + * @return string + */ + public function getEmailAttribute() + { + return $this->getEmail(); + } + public function getFullName() { return $this->getFirstName() . " " . $this->getLastName(); @@ -175,7 +176,7 @@ class User extends BaseModelEloquent implements UserInterface, IOpenIdUser, IOAu public function getId() { - return $this->id; + return (int)$this->id; } public function getShowProfileFullName() @@ -264,7 +265,11 @@ class User extends BaseModelEloquent implements UserInterface, IOpenIdUser, IOAu { $this->getAssociatedMember(); - return sprintf("%s, %s ", $this->member->Address, $this->member->Suburb); + $street_address = $this->member->Address; + $suburb = $this->member->Suburb; + if(!empty($suburb)) + $street_address .= ', '.$suburb; + return $street_address; } public function getRegion() @@ -307,4 +312,73 @@ class User extends BaseModelEloquent implements UserInterface, IOpenIdUser, IOAu { return 'remember_token'; } + + /** + * @return int + */ + public function getExternalIdentifier() + { + return $this->getAuthIdentifier(); + } + + /** + * @return string + */ + public function getFormattedAddress() + { + $street = $this->getStreetAddress(); + $region = $this->getRegion(); + $city = $this->getLocality(); + $zip_code = $this->getPostalCode(); + $country = $this->getCountry(); + + $complete = $street; + + if(!empty($city)) + $complete .= ', '.$city; + + if(!empty($region)) + $complete .= ', '.$region; + + if(!empty($zip_code)) + $complete .= ', '.$zip_code; + + if(!empty($country)) + $complete .= ', '.$country; + + return $complete; + } + + /** + * @return IApiScopeGroup[] + */ + public function getGroups() + { + return $this->groups()->where('active','=',true)->get(); + } + + /** + * @return mixed + */ + public function groups() + { + return $this->belongsToMany('ApiScopeGroup','oauth2_api_scope_group_users','user_id', 'group_id'); + } + + /** + * @return IApiScope[] + */ + public function getGroupScopes() + { + $scopes = array(); + $map = array(); + foreach($this->groups()->where('active','=',true)->get() as $group){ + foreach($group->scopes()->get() as $scope) + { + if(!isset($map[$scope->id])) + array_push($scopes, $scope); + } + } + return $scopes; + } } \ No newline at end of file diff --git a/app/libs/oauth2/AddressClaim.php b/app/libs/oauth2/AddressClaim.php new file mode 100644 index 00000000..7ceb16d2 --- /dev/null +++ b/app/libs/oauth2/AddressClaim.php @@ -0,0 +1,66 @@ +container[$param])?$this->container[$param]:null; + return isset($this->container[$param])? $this->container[$param] : null; + } + + /** + * @param string $param + * @param mixed $value + * @return $this + */ + public function setParam($param, $value) + { + $this->container[$param] = $value; + return $this; + } + + /** + * @return OAuth2RequestMemento + */ + public function createMemento(){ + return OAuth2RequestMemento::buildFromRequest($this); + } + + /** + * @param OAuth2RequestMemento $memento + * @return $this + */ + public function setMemento(OAuth2RequestMemento $memento){ + $this->container = $memento->getState(); + return $this; + } + + /** + * @param OAuth2RequestMemento $memento + * @return OAuth2Message + */ + static public function buildFromMemento(OAuth2RequestMemento $memento){ + $msg = new self; + $msg->setMemento($memento); + return $msg; } } \ No newline at end of file diff --git a/app/libs/oauth2/OAuth2Protocol.php b/app/libs/oauth2/OAuth2Protocol.php index 3f60a365..1c847de7 100644 --- a/app/libs/oauth2/OAuth2Protocol.php +++ b/app/libs/oauth2/OAuth2Protocol.php @@ -2,56 +2,53 @@ namespace oauth2; -//endpoints +use Exception; +use jwa\JSONWebSignatureAndEncryptionAlgorithms; +use jwk\impl\JWKSet; +use jwk\impl\RSAJWKFactory; +use jwk\impl\RSAJWKPEMPrivateKeySpecification; +use jwk\JSONWebKeyVisibility; +use jws\IJWS; +use jwt\IJWT; +use jwt\impl\UnsecuredJWT; +use oauth2\discovery\DiscoveryDocumentBuilder; +use oauth2\discovery\IOpenIDProviderConfigurationService; use oauth2\endpoints\AuthorizationEndpoint; use oauth2\endpoints\TokenEndpoint; use oauth2\endpoints\TokenIntrospectionEndpoint; use oauth2\endpoints\TokenRevocationEndpoint; - -//exceptions -use Exception; -use oauth2\exceptions\AccessDeniedException; -use oauth2\exceptions\BearerTokenDisclosureAttemptException; -use oauth2\exceptions\ExpiredAuthorizationCodeException; -use oauth2\exceptions\InvalidAccessTokenException; -use oauth2\exceptions\InvalidApplicationType; -use oauth2\exceptions\InvalidAuthorizationCodeException; -use oauth2\exceptions\InvalidClientException; -use oauth2\exceptions\InvalidClientType; -use oauth2\exceptions\InvalidGrantTypeException; -use oauth2\exceptions\InvalidOAuth2Request; -use oauth2\exceptions\LockedClientException; -use oauth2\exceptions\MissingClientIdParam; -use oauth2\exceptions\OAuth2GenericException; -use oauth2\exceptions\ReplayAttackException; -use oauth2\exceptions\ScopeNotAllowedException; -use oauth2\exceptions\UnAuthorizedClientException; -use oauth2\exceptions\UnsupportedResponseTypeException; -use oauth2\exceptions\UriNotAllowedException; -use oauth2\exceptions\MissingClientAuthorizationInfo; -use oauth2\exceptions\InvalidRedeemAuthCodeException; -use oauth2\exceptions\InvalidClientCredentials; use oauth2\exceptions\ExpiredAccessTokenException; - -//grant types +use oauth2\exceptions\InvalidClientException; +use oauth2\exceptions\InvalidOAuth2Request; +use oauth2\exceptions\OAuth2BaseException; +use oauth2\exceptions\UriNotAllowedException; use oauth2\grant_types\AuthorizationCodeGrantType; +use oauth2\grant_types\ClientCredentialsGrantType; +use oauth2\grant_types\HybridGrantType; use oauth2\grant_types\ImplicitGrantType; use oauth2\grant_types\RefreshBearerTokenGrantType; -use oauth2\grant_types\ClientCredentialsGrantType; - +use oauth2\models\IClient; +use oauth2\repositories\IServerPrivateKeyRepository; +use oauth2\requests\OAuth2EndSessionRequest; +use oauth2\requests\OAuth2LogoutRequest; use oauth2\requests\OAuth2Request; - +use oauth2\resource_server\IUserService; use oauth2\responses\OAuth2DirectErrorResponse; use oauth2\responses\OAuth2IndirectErrorResponse; +use oauth2\responses\OAuth2LogoutResponse; use oauth2\responses\OAuth2TokenRevocationResponse; - use oauth2\services\IApiScopeService; +use oauth2\services\IClientJWKSetReader; use oauth2\services\IClientService; -use oauth2\services\IMementoOAuth2AuthenticationRequestService; +use oauth2\services\IMementoOAuth2SerializerService; +use oauth2\services\IPrincipalService; +use oauth2\services\ISecurityContextService; use oauth2\services\ITokenService; +use oauth2\services\IUserConsentService; use oauth2\strategies\IOAuth2AuthenticationStrategy; use oauth2\strategies\OAuth2IndirectErrorResponseFactoryMethod; -use oauth2\services\IUserConsentService; +use utils\ArrayUtils; +use utils\factories\BasicJWTFactory; use utils\services\IAuthService; use utils\services\ICheckPointService; use utils\services\ILogService; @@ -61,40 +58,459 @@ use utils\services\ILogService; * Implementation of http://tools.ietf.org/html/rfc6749 * @package oauth2 */ -class OAuth2Protocol implements IOAuth2Protocol +final class OAuth2Protocol implements IOAuth2Protocol { - const OAuth2Protocol_GrantType_AuthCode = 'authorization_code'; - const OAuth2Protocol_GrantType_Implicit = 'implicit'; + const OAuth2Protocol_Scope_Delimiter = ' '; + const OAuth2Protocol_ResponseType_Delimiter = ' '; + + const OAuth2Protocol_GrantType_AuthCode = 'authorization_code'; + const OAuth2Protocol_GrantType_Implicit = 'implicit'; + const OAuth2Protocol_GrantType_Hybrid = 'hybrid'; + const OAuth2Protocol_GrantType_ResourceOwner_Password = 'password'; - const OAuth2Protocol_GrantType_ClientCredentials = 'client_credentials'; - const OAuth2Protocol_GrantType_RefreshToken = 'refresh_token'; - const OAuth2Protocol_ResponseType_Code = 'code'; - const OAuth2Protocol_ResponseType_Token = 'token'; + const OAuth2Protocol_GrantType_ClientCredentials = 'client_credentials'; + const OAuth2Protocol_GrantType_RefreshToken = 'refresh_token'; + + const OAuth2Protocol_ResponseType_Code = 'code'; + const OAuth2Protocol_ResponseType_Token = 'token'; + const OAuth2Protocol_ResponseType_IdToken = 'id_token'; + const OAuth2Protocol_ResponseType_None = 'none'; + + /** + * The OAuth 2.0 specification allows for registration of space-separated response_type parameter values. If a + * Response Type contains one of more space characters (%20), it is compared as a space-delimited list of values + * in which the order of values does not matter. + */ const OAuth2Protocol_ResponseType = 'response_type'; - const OAuth2Protocol_ClientId = 'client_id'; - const OAuth2Protocol_UserId = 'user_id'; + /** + * http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes + * Informs the Authorization Server of the mechanism to be used for returning Authorization Response parameters from + * the Authorization Endpoint. This use of this parameter is NOT RECOMMENDED with a value that specifies the same + * Response Mode as the default Response Mode for the Response Type used. + */ + const OAuth2Protocol_ResponseMode = 'response_mode'; + + /** + * In this mode, Authorization Response parameters are encoded in the query string added to the redirect_uri when + * redirecting back to the Client. + */ + const OAuth2Protocol_ResponseMode_Query = 'query'; + + /** + * In this mode, Authorization Response parameters are encoded in the fragment added to the redirect_uri when + * redirecting back to the Client. + */ + const OAuth2Protocol_ResponseMode_Fragment = 'fragment'; + + /** + * http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html + * In this mode, Authorization Response parameters are encoded as HTML form values that are auto-submitted in the + * User Agent, and thus are transmitted via the HTTP POST method to the Client, with the result parameters being + * encoded in the body using the application/x-www-form-urlencoded format. The action attribute of the form MUST be + * the Client's Redirection URI. The method of the form attribute MUST be POST. Because the Authorization Response + * is intended to be used only once, the Authorization Server MUST instruct the User Agent (and any intermediaries) + * not to store or reuse the content of the response. + */ + const OAuth2Protocol_ResponseMode_FormPost = 'form_post'; + + const OAuth2Protocol_ResponseMode_Direct = 'direct'; + + + static public $valid_response_modes = array + ( + self::OAuth2Protocol_ResponseMode_Query, + self::OAuth2Protocol_ResponseMode_Fragment, + self::OAuth2Protocol_ResponseMode_FormPost, + self::OAuth2Protocol_ResponseMode_Direct + ); + + /** + * http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes + * + * Each Response Type value also defines a default Response Mode mechanism to be used, + * if no Response Mode is specified using the request parameter. + * For purposes of this specification, the default Response Mode for the OAuth 2.0 code Response Type is the query + * encoding. For purposes of this specification, the default Response Mode for the OAuth 2.0 token Response Type is + * the fragment encoding. + * + * @param array $response_type + * @return string + */ + static public function getDefaultResponseMode(array $response_type) + { + + if(count(array_diff($response_type, array(self::OAuth2Protocol_ResponseType_Code))) === 0) + return self::OAuth2Protocol_ResponseMode_Query; + + if(count(array_diff($response_type, array(self::OAuth2Protocol_ResponseType_Token))) === 0) + return self::OAuth2Protocol_ResponseMode_Fragment; + // http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations + if(count(array_diff($response_type, array + ( + self::OAuth2Protocol_ResponseType_Code, + self::OAuth2Protocol_ResponseType_Token + ) + )) === 0) + return self::OAuth2Protocol_ResponseMode_Fragment; + + if(count(array_diff($response_type, array + ( + self::OAuth2Protocol_ResponseType_Code, + self::OAuth2Protocol_ResponseType_IdToken + ) + )) === 0) + return self::OAuth2Protocol_ResponseMode_Fragment; + + if(count(array_diff($response_type, array + ( + self::OAuth2Protocol_ResponseType_Token, + self::OAuth2Protocol_ResponseType_IdToken + ) + )) === 0) + return self::OAuth2Protocol_ResponseMode_Fragment; + + if(count(array_diff($response_type, array + ( + self::OAuth2Protocol_ResponseType_Code, + self::OAuth2Protocol_ResponseType_Token, + self::OAuth2Protocol_ResponseType_IdToken + ) + )) === 0) + return self::OAuth2Protocol_ResponseMode_Fragment; + } + + + const OAuth2Protocol_ClientId = 'client_id'; + const OAuth2Protocol_UserId = 'user_id'; const OAuth2Protocol_ClientSecret = 'client_secret'; - const OAuth2Protocol_Token = 'token'; - const OAuth2Protocol_TokenType = 'token_type'; - //http://tools.ietf.org/html/rfc7009#section-2.1 - const OAuth2Protocol_TokenType_Hint = 'token_type_hint'; + const OAuth2Protocol_Token = 'token'; + const OAuth2Protocol_TokenType = 'token_type'; + + // http://tools.ietf.org/html/rfc7009#section-2.1 + const OAuth2Protocol_TokenType_Hint = 'token_type_hint'; const OAuth2Protocol_AccessToken_ExpiresIn = 'expires_in'; - const OAuth2Protocol_RefreshToken = 'refresh_token'; - const OAuth2Protocol_AccessToken = 'access_token'; - const OAuth2Protocol_RedirectUri = 'redirect_uri'; - const OAuth2Protocol_Scope = 'scope'; - const OAuth2Protocol_Audience = 'audience'; - const OAuth2Protocol_State = 'state'; + const OAuth2Protocol_RefreshToken = 'refresh_token'; + const OAuth2Protocol_AccessToken = 'access_token'; + const OAuth2Protocol_RedirectUri = 'redirect_uri'; + const OAuth2Protocol_Scope = 'scope'; + const OAuth2Protocol_Audience = 'audience'; + const OAuth2Protocol_State = 'state'; + + /** + * http://openid.net/specs/openid-connect-session-1_0.html#CreatingUpdatingSessions + * In OpenID Connect, the session at the RP typically starts when the RP validates the End-User's ID Token. Refer + * to the OpenID Connect Core 1.0 [OpenID.Core] specification to find out how to obtain an ID Token and validate it. + * When the OP supports session management, it MUST also return the Session State as an additional session_state + * parameter in the Authentication Response. The OpenID Connect Authentication Response is specified in + * Section 3.1.2.5 of OpenID Connect Core 1.0. + * JSON string that represents the End-User's login state at the OP. It MUST NOT contain the space (" ") character. + * This value is opaque to the RP. This is REQUIRED if session management is supported. + */ + const OAuth2Protocol_Session_State = 'session_state'; + // http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse + // ID Token value associated with the authenticated session. + const OAuth2Protocol_IdToken = 'id_token'; + + // http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + const OAuth2Protocol_Nonce = 'nonce'; + + /** + * Time when the End-User authentication occurred. Its value is a JSON number representing the number of seconds + * from 1970-01-01T0:0:0Z as measured in UTC until the date/time. When a max_age request is made or when auth_time + * is requested as an Essential Claim, then this Claim is REQUIRED; otherwise, its inclusion is OPTIONAL. + * (The auth_time Claim semantically corresponds to the OpenID 2.0 PAPE [OpenID.PAPE] auth_time response parameter.) + */ + const OAuth2Protocol_AuthTime = 'auth_time'; + + /** + * Access Token hash value. Its value is the base64url encoding of the left-most half of the hash of the octets of + * the ASCII representation of the access_token value, where the hash algorithm used is the hash algorithm used in + * the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, hash the access_token + * value with SHA-256, then take the left-most 128 bits and base64url encode them. The at_hash value is a case + * sensitive string. + */ + const OAuth2Protocol_AccessToken_Hash = 'at_hash'; + + + + + /** + * Code hash value. Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII + * representation of the code value, where the hash algorithm used is the hash algorithm used in the alg Header + * Parameter of the ID Token's JOSE Header. For instance, if the alg is HS512, hash the code value with SHA-512, + * then take the left-most 256 bits and base64url encode them. The c_hash value is a case sensitive string. + * If the ID Token is issued from the Authorization Endpoint with a code, which is the case for the response_type + * values code id_token and code id_token token, this is REQUIRED; otherwise, its inclusion is OPTIONAL. + */ + const OAuth2Protocol_AuthCode_Hash = 'c_hash'; + + /** + * Specifies how the Authorization Server displays the authentication and consent user interface pages to + * the End-User. + */ + const OAuth2Protocol_Display ='display'; + /** + * The Authorization Server SHOULD display the authentication and consent UI consistent with a full User Agent page + * view. If the display parameter is not specified, this is the default display mode. + * The Authorization Server MAY also attempt to detect the capabilities of the User Agent and present an + * appropriate display. + */ + const OAuth2Protocol_Display_Page ='page'; + /** + * The Authorization Server SHOULD display the authentication and consent UI consistent with a popup User Agent + * window. The popup User Agent window should be of an appropriate size for a login-focused dialog and should not + * obscure the entire window that it is popping up over. + */ + const OAuth2Protocol_Display_PopUp ='popup'; + /** + * The Authorization Server SHOULD display the authentication and consent UI consistent with a device that leverages + * a touch interface. + */ + const OAuth2Protocol_Display_Touch ='touch'; + /** + * The Authorization Server SHOULD display the authentication and consent UI consistent with a "feature phone" + * type display. + */ + const OAuth2Protocol_Display_Wap ='wap'; + + /** + * @var array + */ + static public $valid_display_values = array + ( + self::OAuth2Protocol_Display_Page, + self::OAuth2Protocol_Display_PopUp, + self::OAuth2Protocol_Display_Touch, + self::OAuth2Protocol_Display_Wap + ); + + /** + * Specifies whether the Authorization Server prompts the End-User for reauthentication and consent. + * The prompt parameter can be used by the Client to make sure that the End-User is still present for the current + * session or to bring attention to the request. If this parameter contains none with any other value, an error is + * returned. + */ + const OAuth2Protocol_Prompt = 'prompt'; + + /** + * The Authorization Server MUST NOT display any authentication or consent user interface pages. An error is + * returned if an End-User is not already authenticated or the Client does not have pre-configured consent for the + * requested Claims or does not fulfill other conditions for processing the request. The error code will typically + * be login_required, interaction_required, or another code defined in Section 3.1.2.6. This can be used as a method + * to check for existing authentication and/or consent. + */ + const OAuth2Protocol_Prompt_None = 'none'; + + /** + * The Authorization Server SHOULD prompt the End-User for reauthentication. If it cannot reauthenticate the + * End-User, it MUST return an error, typically login_required. + */ + const OAuth2Protocol_Prompt_Login = 'login'; + + /** + * The Authorization Server SHOULD prompt the End-User for consent before returning information to the Client. + * If it cannot obtain consent, it MUST return an error, typically consent_required. + */ + const OAuth2Protocol_Prompt_Consent = 'consent'; + + /** + * The Authorization Server SHOULD prompt the End-User to select a user account. This enables an End-User who has + * multiple accounts at the Authorization Server to select amongst the multiple accounts that they might have + * current sessions for. If it cannot obtain an account selection choice made by the End-User, it MUST return an + * error, typically account_selection_required. + */ + const OAuth2Protocol_Prompt_SelectAccount = 'select_account'; + + /** + * @var array + */ + static public $valid_prompt_values = array + ( + self::OAuth2Protocol_Prompt_None, + self::OAuth2Protocol_Prompt_Login, + self::OAuth2Protocol_Prompt_Consent, + self::OAuth2Protocol_Prompt_SelectAccount + ); + + /** + * @param string $flow + * @return array + */ + static public function getValidResponseTypes($flow = 'all') + { + $code_flow = array + ( + //OAuth2 / OIDC + array + ( + self::OAuth2Protocol_ResponseType_Code + ) + ); + + $implicit_flow = array + ( + // only for OAuth2 + array + ( + self::OAuth2Protocol_ResponseType_Token + ), + // OIDC + array + ( + self::OAuth2Protocol_ResponseType_IdToken + ), + array + ( + self::OAuth2Protocol_ResponseType_IdToken , + self::OAuth2Protocol_ResponseType_Token + ) + ); + + $hybrid_flow = array + ( + array + ( + self::OAuth2Protocol_ResponseType_Code, + self::OAuth2Protocol_ResponseType_IdToken + ), + array + ( + self::OAuth2Protocol_ResponseType_Code, + self::OAuth2Protocol_ResponseType_Token + ), + array + ( + self::OAuth2Protocol_ResponseType_Code , + self::OAuth2Protocol_ResponseType_IdToken, + self::OAuth2Protocol_ResponseType_Token + ) + ); + + if($flow === 'all') + return array_merge + ( + $code_flow, + $implicit_flow, + $hybrid_flow + ); + + if($flow === OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode) + return $code_flow; + + if($flow === OAuth2Protocol::OAuth2Protocol_GrantType_Implicit) + return $implicit_flow; + + if($flow === OAuth2Protocol::OAuth2Protocol_GrantType_Hybrid) + return $hybrid_flow; + } + + /** + * http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Terminology + * + * The OAuth 2.0 specification allows for registration of space-separated response_type parameter values. If a + * Response Type contains one of more space characters (%20), it is compared as a space-delimited list of values in + * which the order of values does not matter. + * + * @param array $response_type + * @param string $flow + * @return bool + */ + static public function responseTypeBelongsToFlow(array $response_type, $flow = 'all') + { + if + ( + !in_array + ( + $flow, array + ( + OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode, + OAuth2Protocol::OAuth2Protocol_GrantType_Implicit, + OAuth2Protocol::OAuth2Protocol_GrantType_Hybrid, + 'all' + ) + ) + ) + return false; + + $flow_response_types = self::getValidResponseTypes($flow); + + foreach($flow_response_types as $rt) + { + if(count($rt) !== count($response_type)) continue; + $diff = array_diff($rt, $response_type); + if(count($diff) === 0) return true; + } + return false; + } + + /** + * Maximum Authentication Age. Specifies the allowable elapsed time in seconds since the last time the End-User was + * actively authenticated by the OP. If the elapsed time is greater than this value, the OP MUST attempt to actively + * re-authenticate the End-User. (The max_age request parameter corresponds to the OpenID 2.0 PAPE [OpenID.PAPE] + * max_auth_age request parameter.) When max_age is used, the ID Token returned MUST include an auth_time Claim + * Value. + */ + const OAuth2Protocol_MaxAge = 'max_age'; + + /** + * End-User's preferred languages and scripts for the user interface, represented as a space-separated list + * of BCP47 [RFC5646] language tag values, ordered by preference. For instance, the value "fr-CA fr en" represents + * a preference for French as spoken in Canada, then French (without a region designation), followed by English + * (without a region designation). An error SHOULD NOT result if some or all of the requested locales are not + * supported by the OpenID Provider. + */ + const OAuth2Protocol_UILocales = 'ui_locales'; + + /** + * ID Token previously issued by the Authorization Server being passed as a hint about the End-User's current or + * past authenticated session with the Client. If the End-User identified by the ID Token is logged in or is logged + * in by the request, then the Authorization Server returns a positive response; otherwise, it SHOULD return an + * error, such as login_required. When possible, an id_token_hint SHOULD be present when prompt=none is used and an + * invalid_request error MAY be returned if it is not; however, the server SHOULD respond successfully when + * possible, even if it is not present. The Authorization Server need not be listed as an audience of the ID Token + * when it is used as an id_token_hint value. + * If the ID Token received by the RP from the OP is encrypted, to use it as an id_token_hint, the Client MUST + * decrypt the signed ID Token contained within the encrypted ID Token. The Client MAY re-encrypt the signed ID + * token to the Authentication Server using a key that enables the server to decrypt the ID Token, and use the + * re-encrypted ID token as the id_token_hint value. + */ + const OAuth2Protocol_IDTokenHint = 'id_token_hint'; + + /** + * Hint to the Authorization Server about the login identifier the End-User might use to log in (if necessary). + * This hint can be used by an RP if it first asks the End-User for their e-mail address (or other identifier) + * and then wants to pass that value as a hint to the discovered authorization service. It is RECOMMENDED that the + * hint value match the value used for discovery. This value MAY also be a phone number in the format specified for + * the phone_number Claim. The use of this parameter is left to the OP's discretion. + */ + const OAuth2Protocol_LoginHint = 'login_hint'; + + /** + * Requested Authentication Context Class Reference values. Space-separated string that specifies the acr values + * that the Authorization Server is being requested to use for processing this Authentication Request, with the + * values appearing in order of preference. The Authentication Context Class satisfied by the authentication + * performed is returned as the acr Claim Value, as specified in Section 2. The acr Claim is requested as a + * Voluntary Claim by this parameter. + */ + const OAuth2Protocol_ACRValues = 'acr_values'; + + /** + * OPTIONAL. URL to which the RP is requesting that the End-User's User Agent be redirected after a logout has been + * performed. The value MUST have been previously registered with the OP, either using the post_logout_redirect_uris + * Registration parameter or via another mechanism. If supplied, the OP SHOULD honor this request following the logout. + */ + const OAuth2Protocol_PostLogoutRedirectUri = 'post_logout_redirect_uri'; + /** * Indicates whether the user should be re-prompted for consent. The default is auto, * so a given user should only see the consent page for a given set of scopes the first time * through the sequence. If the value is force, then the user sees a consent page even if they * previously gave consent to your application for a given set of scopes. */ - const OAuth2Protocol_Approval_Prompt = 'approval_prompt'; + const OAuth2Protocol_Approval_Prompt = 'approval_prompt'; const OAuth2Protocol_Approval_Prompt_Force = 'force'; - const OAuth2Protocol_Approval_Prompt_Auto = 'auto'; + const OAuth2Protocol_Approval_Prompt_Auto = 'auto'; /** * Indicates whether your application needs to access an API when the user is not present at @@ -102,8 +518,8 @@ class OAuth2Protocol implements IOAuth2Protocol * when the user is not present at the browser, then use offline. This will result in your application * obtaining a refresh token the first time your application exchanges an authorization code for a user. */ - const OAuth2Protocol_AccessType = 'access_type'; - const OAuth2Protocol_AccessType_Online = 'online'; + const OAuth2Protocol_AccessType = 'access_type'; + const OAuth2Protocol_AccessType_Online = 'online'; const OAuth2Protocol_AccessType_Offline = 'offline'; const OAuth2Protocol_GrantType = 'grant_type'; @@ -112,6 +528,7 @@ class OAuth2Protocol implements IOAuth2Protocol const OAuth2Protocol_ErrorUri = 'error_uri'; const OAuth2Protocol_Error_InvalidRequest = 'invalid_request'; const OAuth2Protocol_Error_UnauthorizedClient = 'unauthorized_client'; + const OAuth2Protocol_Error_RedirectUriMisMatch = 'redirect_uri_mismatch'; const OAuth2Protocol_Error_AccessDenied = 'access_denied'; const OAuth2Protocol_Error_UnsupportedResponseType = 'unsupported_response_type'; const OAuth2Protocol_Error_InvalidScope = 'invalid_scope'; @@ -126,18 +543,145 @@ class OAuth2Protocol implements IOAuth2Protocol const OAuth2Protocol_Error_InvalidToken = 'invalid_token'; const OAuth2Protocol_Error_InsufficientScope = 'insufficient_scope'; - public static $valid_responses_types = array( - self::OAuth2Protocol_ResponseType_Code => self::OAuth2Protocol_ResponseType_Code, + // http://openid.net/specs/openid-connect-core-1_0.html#AuthError + + /** + * The Authorization Server requires End-User interaction of some form to proceed. This error MAY be returned when + * the prompt parameter value in the Authentication Request is none, but the Authentication Request cannot be + * completed without displaying a user interface for End-User interaction. + */ + const OAuth2Protocol_Error_Interaction_Required = 'interaction_required'; + + /** + * The Authorization Server requires End-User authentication. This error MAY be returned when the prompt parameter + * value in the Authentication Request is none, but the Authentication Request cannot be completed without + * displaying a user interface for End-User authentication. + */ + const OAuth2Protocol_Error_Login_Required = 'login_required'; + + /** + * The End-User is REQUIRED to select a session at the Authorization Server. The End-User MAY be authenticated at + * the Authorization Server with different associated accounts, but the End-User did not select a session. + * This error MAY be returned when the prompt parameter value in the Authentication Request is none, but the + * Authentication Request cannot be completed without displaying a user interface to prompt for a session to use. + */ + const OAuth2Protocol_Error_Account_Selection_Required = 'account_selection_required'; + + /** + * The Authorization Server requires End-User consent. This error MAY be returned when the prompt parameter value + * in the Authentication Request is none, but the Authentication Request cannot be completed without displaying a + * user interface for End-User consent. + */ + const OAuth2Protocol_Error_Consent_Required = 'consent_required'; + + /** + * The request_uri in the Authorization Request returns an error or contains invalid data + */ + const OAuth2Protocol_Error_Invalid_RequestUri = 'invalid_request_uri'; + + /** + * The request parameter contains an invalid Request Object. + */ + const OAuth2Protocol_Error_Invalid_RequestObject = 'invalid_request_object'; + + /** + * The OP does not support use of the request parameter defined in Section 6. + */ + const OAuth2Protocol_Error_Request_Not_Supported = 'request_not_supported'; + /** + * The OP does not support use of the request_uri parameter defined in Section 6. + */ + const OAuth2Protocol_Error_Request_Uri_Not_Supported = 'request_uri_not_supported'; + + /** + * The OP does not support use of the registration parameter defined in Section 7.2.1. + */ + const OAuth2Protocol_Error_Registration_Not_Supported = 'registration_not_supported'; + + + const OAuth2Protocol_Error_Invalid_Recipient_Keys = 'invalid_recipient_keys'; + const OAuth2Protocol_Error_Invalid_Server_Keys = 'invalid_server_keys'; + + + public static $valid_responses_types = array + ( + self::OAuth2Protocol_ResponseType_Code => self::OAuth2Protocol_ResponseType_Code, self::OAuth2Protocol_ResponseType_Token => self::OAuth2Protocol_ResponseType_Token ); - public static $protocol_definition = array( - self::OAuth2Protocol_ResponseType => self::OAuth2Protocol_ResponseType, - self::OAuth2Protocol_ClientId => self::OAuth2Protocol_ClientId, - self::OAuth2Protocol_RedirectUri => self::OAuth2Protocol_RedirectUri, - self::OAuth2Protocol_Scope => self::OAuth2Protocol_Scope, - self::OAuth2Protocol_State => self::OAuth2Protocol_State + + // http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication + + const TokenEndpoint_AuthMethod_ClientSecretBasic = 'client_secret_basic'; + const TokenEndpoint_AuthMethod_ClientSecretPost = 'client_secret_post'; + const TokenEndpoint_AuthMethod_ClientSecretJwt = 'client_secret_jwt'; + const TokenEndpoint_AuthMethod_PrivateKeyJwt = 'private_key_jwt'; + const TokenEndpoint_AuthMethod_None = 'none'; + + const OAuth2Protocol_ClientAssertionType = 'client_assertion_type'; + const OAuth2Protocol_ClientAssertion = 'client_assertion'; + + public static $token_endpoint_auth_methods = array + ( + self::TokenEndpoint_AuthMethod_ClientSecretBasic, + self::TokenEndpoint_AuthMethod_ClientSecretPost, + self::TokenEndpoint_AuthMethod_ClientSecretJwt, + self::TokenEndpoint_AuthMethod_PrivateKeyJwt, ); + const OpenIdConnect_Scope = 'openid'; + const OfflineAccess_Scope = 'offline_access'; + + public static $supported_signing_algorithms = array + ( + // MAC SHA2 + JSONWebSignatureAndEncryptionAlgorithms::HS256, + JSONWebSignatureAndEncryptionAlgorithms::HS384, + JSONWebSignatureAndEncryptionAlgorithms::HS512, + // RSA + JSONWebSignatureAndEncryptionAlgorithms::RS256, + JSONWebSignatureAndEncryptionAlgorithms::RS384, + JSONWebSignatureAndEncryptionAlgorithms::RS512, + JSONWebSignatureAndEncryptionAlgorithms::PS256, + JSONWebSignatureAndEncryptionAlgorithms::PS384, + JSONWebSignatureAndEncryptionAlgorithms::PS512, + JSONWebSignatureAndEncryptionAlgorithms::None + ); + + public static $supported_signing_algorithms_hmac_sha2 = array + ( + JSONWebSignatureAndEncryptionAlgorithms::HS256, + JSONWebSignatureAndEncryptionAlgorithms::HS384, + JSONWebSignatureAndEncryptionAlgorithms::HS512, + ); + + public static $supported_signing_algorithms_rsa = array + ( + JSONWebSignatureAndEncryptionAlgorithms::RS256, + JSONWebSignatureAndEncryptionAlgorithms::RS384, + JSONWebSignatureAndEncryptionAlgorithms::RS512, + JSONWebSignatureAndEncryptionAlgorithms::PS256, + JSONWebSignatureAndEncryptionAlgorithms::PS384, + JSONWebSignatureAndEncryptionAlgorithms::PS512, + ); + + // https://tools.ietf.org/html/rfc7518#page-12 + public static $supported_key_management_algorithms = array + ( + JSONWebSignatureAndEncryptionAlgorithms::RSA1_5, + JSONWebSignatureAndEncryptionAlgorithms::RSA_OAEP, + JSONWebSignatureAndEncryptionAlgorithms::RSA_OAEP_256, + JSONWebSignatureAndEncryptionAlgorithms::Dir, + JSONWebSignatureAndEncryptionAlgorithms::None, + ); + + // https://tools.ietf.org/html/rfc7518#page-22 + public static $supported_content_encryption_algorithms = array + ( + JSONWebSignatureAndEncryptionAlgorithms::A128CBC_HS256, + JSONWebSignatureAndEncryptionAlgorithms::A192CBC_HS384, + JSONWebSignatureAndEncryptionAlgorithms::A256CBC_HS512, + JSONWebSignatureAndEncryptionAlgorithms::None, + ); /** * http://tools.ietf.org/html/rfc6749#appendix-A @@ -146,50 +690,188 @@ class OAuth2Protocol implements IOAuth2Protocol const VsChar = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-_~'; //services + /** + * @var ILogService + */ private $log_service; + /** + * @var ICheckPointService + */ private $checkpoint_service; + /** + * @var IClientService + */ private $client_service; + /** + * @var IAuthService + */ + private $auth_service; + + /** + * @var IPrincipalService + */ + private $principal_service; + + /** + * @var ITokenService + */ + private $token_service; + //endpoints + /** + * @var AuthorizationEndpoint + */ private $authorize_endpoint; + /** + * @var TokenEndpoint + */ private $token_endpoint; + /** + * @var TokenRevocationEndpoint + */ private $revoke_endpoint; + /** + * @var TokenIntrospectionEndpoint + */ private $introspection_endpoint; - //grant types + /** + * grant types + * @var array + */ private $grant_types = array(); - public function __construct( + /** + * @var IServerPrivateKeyRepository + */ + private $server_private_keys_repository; + + /** + * @var IOpenIDProviderConfigurationService + */ + private $oidc_provider_configuration_service; + + /** + * @param ILogService $log_service + * @param IClientService $client_service + * @param ITokenService $token_service + * @param IAuthService $auth_service + * @param IOAuth2AuthenticationStrategy $auth_strategy + * @param ICheckPointService $checkpoint_service + * @param IApiScopeService $scope_service + * @param IUserConsentService $user_consent_service + * @param IServerPrivateKeyRepository $server_private_keys_repository + * @param IOpenIDProviderConfigurationService $oidc_provider_configuration_service + * @param IMementoOAuth2SerializerService $memento_service + * @param ISecurityContextService $security_context_service + * @param IPrincipalService $principal_service + * @param IServerPrivateKeyRepository $server_private_key_repository + * @param IClientJWKSetReader $jwk_set_reader_service + */ + public function __construct + ( ILogService $log_service, IClientService $client_service, ITokenService $token_service, IAuthService $auth_service, - IMementoOAuth2AuthenticationRequestService $memento_service, IOAuth2AuthenticationStrategy $auth_strategy, ICheckPointService $checkpoint_service, IApiScopeService $scope_service, - IUserConsentService $user_consent_service) + IUserConsentService $user_consent_service, + IServerPrivateKeyRepository $server_private_keys_repository, + IOpenIDProviderConfigurationService $oidc_provider_configuration_service, + IMementoOAuth2SerializerService $memento_service, + ISecurityContextService $security_context_service, + IPrincipalService $principal_service, + IServerPrivateKeyRepository $server_private_key_repository, + IClientJWKSetReader $jwk_set_reader_service + ) { - $authorization_code_grant_type = new AuthorizationCodeGrantType($scope_service, $client_service, $token_service, $auth_service, $memento_service, $auth_strategy, $log_service,$user_consent_service); - $implicit_grant_type = new ImplicitGrantType($scope_service, $client_service, $token_service, $auth_service, $memento_service, $auth_strategy, $log_service,$user_consent_service); - $refresh_bearer_token_grant_type = new RefreshBearerTokenGrantType($client_service, $token_service, $log_service); - $client_credential_grant_type = new ClientCredentialsGrantType($scope_service,$client_service, $token_service, $log_service); + $this->server_private_keys_repository = $server_private_keys_repository; + $this->oidc_provider_configuration_service = $oidc_provider_configuration_service; + + $authorization_code_grant_type = new AuthorizationCodeGrantType + ( + $scope_service, + $client_service, + $token_service, + $auth_service, + $auth_strategy, + $log_service, + $user_consent_service, + $memento_service, + $security_context_service, + $principal_service, + $server_private_key_repository, + $jwk_set_reader_service + ); + + $implicit_grant_type = new ImplicitGrantType + ( + $scope_service, + $client_service, + $token_service, + $auth_service, + $auth_strategy, + $log_service, + $user_consent_service, + $memento_service, + $security_context_service, + $principal_service, + $server_private_key_repository, + $jwk_set_reader_service + ); + + $hybrid_grant_type = new HybridGrantType + ( + $scope_service, + $client_service, + $token_service, + $auth_service, + $auth_strategy, + $log_service, + $user_consent_service, + $memento_service, + $security_context_service, + $principal_service, + $server_private_key_repository, + $jwk_set_reader_service + ); + + $refresh_bearer_token_grant_type = new RefreshBearerTokenGrantType + ( + $client_service, + $token_service, + $log_service + ); + + $client_credential_grant_type = new ClientCredentialsGrantType + ( + $scope_service, + $client_service, + $token_service, + $log_service + ); $this->grant_types[$authorization_code_grant_type->getType()] = $authorization_code_grant_type; $this->grant_types[$implicit_grant_type->getType()] = $implicit_grant_type; $this->grant_types[$refresh_bearer_token_grant_type->getType()] = $refresh_bearer_token_grant_type; $this->grant_types[$client_credential_grant_type->getType()] = $client_credential_grant_type; - + $this->grant_types[$hybrid_grant_type->getType()] = $hybrid_grant_type; $this->log_service = $log_service; $this->checkpoint_service = $checkpoint_service; $this->client_service = $client_service; + $this->auth_service = $auth_service; + $this->principal_service = $principal_service; + $this->token_service = $token_service; $this->authorize_endpoint = new AuthorizationEndpoint($this); $this->token_endpoint = new TokenEndpoint($this); $this->revoke_endpoint = new TokenRevocationEndpoint($this,$client_service, $token_service, $log_service); - $this->introspection_endpoint = new TokenIntrospectionEndpoint($this,$client_service, $token_service, $log_service); + $this->introspection_endpoint = new TokenIntrospectionEndpoint($this, $client_service, $token_service, $auth_service, $log_service); } /** @@ -200,119 +882,39 @@ class OAuth2Protocol implements IOAuth2Protocol */ public function authorize(OAuth2Request $request = null) { - try { + try + { if (is_null($request) || !$request->isValid()) throw new InvalidOAuth2Request; + return $this->authorize_endpoint->handle($request); - } catch (InvalidOAuth2Request $ex1) { + } + catch (UriNotAllowedException $ex1) + { $this->log_service->error($ex1); $this->checkpoint_service->trackException($ex1); - $redirect_uri = $this->validateRedirectUri($request); - if (is_null($redirect_uri)) - throw $ex1; - - return OAuth2IndirectErrorResponseFactoryMethod::buildResponse($request, OAuth2Protocol::OAuth2Protocol_Error_InvalidRequest, $redirect_uri); - } catch (UnsupportedResponseTypeException $ex2) { + throw $ex1; + } + catch(OAuth2BaseException $ex2) + { $this->log_service->error($ex2); $this->checkpoint_service->trackException($ex2); $redirect_uri = $this->validateRedirectUri($request); + if (is_null($redirect_uri)) throw $ex2; - return OAuth2IndirectErrorResponseFactoryMethod::buildResponse($request, OAuth2Protocol::OAuth2Protocol_Error_UnsupportedResponseType, $redirect_uri); - } catch (InvalidClientException $ex3) { - $this->log_service->error($ex3); - $this->checkpoint_service->trackException($ex3); - - $redirect_uri = $this->validateRedirectUri($request); - if (is_null($redirect_uri)) - throw $ex3; - - return OAuth2IndirectErrorResponseFactoryMethod::buildResponse($request, OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient, $redirect_uri); - } catch (UriNotAllowedException $ex4) { - $this->log_service->error($ex4); - $this->checkpoint_service->trackException($ex4); - throw $ex4; - } catch (ScopeNotAllowedException $ex5) { - - $this->log_service->error($ex5); - $this->checkpoint_service->trackException($ex5); - - $redirect_uri = $this->validateRedirectUri($request); - if (is_null($redirect_uri)) - throw $ex5; - - return OAuth2IndirectErrorResponseFactoryMethod::buildResponse($request, OAuth2Protocol::OAuth2Protocol_Error_InvalidScope, $redirect_uri); - } catch (UnAuthorizedClientException $ex6) { - $this->log_service->error($ex6); - $this->checkpoint_service->trackException($ex6); - - $redirect_uri = $this->validateRedirectUri($request); - if (is_null($redirect_uri)) - throw $ex6; - - return OAuth2IndirectErrorResponseFactoryMethod::buildResponse($request, OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient, $redirect_uri); - } catch (AccessDeniedException $ex7) { - $this->log_service->error($ex7); - $this->checkpoint_service->trackException($ex7); - - $redirect_uri = $this->validateRedirectUri($request); - if (is_null($redirect_uri)) - throw $ex7; - - return OAuth2IndirectErrorResponseFactoryMethod::buildResponse($request, OAuth2Protocol::OAuth2Protocol_Error_AccessDenied, $redirect_uri); - } catch (OAuth2GenericException $ex8) { - $this->log_service->error($ex8); - $this->checkpoint_service->trackException($ex8); - - $redirect_uri = $this->validateRedirectUri($request); - if (is_null($redirect_uri)) - throw $ex8; - - return OAuth2IndirectErrorResponseFactoryMethod::buildResponse($request, OAuth2Protocol::OAuth2Protocol_Error_ServerError, $redirect_uri); + return OAuth2IndirectErrorResponseFactoryMethod::buildResponse + ( + $request, + $ex2->getError(), + $ex2->getMessage(), + $redirect_uri + ); } - catch(InvalidApplicationType $ex9){ - $this->log_service->error($ex9); - $this->checkpoint_service->trackException($ex9); - - $redirect_uri = $this->validateRedirectUri($request); - if (is_null($redirect_uri)) - throw $ex9; - - return OAuth2IndirectErrorResponseFactoryMethod::buildResponse($request, OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient, $redirect_uri); - } - catch(LockedClientException $ex10){ - $this->log_service->error($ex10); - $this->checkpoint_service->trackException($ex10); - - $redirect_uri = $this->validateRedirectUri($request); - if (is_null($redirect_uri)) - throw $ex10; - - return OAuth2IndirectErrorResponseFactoryMethod::buildResponse($request, OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient, $redirect_uri); - } - catch(MissingClientIdParam $ex11){ - $this->log_service->error($ex11); - $this->checkpoint_service->trackException($ex11); - - $redirect_uri = $this->validateRedirectUri($request); - if (is_null($redirect_uri)) - throw $ex11; - - return OAuth2IndirectErrorResponseFactoryMethod::buildResponse($request, OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient, $redirect_uri); - } - catch(InvalidClientType $ex12){ - $this->log_service->error($ex12); - $this->checkpoint_service->trackException($ex12); - - $redirect_uri = $this->validateRedirectUri($request); - if (is_null($redirect_uri)) - throw $ex12; - - return OAuth2IndirectErrorResponseFactoryMethod::buildResponse($request, OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient, $redirect_uri); - } - catch (Exception $ex) { + catch (Exception $ex) + { $this->log_service->error($ex); $this->checkpoint_service->trackException($ex); @@ -320,7 +922,13 @@ class OAuth2Protocol implements IOAuth2Protocol if (is_null($redirect_uri)) throw $ex; - return OAuth2IndirectErrorResponseFactoryMethod::buildResponse($request, OAuth2Protocol::OAuth2Protocol_Error_ServerError, $redirect_uri); + return OAuth2IndirectErrorResponseFactoryMethod::buildResponse + ( + $request, + OAuth2Protocol::OAuth2Protocol_Error_ServerError, + $ex->getMessage(), + $redirect_uri + ); } } @@ -348,95 +956,29 @@ class OAuth2Protocol implements IOAuth2Protocol */ public function token(OAuth2Request $request = null) { - try { + try + { if (is_null($request) || !$request->isValid()) throw new InvalidOAuth2Request; return $this->token_endpoint->handle($request); - } catch (InvalidOAuth2Request $ex1) { + } + catch(OAuth2BaseException $ex1) + { $this->log_service->error($ex1); $this->checkpoint_service->trackException($ex1); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_InvalidRequest); - } catch (InvalidAuthorizationCodeException $ex2) { - $this->log_service->error($ex2); - $this->checkpoint_service->trackException($ex2); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient); - } catch (InvalidClientException $ex3) { - $this->log_service->error($ex3); - $this->checkpoint_service->trackException($ex3); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient); - } catch (UriNotAllowedException $ex4) { - $this->log_service->error($ex4); - $this->checkpoint_service->trackException($ex4); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient); - } catch (UnAuthorizedClientException $ex5) { - $this->log_service->error($ex5); - $this->checkpoint_service->trackException($ex5); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient); - } catch (ExpiredAuthorizationCodeException $ex6) { - $this->log_service->error($ex6); - $this->checkpoint_service->trackException($ex6); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_InvalidRequest); - } catch (ReplayAttackException $ex7) { - $this->log_service->error($ex7); - $this->checkpoint_service->trackException($ex7); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_InvalidRequest); - } catch (InvalidAccessTokenException $ex8) { - $this->log_service->error($ex8); - $this->checkpoint_service->trackException($ex8); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_InvalidGrant); - } catch (InvalidGrantTypeException $ex9) { - $this->log_service->error($ex9); - $this->checkpoint_service->trackException($ex9); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_InvalidGrant); - } catch (BearerTokenDisclosureAttemptException $ex10) { - $this->log_service->error($ex10); - $this->checkpoint_service->trackException($ex10); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_InvalidGrant); + + return new OAuth2DirectErrorResponse($ex1->getError(), $ex1->getMessage());; } - catch(ScopeNotAllowedException $ex11){ - $this->log_service->error($ex11); - $this->checkpoint_service->trackException($ex11); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_InvalidScope); - } - catch(InvalidApplicationType $ex12){ - $this->log_service->error($ex12); - $this->checkpoint_service->trackException($ex12); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient); - } - catch(LockedClientException $ex13){ - $this->log_service->error($ex13); - $this->checkpoint_service->trackException($ex13); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient); - } - catch(MissingClientIdParam $ex14){ - $this->log_service->error($ex14); - $this->checkpoint_service->trackException($ex14); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient); - } - catch(InvalidClientType $ex15){ - $this->log_service->error($ex15); - $this->checkpoint_service->trackException($ex15); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient); - } - catch(MissingClientAuthorizationInfo $ex16){ - $this->log_service->error($ex16); - $this->checkpoint_service->trackException($ex16); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient); - } - catch(InvalidRedeemAuthCodeException $ex17){ - $this->log_service->error($ex17); - $this->checkpoint_service->trackException($ex17); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient); - } - catch(InvalidClientCredentials $ex18){ - $this->log_service->error($ex18); - $this->checkpoint_service->trackException($ex18); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient); - } - catch (Exception $ex) { + catch (Exception $ex) + { $this->log_service->error($ex); $this->checkpoint_service->trackException($ex); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_ServerError); + + return new OAuth2DirectErrorResponse + ( + OAuth2Protocol::OAuth2Protocol_Error_ServerError, + $ex->getMessage() + ); } } @@ -467,37 +1009,36 @@ class OAuth2Protocol implements IOAuth2Protocol * @param OAuth2Request $request * @return mixed */ - public function introspection(OAuth2Request $request = null){ - - try { + public function introspection(OAuth2Request $request = null) + { + try + { if (is_null($request) || !$request->isValid()) throw new InvalidOAuth2Request; + return $this->introspection_endpoint->handle($request); } - catch(UnAuthorizedClientException $ex1){ + catch(ExpiredAccessTokenException $ex1) + { $this->log_service->error($ex1); - $this->checkpoint_service->trackException($ex1); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient); + return new OAuth2DirectErrorResponse($ex1->getError(), $ex1->getMessage()); } - catch(BearerTokenDisclosureAttemptException $ex2){ + catch(OAuth2BaseException $ex2) + { $this->log_service->error($ex2); $this->checkpoint_service->trackException($ex2); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_InvalidGrant); + return new OAuth2DirectErrorResponse($ex2->getError(), $ex2->getMessage()); } - catch(InvalidClientCredentials $ex3){ - $this->log_service->error($ex3); - $this->checkpoint_service->trackException($ex3); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient); - } - catch(ExpiredAccessTokenException $ex4){ - $this->log_service->warning($ex4); - $this->checkpoint_service->trackException($ex4); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_InvalidToken); - } - catch (Exception $ex) { + catch (Exception $ex) + { $this->log_service->error($ex); $this->checkpoint_service->trackException($ex); - return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_InvalidRequest); + + return new OAuth2DirectErrorResponse + ( + OAuth2Protocol::OAuth2Protocol_Error_InvalidRequest, + OAuth2Protocol::OAuth2Protocol_Error_InvalidRequest + ); } } @@ -505,4 +1046,319 @@ class OAuth2Protocol implements IOAuth2Protocol { return $this->grant_types; } + + /** + * @param IClient $client + * @return bool + */ + static public function isClientAllowedToUseTokenEndpointAuth(IClient $client) + { + return $client->client_type === IClient::ClientType_Confidential || + $client->application_type === IClient::ApplicationType_Native; + } + + static public function getTokenEndpointAuthMethodsPerClientType(IClient $client) + { + if($client->getClientType() == IClient::ClientType_Public) + { + return ArrayUtils::convert2Assoc + ( + array + ( + self::TokenEndpoint_AuthMethod_PrivateKeyJwt, + self::TokenEndpoint_AuthMethod_None + ) + ); + } + + return ArrayUtils::convert2Assoc + ( + array_merge + ( + self::$token_endpoint_auth_methods, + array + ( + self::TokenEndpoint_AuthMethod_None + ) + ) + ); + } + + /** + * @param IClient $client + * @return array + */ + static public function getSigningAlgorithmsPerClientType(IClient $client) + { + if($client->getClientType() == IClient::ClientType_Public) + { + return ArrayUtils::convert2Assoc + ( + array_merge + ( + self::$supported_signing_algorithms_rsa, + array + ( + JSONWebSignatureAndEncryptionAlgorithms::None + ) + ) + ); + } + return ArrayUtils::convert2Assoc + ( + array_merge + ( + self::$supported_signing_algorithms_hmac_sha2, + self::$supported_signing_algorithms_rsa, + array + ( + JSONWebSignatureAndEncryptionAlgorithms::None + ) + ) + ); + } + + + /** + * @param IClient $client + * @return array + */ + static public function getKeyManagementAlgorithmsPerClientType(IClient $client) + { + if($client->getClientType() == IClient::ClientType_Public) + { + return ArrayUtils::convert2Assoc + ( + array_diff + ( + self::$supported_key_management_algorithms, + array + ( + JSONWebSignatureAndEncryptionAlgorithms::Dir + ) + ) + ); + } + return ArrayUtils::convert2Assoc + ( + self::$supported_key_management_algorithms + ); + } + + + /** + * @return string + */ + public function getJWKSDocument() + { + $keys = $this->server_private_keys_repository->getActives(); + $set = array(); + + foreach($keys as $private_key) + { + $jwk = RSAJWKFactory::build + ( + new RSAJWKPEMPrivateKeySpecification + ( + $private_key->getPEM(), + $private_key->getPassword() + ) + ); + + $jwk->setVisibility(JSONWebKeyVisibility::PublicOnly); + + $jwk + ->setId($private_key->getKeyId()) + ->setKeyUse($private_key->getUse()) + ->setType($private_key->getType()) + ->setAlgorithm($private_key->getAlg()->getName()); + + array_push($set, $jwk); + } + + $jkws = new JWKSet($set); + return $jkws->toJson(); + } + + /** + * http://openid.net/specs/openid-connect-discovery-1_0.html + * @return string + */ + public function getDiscoveryDocument() + { + $builder = new DiscoveryDocumentBuilder(); + + return $builder + ->setIssuer($this->oidc_provider_configuration_service->getIssuerUrl()) + ->setAuthEndpoint($this->oidc_provider_configuration_service->getAuthEndpoint()) + ->setTokenEndpoint($this->oidc_provider_configuration_service->getTokenEndpoint()) + ->setUserInfoEndpoint($this->oidc_provider_configuration_service->getUserInfoEndpoint()) + ->setJWKSUrl($this->oidc_provider_configuration_service->getJWKSUrl()) + ->setRevocationEndpoint($this->oidc_provider_configuration_service->getRevocationEndpoint()) + ->setIntrospectionEndpoint($this->oidc_provider_configuration_service->getIntrospectionEndpoint()) + // session management http://openid.net/specs/openid-connect-session-1_0.html + ->setEndSessionEndpoint($this->oidc_provider_configuration_service->getEndSessionEndpoint()) + ->setCheckSessionIframe($this->oidc_provider_configuration_service->getCheckSessionIFrame()) + // response types + ->addResponseTypeSupported('code') + ->addResponseTypeSupported('token') + ->addResponseTypeSupported('code token') + ->addResponseTypeSupported('token id_token') + ->addResponseTypeSupported('code token id_token') + // claims + ->addClaimSupported('aud') + ->addClaimSupported('exp') + ->addClaimSupported('iat') + ->addClaimSupported('iss') + ->addClaimSupported('sub') + ->addClaimSupported(StandardClaims::Email) + ->addClaimSupported(StandardClaims::EmailVerified) + ->addClaimSupported(StandardClaims::Name) + ->addClaimSupported(StandardClaims::GivenName) + ->addClaimSupported(StandardClaims::FamilyName) + ->addClaimSupported(StandardClaims::NickName) + ->addClaimSupported(StandardClaims::Picture) + ->addClaimSupported(StandardClaims::Birthdate) + ->addClaimSupported(StandardClaims::Locale) + ->addClaimSupported(StandardClaims::Gender) + ->addClaimSupported(StandardClaims::Address) + // scopes + ->addScopeSupported(self::OpenIdConnect_Scope) + ->addScopeSupported(IUserService::UserProfileScope_Address) + ->addScopeSupported(IUserService::UserProfileScope_Email) + ->addScopeSupported(IUserService::UserProfileScope_Profile) + // id token signing alg + ->addIdTokenSigningAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::HS256) + ->addIdTokenSigningAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::HS384) + ->addIdTokenSigningAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::HS512) + ->addIdTokenSigningAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::RS256) + ->addIdTokenSigningAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::RS384) + ->addIdTokenSigningAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::RS512) + ->addIdTokenSigningAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::PS256) + ->addIdTokenSigningAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::PS384) + ->addIdTokenSigningAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::PS512) + // id token enc alg + ->addIdTokenEncryptionAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::RSA1_5) + ->addIdTokenEncryptionAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::RSA_OAEP) + ->addIdTokenEncryptionAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::RSA_OAEP_256) + ->addIdTokenEncryptionAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::Dir) + // id token enc enc + ->addIdTokenEncryptionEncSupported(JSONWebSignatureAndEncryptionAlgorithms::A128CBC_HS256) + ->addIdTokenEncryptionEncSupported(JSONWebSignatureAndEncryptionAlgorithms::A192CBC_HS384) + ->addIdTokenEncryptionEncSupported(JSONWebSignatureAndEncryptionAlgorithms::A256CBC_HS512) + // user info signing alg + ->addUserInfoSigningAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::HS256) + ->addUserInfoSigningAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::HS384) + ->addUserInfoSigningAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::HS512) + ->addUserInfoSigningAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::RS256) + ->addUserInfoSigningAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::RS384) + ->addUserInfoSigningAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::RS512) + ->addUserInfoSigningAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::PS256) + ->addUserInfoSigningAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::PS384) + ->addUserInfoSigningAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::PS512) + // user info enc alg + ->addUserInfoEncryptionAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::RSA1_5) + ->addUserInfoEncryptionAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::RSA_OAEP) + ->addUserInfoEncryptionAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::RSA_OAEP_256) + ->addUserInfoEncryptionAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::Dir) + // user info enc enc + ->addUserInfoEncryptionAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::A128CBC_HS256) + ->addUserInfoEncryptionAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::A192CBC_HS384) + ->addUserInfoEncryptionAlgSupported(JSONWebSignatureAndEncryptionAlgorithms::A256CBC_HS512) + ->addSubjectTypeSupported(IClient::SubjectType_Public) + ->addSubjectTypeSupported(IClient::SubjectType_Pairwise) + ->addTokenEndpointAuthMethodSupported(self::TokenEndpoint_AuthMethod_ClientSecretBasic) + ->addTokenEndpointAuthMethodSupported(self::TokenEndpoint_AuthMethod_ClientSecretPost) + ->addTokenEndpointAuthMethodSupported(self::TokenEndpoint_AuthMethod_PrivateKeyJwt) + ->addTokenEndpointAuthMethodSupported(self::TokenEndpoint_AuthMethod_ClientSecretJwt) + ->addResponseModeSupported(self::OAuth2Protocol_ResponseMode_FormPost) + ->addResponseModeSupported(self::OAuth2Protocol_ResponseMode_Query) + ->addResponseModeSupported(self::OAuth2Protocol_ResponseMode_Fragment) + ->render(); + } + + /** + * http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ + public function endSession(OAuth2Request $request = null) + { + try + { + if (is_null($request) || !$request->isValid()) + throw new InvalidOAuth2Request; + + if(! $request instanceof OAuth2LogoutRequest) throw new InvalidOAuth2Request; + + $id_token_hint = $request->getIdTokenHint(); + + $jwt = BasicJWTFactory::build($id_token_hint); + + if((!$jwt instanceof IJWT)) + throw new InvalidOAuth2Request('invalid id_token_hint!'); + + $client_id = $jwt->getClaimSet()->getAudience(); + + if(is_null($client_id)) throw new InvalidClientException('claim aud not set on id_token_hint!'); + + $client = $this->client_service->getClientById($client_id->getString()); + + if(is_null($client)) throw new InvalidClientException('client not found!'); + + $redirect_logout_uri = $request->getPostLogoutRedirectUri(); + + $state = $request->getState(); + + + if(!empty($redirect_logout_uri) && !$client->isPostLogoutUriAllowed($redirect_logout_uri)) + throw new InvalidOAuth2Request('post_logout_redirect_uri not allowed!'); + + $user_id = $jwt->getClaimSet()->getSubject(); + + if(is_null($user_id)) throw new InvalidOAuth2Request('claim sub not set on id_token_hint!'); + + $user_id = $this->auth_service->unwrapUserId(intval($user_id->getString())); + + $user = $this->auth_service->getUserByExternaldId($user_id); + + if(is_null($user)) throw new InvalidOAuth2Request('user not found!'); + + if($this->principal_service->get()->getUserId() !== $user->getId()) + throw new InvalidOAuth2Request('user does not match with current session!'); + + $this->auth_service->logout(); + + if(!empty($redirect_logout_uri)) + { + return new OAuth2LogoutResponse($redirect_logout_uri, $state); + } + + return null; + } + + catch(OAuth2BaseException $ex1) + { + $this->log_service->error($ex1); + $this->checkpoint_service->trackException($ex1); + + return new OAuth2DirectErrorResponse($ex1->getError(), $ex1->getMessage());; + } + catch (UriNotAllowedException $ex2) + { + $this->log_service->error($ex2); + $this->checkpoint_service->trackException($ex2); + + return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient); + } + catch (Exception $ex) + { + $this->log_service->error($ex); + $this->checkpoint_service->trackException($ex); + + return new OAuth2DirectErrorResponse + ( + OAuth2Protocol::OAuth2Protocol_Error_ServerError, + $ex->getMessage() + ); + } + } } \ No newline at end of file diff --git a/app/libs/oauth2/StandardClaims.php b/app/libs/oauth2/StandardClaims.php new file mode 100644 index 00000000..f3c0112b --- /dev/null +++ b/app/libs/oauth2/StandardClaims.php @@ -0,0 +1,157 @@ +set[OpenIDProviderMetadata::Issuer] = $issuer; + return $this; + } + + /** + * @param string $auth_endpoint + * @return $this + */ + public function setAuthEndpoint($auth_endpoint) + { + $this->set[OpenIDProviderMetadata::AuthEndpoint] = $auth_endpoint; + return $this; + } + + /** + * @param string $token_endpoint + * @return $this + */ + public function setTokenEndpoint($token_endpoint) + { + $this->set[OpenIDProviderMetadata::TokenEndpoint] = $token_endpoint; + return $this; + } + + /** + * @param string $user_info_endpoint + * @return $this + */ + public function setUserInfoEndpoint($user_info_endpoint) + { + $this->set[OpenIDProviderMetadata::UserInfoEndpoint] = $user_info_endpoint; + return $this; + } + + /** + * @param string $end_session_endpoint + * @return $this + */ + public function setEndSessionEndpoint($end_session_endpoint) + { + $this->set[OpenIDProviderMetadata::EndSessionEndPoint] = $end_session_endpoint; + return $this; + } + + /** + * @param string $check_session_iframe + * @return $this + */ + public function setCheckSessionIframe($check_session_iframe) + { + $this->set[OpenIDProviderMetadata::CheckSessionIFrame] = $check_session_iframe; + return $this; + } + + /** + * @param string $jwks_url + * @return $this + */ + public function setJWKSUrl($jwks_url) + { + $this->set[OpenIDProviderMetadata::JWKSUrl] = $jwks_url; + return $this; + } + + /** + * @param string $revocation_endpoint + * @return $this + */ + public function setRevocationEndpoint($revocation_endpoint) + { + $this->set[OpenIDProviderMetadata::RevocationEndpoint] = $revocation_endpoint; + return $this; + } + + /** + * @param string $introspection_endpoint + * @return $this + */ + public function setIntrospectionEndpoint($introspection_endpoint) + { + $this->set[OpenIDProviderMetadata::IntrospectionEndpoint] = $introspection_endpoint; + return $this; + } + + /** + * @param string $key + * @param string $value + */ + private function addArrayValue($key, $value) + { + + if( !isset($this->set[$key])) + $this->set[$key] = array(); + + array_push($this->set[$key], $value); + } + + /** + * @param string $response_type + * @return $this + */ + public function addResponseTypeSupported($response_type) + { + $this->addArrayValue(OpenIDProviderMetadata::ResponseTypesSupported, $response_type); + return $this; + } + + + /** + * @param string $response_mode + * @return $this + */ + public function addResponseModeSupported($response_mode) + { + $this->addArrayValue(OpenIDProviderMetadata::ResponseModesSupported, $response_mode); + return $this; + } + + /** + * @param string $subject_type + * @return $this + */ + public function addSubjectTypeSupported($subject_type) + { + $this->addArrayValue(OpenIDProviderMetadata::SubjectTypesSupported, $subject_type); + return $this; + } + + /** + * @param string $scope + * @return $this + */ + public function addScopeSupported($scope){ + $this->addArrayValue(OpenIDProviderMetadata::ScopesSupported, $scope); + return $this; + } + + /** + * @param string $claim + * @return $this + */ + public function addClaimSupported($claim) + { + $this->addArrayValue(OpenIDProviderMetadata::ClaimsSupported, $claim); + return $this; + } + + /** + * @param string $auth_method + * @return $this + */ + public function addTokenEndpointAuthMethodSupported($auth_method) + { + $this->addArrayValue(OpenIDProviderMetadata::TokenEndpointAuthMethodsSupported, $auth_method); + return $this; + } + + /** + * @param string $alg + * @return $this + */ + public function addIdTokenSigningAlgSupported($alg) + { + $this->addArrayValue(OpenIDProviderMetadata::IdTokenSigningAlgValuesSupported, $alg); + return $this; + } + + /** + * @param string $alg + * @return $this + */ + public function addIdTokenEncryptionAlgSupported($alg) + { + $this->addArrayValue(OpenIDProviderMetadata::IdTokenEncryptionAlgValuesSupported, $alg); + return $this; + } + + /** + * @param string $enc + * @return $this + */ + public function addIdTokenEncryptionEncSupported($enc) + { + $this->addArrayValue(OpenIDProviderMetadata::IdTokenEncryptionEncValuesSupported, $enc); + return $this; + } + + /** + * @param string $alg + * @return $this + */ + public function addUserInfoSigningAlgSupported($alg) + { + $this->addArrayValue(OpenIDProviderMetadata::UserInfoSigningAlgValuesSupported, $alg); + return $this; + } + + /** + * @param string $alg + * @return $this + */ + public function addUserInfoEncryptionAlgSupported($alg) + { + $this->addArrayValue(OpenIDProviderMetadata::UserInfoEncryptionAlgValuesSupported, $alg); + return $this; + } + + /** + * @param string $enc + * @return $this + */ + public function addUserInfoEncryptionEncSupported($enc) + { + $this->addArrayValue(OpenIDProviderMetadata::UserInfoEncryptionEncValuesSupported, $enc); + return $this; + } + + /** + * @return string + */ + public function render() + { + $json = json_encode($this->set); + $json = str_replace('\/','/', $json); + return $json; + } +} \ No newline at end of file diff --git a/app/libs/oauth2/discovery/IOpenIDProviderConfigurationService.php b/app/libs/oauth2/discovery/IOpenIDProviderConfigurationService.php new file mode 100644 index 00000000..98064171 --- /dev/null +++ b/app/libs/oauth2/discovery/IOpenIDProviderConfigurationService.php @@ -0,0 +1,67 @@ +protocol = $protocol; } diff --git a/app/libs/oauth2/endpoints/IOAuth2Endpoint.php b/app/libs/oauth2/endpoints/IOAuth2Endpoint.php index 3da74c13..e485a697 100644 --- a/app/libs/oauth2/endpoints/IOAuth2Endpoint.php +++ b/app/libs/oauth2/endpoints/IOAuth2Endpoint.php @@ -8,6 +8,11 @@ use oauth2\requests\OAuth2Request; * Interface IOAuth2Endpoint * @package oauth2\endpoints */ -interface IOAuth2Endpoint { +interface IOAuth2Endpoint +{ + /** + * @param OAuth2Request $request + * @return mixed + */ public function handle(OAuth2Request $request); } \ No newline at end of file diff --git a/app/libs/oauth2/endpoints/TokenEndpoint.php b/app/libs/oauth2/endpoints/TokenEndpoint.php index 85ee0bfd..e6a8138b 100644 --- a/app/libs/oauth2/endpoints/TokenEndpoint.php +++ b/app/libs/oauth2/endpoints/TokenEndpoint.php @@ -20,13 +20,24 @@ use oauth2\requests\OAuth2Request; class TokenEndpoint implements IOAuth2Endpoint { + /** + * @var IOAuth2Protocol + */ private $protocol; + /** + * @param IOAuth2Protocol $protocol + */ public function __construct(IOAuth2Protocol $protocol) { $this->protocol = $protocol; } + /** + * @param OAuth2Request $request + * @return mixed + * @throws InvalidGrantTypeException + */ public function handle(OAuth2Request $request) { foreach ($this->protocol->getAvailableGrants() as $key => $grant) { diff --git a/app/libs/oauth2/endpoints/TokenIntrospectionEndpoint.php b/app/libs/oauth2/endpoints/TokenIntrospectionEndpoint.php index a151e1ac..bb20e979 100644 --- a/app/libs/oauth2/endpoints/TokenIntrospectionEndpoint.php +++ b/app/libs/oauth2/endpoints/TokenIntrospectionEndpoint.php @@ -6,22 +6,58 @@ use oauth2\requests\OAuth2Request; use oauth2\IOAuth2Protocol; use oauth2\services\IClientService; use oauth2\services\ITokenService; +use utils\services\IAuthService; use utils\services\ILogService; use oauth2\grant_types\ValidateBearerTokenGrantType; +use oauth2\exceptions\InvalidOAuth2Request; +/** + * Class TokenIntrospectionEndpoint + * @package oauth2\endpoints + */ +class TokenIntrospectionEndpoint implements IOAuth2Endpoint +{ -class TokenIntrospectionEndpoint implements IOAuth2Endpoint { - + /** + * @var IOAuth2Protocol + */ private $protocol; + /** + * @var ValidateBearerTokenGrantType + */ private $grant_type; - public function __construct(IOAuth2Protocol $protocol, IClientService $client_service, ITokenService $token_service, ILogService $log_service) + /** + * @param IOAuth2Protocol $protocol + * @param IClientService $client_service + * @param ITokenService $token_service + * @param IAuthService $auth_service + * @param ILogService $log_service + */ + public function __construct + ( + IOAuth2Protocol $protocol, + IClientService $client_service, + ITokenService $token_service, + IAuthService $auth_service, + ILogService $log_service + ) { $this->protocol = $protocol; - $this->grant_type = new ValidateBearerTokenGrantType($client_service, $token_service, $log_service); + $this->grant_type = new ValidateBearerTokenGrantType($client_service, $token_service, $auth_service, $log_service); } + /** + * @param OAuth2Request $request + * @return mixed|\oauth2\responses\OAuth2AccessTokenValidationResponse|void + * @throws InvalidOAuth2Request + * @throws \oauth2\exceptions\BearerTokenDisclosureAttemptException + * @throws \oauth2\exceptions\ExpiredAccessTokenException + * @throws \oauth2\exceptions\InvalidApplicationType + * @throws \oauth2\exceptions\InvalidOAuth2Request + * @throws \oauth2\exceptions\LockedClientException + */ public function handle(OAuth2Request $request) { if($this->grant_type->canHandle($request)) diff --git a/app/libs/oauth2/endpoints/TokenRevocationEndpoint.php b/app/libs/oauth2/endpoints/TokenRevocationEndpoint.php index be6d4e35..b1ad29fb 100644 --- a/app/libs/oauth2/endpoints/TokenRevocationEndpoint.php +++ b/app/libs/oauth2/endpoints/TokenRevocationEndpoint.php @@ -10,18 +10,48 @@ use oauth2\services\ITokenService; use utils\services\ILogService; use oauth2\grant_types\RevokeBearerTokenGrantType; -class TokenRevocationEndpoint implements IOAuth2Endpoint { +/** + * Class TokenRevocationEndpoint + * @package oauth2\endpoints + */ +class TokenRevocationEndpoint implements IOAuth2Endpoint +{ + /** + * @var IOAuth2Protocol + */ private $protocol; + /** + * @var RevokeBearerTokenGrantType + */ private $grant_type; - public function __construct(IOAuth2Protocol $protocol, IClientService $client_service, ITokenService $token_service, ILogService $log_service) + /** + * @param IOAuth2Protocol $protocol + * @param IClientService $client_service + * @param ITokenService $token_service + * @param ILogService $log_service + */ + public function __construct( + IOAuth2Protocol $protocol, + IClientService $client_service, + ITokenService $token_service, + ILogService $log_service + ) { $this->protocol = $protocol; $this->grant_type = new RevokeBearerTokenGrantType($client_service, $token_service, $log_service); } - + /** + * @param OAuth2Request $request + * @return \oauth2\responses\OAuth2TokenRevocationResponse + * @throws InvalidOAuth2Request + * @throws \Exception + * @throws \oauth2\exceptions\BearerTokenDisclosureAttemptException + * @throws \oauth2\exceptions\ExpiredAccessTokenException + * @throws \oauth2\exceptions\UnAuthorizedClientException + */ public function handle(OAuth2Request $request) { if($this->grant_type->canHandle($request)) diff --git a/app/libs/oauth2/exceptions/AbsentCurrentUserException.php b/app/libs/oauth2/exceptions/AbsentCurrentUserException.php new file mode 100644 index 00000000..552087d1 --- /dev/null +++ b/app/libs/oauth2/exceptions/AbsentCurrentUserException.php @@ -0,0 +1,32 @@ +http_code = $http_code; @@ -21,19 +44,23 @@ class OAuth2ResourceServerException extends Exception{ parent::__construct($message, 0, null); } - public function getError(){ + public function getError() + { return $this->error; } - public function getErrorDescription(){ + public function getErrorDescription() + { return $this->error_description; } - public function getScope(){ + public function getScope() + { return $this->scope; } - public function getHttpCode(){ + public function getHttpCode() + { return $this->http_code; } } \ No newline at end of file diff --git a/app/libs/oauth2/exceptions/RecipientKeyNotFoundException.php b/app/libs/oauth2/exceptions/RecipientKeyNotFoundException.php new file mode 100644 index 00000000..c87fd2bd --- /dev/null +++ b/app/libs/oauth2/exceptions/RecipientKeyNotFoundException.php @@ -0,0 +1,32 @@ +auth_code; - } - - public function __construct($auth_code,$message = "") + /** + * @param null|string $code + * @param null $description + */ + public function __construct($token, $description = null) { - $this->auth_code = $auth_code; - $message = "Possible Replay Attack : " . $message; - parent::__construct($message, 0, null); + $this->token = $token; + parent::__construct($description); + } + /** + * @var string + */ + private $token; + + /** + * @return string + */ + public function getToken() + { + return $this->token; + } + /** + * @return string + */ + public function getError() + { + return OAuth2Protocol::OAuth2Protocol_Error_InvalidGrant; } } \ No newline at end of file diff --git a/app/libs/oauth2/exceptions/RevokedAccessTokenException.php b/app/libs/oauth2/exceptions/RevokedAccessTokenException.php new file mode 100644 index 00000000..2c6233b2 --- /dev/null +++ b/app/libs/oauth2/exceptions/RevokedAccessTokenException.php @@ -0,0 +1,32 @@ +getResponseType(false))) + { + $access_token = $token_service->createAccessTokenFromParams + ( + $request->getClientId(), + $request->getScope(), + $audience, + $user->getId() + ); + } + + if(in_array(OAuth2Protocol::OAuth2Protocol_ResponseType_IdToken, $request->getResponseType(false))) { + + $id_token = $token_service->createIdToken + ( + $request->getNonce(), + $request->getClientId(), + $access_token + ); + } + + return new OAuth2IDTokenFragmentResponse + ( + $request->getRedirectUri(), + is_null($access_token) ? null : $access_token->getValue(), + is_null($access_token) ? null : $access_token->getLifetime(), + is_null($access_token) ? null : $request->getScope(), + $request->getState(), + $session_state, + is_null($id_token) ? null : $id_token->toCompactSerialization() + ); + } + + if($request instanceof OAuth2AuthorizationRequest) + { + $access_token = $token_service->createAccessTokenFromParams + ( + $request->getClientId(), + $request->getScope(), + $audience, + $user->getId() + ); + + return new OAuth2AccessTokenFragmentResponse + ( + $request->getRedirectUri(), + $access_token->getValue(), + $access_token->getLifetime(), + $request->getScope(), + $request->getState() + ); + } + + throw new InvalidOAuth2Request; + } +} \ No newline at end of file diff --git a/app/libs/oauth2/factories/OAuth2AccessTokenResponseFactory.php b/app/libs/oauth2/factories/OAuth2AccessTokenResponseFactory.php new file mode 100644 index 00000000..64c6cb4e --- /dev/null +++ b/app/libs/oauth2/factories/OAuth2AccessTokenResponseFactory.php @@ -0,0 +1,140 @@ +getResponseType() + ); + + $is_hybrid_flow = OAuth2Protocol::responseTypeBelongsToFlow + ( + $response_type, + OAuth2Protocol::OAuth2Protocol_GrantType_Hybrid + ); + + if($is_hybrid_flow) + { + + if(in_array(OAuth2Protocol::OAuth2Protocol_ResponseType_Token, $response_type)) + { + + $access_token = $token_service->createAccessToken($auth_code, $request->getRedirectUri()); + } + + // check if should emmit id token + + if(in_array(OAuth2Protocol::OAuth2Protocol_ResponseType_IdToken, $response_type)) + { + + $id_token = $token_service->createIdToken + ( + $auth_code->getNonce(), + $auth_code->getClientId(), + $access_token + ); + } + + if(is_null($id_token) && is_null($access_token)) throw new InvalidOAuth2Request; + } + else + { + $access_token = $token_service->createAccessToken($auth_code, $request->getRedirectUri()); + + $id_token = $token_service->createIdToken + ( + $auth_code->getNonce(), + $auth_code->getClientId(), + $access_token + ); + } + + if(!is_null($access_token)) + $refresh_token = $access_token->getRefreshToken(); + + $response = new OAuth2IdTokenResponse + ( + is_null($access_token) ? null : $access_token->getValue(), + is_null($access_token) ? null : $access_token->getLifetime(), + is_null($id_token) ? null : $id_token->toCompactSerialization(), + is_null($refresh_token) ? null : $refresh_token->getValue() + ); + } + else // normal oauth2.0 code flow + { + $access_token = $token_service->createAccessToken($auth_code, $request->getRedirectUri()); + $refresh_token = $access_token->getRefreshToken(); + + $response = new OAuth2AccessTokenResponse + ( + $access_token->getValue(), + $access_token->getLifetime(), + is_null($refresh_token) ? null : $refresh_token->getValue() + ); + } + + return $response; + } + + /** + * @param AuthorizationCode $auth_code + * @return bool + */ + static public function authCodewasIssuedForOIDC(AuthorizationCode $auth_code) + { + return str_contains($auth_code->getScope(), OAuth2Protocol::OpenIdConnect_Scope); + } + +} \ No newline at end of file diff --git a/app/libs/oauth2/factories/OAuth2AuthorizationRequestFactory.php b/app/libs/oauth2/factories/OAuth2AuthorizationRequestFactory.php new file mode 100644 index 00000000..bd77eb64 --- /dev/null +++ b/app/libs/oauth2/factories/OAuth2AuthorizationRequestFactory.php @@ -0,0 +1,71 @@ +getScope(), OAuth2Protocol::OpenIdConnect_Scope) ) { + $auth_request = new OAuth2AuthenticationRequest($auth_request); + + } + return $auth_request; + } + + /** + * @var OAuth2AuthorizationRequestFactory + */ + private static $instance; + + private function __construct(){} + + private function __clone(){} + + /** + * @return OAuth2AuthorizationRequestFactory + */ + public static function getInstance() + { + if(!is_object(self::$instance)) + { + self::$instance = new OAuth2AuthorizationRequestFactory(); + } + return self::$instance; + } + +} \ No newline at end of file diff --git a/app/libs/oauth2/grant_types/AbstractGrantType.php b/app/libs/oauth2/grant_types/AbstractGrantType.php index 8437e212..e34416e1 100644 --- a/app/libs/oauth2/grant_types/AbstractGrantType.php +++ b/app/libs/oauth2/grant_types/AbstractGrantType.php @@ -2,32 +2,61 @@ namespace oauth2\grant_types; -use oauth2\exceptions\InvalidClientException; -use oauth2\exceptions\MissingClientIdParam; -use oauth2\exceptions\LockedClientException; use oauth2\exceptions\InvalidClientCredentials; - +use oauth2\exceptions\InvalidClientException; +use oauth2\exceptions\LockedClientException; +use oauth2\exceptions\MissingClientIdParam; +use oauth2\models\ClientAuthenticationContext; use oauth2\models\IClient; use oauth2\requests\OAuth2Request; use oauth2\services\IClientService; - use oauth2\services\ITokenService; - +use oauth2\strategies\ClientAuthContextValidatorFactory; use utils\services\ILogService; +/** + * Class AbstractGrantType + * @package oauth2\grant_types + */ abstract class AbstractGrantType implements IGrantType { + /** + * @var ClientAuthenticationContext + */ + protected $client_auth_context; + /** + * @var IClient + */ + protected $current_client; + + /** + * @var IClientService + */ protected $client_service; + /** + * @var ITokenService + */ protected $token_service; - //authorization info - protected $current_client_id; - protected $current_client_secret; - protected $current_client; + /** + * @var ILogService + */ protected $log_service; - public function __construct(IClientService $client_service, ITokenService $token_service, ILogService $log_service) + + /** + * @param IClientService $client_service + * @param ITokenService $token_service + * @param ILogService $log_service + */ + public function __construct + ( + IClientService $client_service, + ITokenService $token_service, + ILogService $log_service + + ) { $this->client_service = $client_service; $this->token_service = $token_service; @@ -37,33 +66,52 @@ abstract class AbstractGrantType implements IGrantType /** * @param OAuth2Request $request * @return mixed|void - * @throws \oauth2\exceptions\MissingClientIdParam - * @throws \oauth2\exceptions\InvalidClientCredentials - * @throws \oauth2\exceptions\InvalidClientException - * @throws \oauth2\exceptions\LockedClientException + * @throws MissingClientIdParam + * @throws InvalidClientCredentials + * @throws InvalidClientException + * @throws LockedClientException */ public function completeFlow(OAuth2Request $request) { //get client credentials from request.. - list($this->current_client_id, $this->current_client_secret) = $this->client_service->getCurrentClientAuthInfo(); + $this->client_auth_context = $this->client_service->getCurrentClientAuthInfo(); - //check if we have at least a client id - if (empty($this->current_client_id)) - throw new MissingClientIdParam(); //retrieve client from storage.. - $this->current_client = $this->client_service->getClientById($this->current_client_id); + $this->current_client = $this->client_service->getClientById($this->client_auth_context->getId()); if (is_null($this->current_client)) - throw new InvalidClientException($this->current_client_id,sprintf("client id %s does not exists!",$this->current_client_id)); + throw new InvalidClientException + ( + sprintf + ( + "client id %s does not exists!", + $this->client_auth_context->getId() + ) + ); if (!$this->current_client->isActive() || $this->current_client->isLocked()) { - throw new LockedClientException($this->current_client_id, sprintf('client id %s',$this->current_client_id)); + throw new LockedClientException + ( + sprintf + ( + 'client id %s is locked.', + $this->client_auth_context->getId() + ) + ); } - //verify client credentials (only for confidential clients ) - if ($this->current_client->getClientType() == IClient::ClientType_Confidential && $this->current_client->getClientSecret() !== $this->current_client_secret) - throw new InvalidClientCredentials($this->current_client_id, sprintf('client id %s',$this->current_client_id)); + $this->client_auth_context->setClient($this->current_client); + if(!ClientAuthContextValidatorFactory::build($this->client_auth_context)->validate($this->client_auth_context)) + throw new InvalidClientCredentials + ( + sprintf + ( + 'invalid credentials for client id %s.', + $this->client_auth_context->getId() + ) + ); } + } \ No newline at end of file diff --git a/app/libs/oauth2/grant_types/AuthorizationCodeGrantType.php b/app/libs/oauth2/grant_types/AuthorizationCodeGrantType.php index f3e5d9a4..53b17226 100644 --- a/app/libs/oauth2/grant_types/AuthorizationCodeGrantType.php +++ b/app/libs/oauth2/grant_types/AuthorizationCodeGrantType.php @@ -4,34 +4,47 @@ namespace oauth2\grant_types; use Exception; use oauth2\exceptions\AccessDeniedException; +use oauth2\exceptions\InvalidApplicationType; use oauth2\exceptions\InvalidAuthorizationCodeException; use oauth2\exceptions\InvalidClientException; +use oauth2\exceptions\InvalidClientType; +use oauth2\exceptions\InvalidLoginHint; use oauth2\exceptions\InvalidOAuth2Request; +use oauth2\exceptions\InvalidRedeemAuthCodeException; use oauth2\exceptions\LockedClientException; use oauth2\exceptions\OAuth2GenericException; use oauth2\exceptions\ScopeNotAllowedException; -use oauth2\exceptions\InvalidRedeemAuthCodeException; use oauth2\exceptions\UnsupportedResponseTypeException; -use oauth2\exceptions\InvalidApplicationType; - use oauth2\exceptions\UriNotAllowedException; +use oauth2\factories\OAuth2AccessTokenResponseFactory; +use oauth2\models\AuthorizationCode; use oauth2\models\IClient; use oauth2\OAuth2Protocol; +use oauth2\repositories\IServerPrivateKeyRepository; use oauth2\requests\OAuth2AccessTokenRequestAuthCode; +use oauth2\requests\OAuth2AuthenticationRequest; +use oauth2\requests\OAuth2AuthorizationRequest; use oauth2\requests\OAuth2Request; +use oauth2\requests\OAuth2TokenRequest; use oauth2\responses\OAuth2AccessTokenResponse; - use oauth2\responses\OAuth2AuthorizationResponse; -use oauth2\services\IClientService; -use oauth2\services\IMementoOAuth2AuthenticationRequestService; -use oauth2\services\ITokenService; -use oauth2\strategies\IOAuth2AuthenticationStrategy; +use oauth2\responses\OAuth2IdTokenResponse; +use oauth2\responses\OAuth2Response; use oauth2\services\IApiScopeService; - -use ReflectionClass; +use oauth2\services\IClientJWKSetReader; +use oauth2\services\IClientService; +use oauth2\services\IMementoOAuth2SerializerService; +use oauth2\services\IPrincipalService; +use oauth2\services\ISecurityContextService; +use oauth2\services\ITokenService; +use oauth2\services\IUserConsentService; +use oauth2\strategies\IOAuth2AuthenticationStrategy; +use openid\model\IOpenIdUser; use utils\services\IAuthService; use utils\services\ILogService; -use oauth2\services\IUserConsentService; +use oauth2\exceptions\InteractionRequiredException; +use oauth2\exceptions\LoginRequiredException; +use oauth2\exceptions\ConsentRequiredException; /** * Class AuthorizationCodeGrantType @@ -45,13 +58,56 @@ use oauth2\services\IUserConsentService; * http://tools.ietf.org/html/rfc6749#section-4.1 * @package oauth2\grant_types */ -class AuthorizationCodeGrantType extends AbstractGrantType +class AuthorizationCodeGrantType extends InteractiveGrantType { - private $auth_service; - private $auth_strategy; - private $memento_service; - private $scope_service; - private $user_consent_service; + + /** + * @param IApiScopeService $scope_service + * @param IClientService $client_service + * @param ITokenService $token_service + * @param IAuthService $auth_service + * @param IOAuth2AuthenticationStrategy $auth_strategy + * @param ILogService $log_service + * @param IUserConsentService $user_consent_service + * @param IMementoOAuth2SerializerService $memento_service + * @param ISecurityContextService $security_context_service + * @param IPrincipalService $principal_service + * @param IServerPrivateKeyRepository $server_private_key_repository + * @param IClientJWKSetReader $jwk_set_reader_service + */ + public function __construct + ( + IApiScopeService $scope_service, + IClientService $client_service, + ITokenService $token_service, + IAuthService $auth_service, + IOAuth2AuthenticationStrategy $auth_strategy, + ILogService $log_service, + IUserConsentService $user_consent_service, + IMementoOAuth2SerializerService $memento_service, + ISecurityContextService $security_context_service, + IPrincipalService $principal_service, + IServerPrivateKeyRepository $server_private_key_repository, + IClientJWKSetReader $jwk_set_reader_service + ) + { + + parent::__construct + ( + $client_service, + $token_service, + $log_service, + $security_context_service, + $principal_service, + $auth_service, + $user_consent_service, + $scope_service, + $auth_strategy, + $memento_service, + $server_private_key_repository, + $jwk_set_reader_service + ); + } /** * @param OAuth2Request $request @@ -59,31 +115,30 @@ class AuthorizationCodeGrantType extends AbstractGrantType */ public function canHandle(OAuth2Request $request) { - $reflector = new ReflectionClass($request); - $class_name = $reflector->getName(); - return - ($class_name == 'oauth2\requests\OAuth2AuthorizationRequest' && $request->isValid() && $request->getResponseType() == $this->getResponseType()) || - ($class_name == 'oauth2\requests\OAuth2TokenRequest' && $request->isValid() && $request->getGrantType() == $this->getType()); - } + if + ( + $request instanceof OAuth2AuthorizationRequest && + $request->isValid() && + OAuth2Protocol::responseTypeBelongsToFlow + ( + $request->getResponseType(false), + OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode + ) + ) + { + return true; + } + if + ( + $request instanceof OAuth2TokenRequest && + $request->isValid() && + $request->getGrantType() == $this->getType() + ) + { + return true; + } - /** - * @param IApiScopeService $scope_service - * @param IClientService $client_service - * @param ITokenService $token_service - * @param IAuthService $auth_service - * @param IMementoOAuth2AuthenticationRequestService $memento_service - * @param IOAuth2AuthenticationStrategy $auth_strategy - * @param ILogService $log_service - * @param IUserConsentService $user_consent_service - */ - public function __construct(IApiScopeService $scope_service ,IClientService $client_service, ITokenService $token_service, IAuthService $auth_service, IMementoOAuth2AuthenticationRequestService $memento_service, IOAuth2AuthenticationStrategy $auth_strategy, ILogService $log_service, IUserConsentService $user_consent_service) - { - parent::__construct($client_service, $token_service,$log_service); - $this->user_consent_service = $user_consent_service; - $this->scope_service = $scope_service; - $this->auth_service = $auth_service; - $this->memento_service = $memento_service; - $this->auth_strategy = $auth_strategy; + return false; } /** @@ -94,119 +149,12 @@ class AuthorizationCodeGrantType extends AbstractGrantType return OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode; } - /** Implements first request processing for Authorization code (Authorization Request processing) - * http://tools.ietf.org/html/rfc6749#section-4.1.1 and - * http://tools.ietf.org/html/rfc6749#section-4.1.2 - * @param OAuth2Request $request - * @return mixed|OAuth2AuthorizationResponse - * @throws \oauth2\exceptions\UnsupportedResponseTypeException - * @throws \oauth2\exceptions\LockedClientException - * @throws \oauth2\exceptions\InvalidClientException - * @throws \oauth2\exceptions\ScopeNotAllowedException - * @throws \oauth2\exceptions\OAuth2GenericException - * @throws \oauth2\exceptions\InvalidApplicationType - * @throws \oauth2\exceptions\AccessDeniedException - * @throws \oauth2\exceptions\UriNotAllowedException - * @throws \oauth2\exceptions\InvalidOAuth2Request - */ - public function handle(OAuth2Request $request) - { - - $reflector = new ReflectionClass($request); - $class_name = $reflector->getName(); - if ($class_name == 'oauth2\requests\OAuth2AuthorizationRequest') { - - $client_id = $request->getClientId(); - - $response_type = $request->getResponseType(); - - if ($response_type !== $this->getResponseType()) - throw new UnsupportedResponseTypeException(sprintf("response_type %s", $response_type)); - - $client = $this->client_service->getClientById($client_id); - if (is_null($client)) - throw new InvalidClientException($client_id, sprintf("client_id %s does not exists!", $client_id)); - - if (!$client->isActive() || $client->isLocked()) { - throw new LockedClientException(sprintf($client,'client id %s is locked',$client)); - } - - if ($client->getApplicationType() != IClient::ApplicationType_Web_App) - throw new InvalidApplicationType($client_id,sprintf("client id %s - Application type must be WEB_APPLICATION",$client_id)); - - //check redirect uri - $redirect_uri = $request->getRedirectUri(); - if (!$client->isUriAllowed($redirect_uri)) - throw new UriNotAllowedException(sprintf("redirect_to %s", $redirect_uri)); - - //check requested scope - $scope = $request->getScope(); - if (!$client->isScopeAllowed($scope)) - throw new ScopeNotAllowedException(sprintf("scope %s", $scope)); - - $state = $request->getState(); - - $authentication_response = $this->auth_service->getUserAuthenticationResponse(); - - if($authentication_response == IAuthService::AuthenticationResponse_Cancel){ - //clear saved data ... - $this->memento_service->clearCurrentRequest(); - $this->auth_service->clearUserAuthenticationResponse(); - $this->auth_service->clearUserAuthorizationResponse(); - throw new AccessDeniedException; - } - - //check user logged - if (!$this->auth_service->isUserLogged()) { - $this->memento_service->saveCurrentAuthorizationRequest(); - return $this->auth_strategy->doLogin($this->memento_service->getCurrentAuthorizationRequest()); - } - - $approval_prompt = $request->getApprovalPrompt(); - $access_type = $request->getAccessType(); - $user = $this->auth_service->getCurrentUser(); - - if(is_null($user)) - throw new OAuth2GenericException("Invalid Current User"); - - $authorization_response = $this->auth_service->getUserAuthorizationResponse(); - //check for former user consents - $former_user_consent = $this->user_consent_service->get($user->getId(),$client->getId(),$scope); - - if( !(!is_null($former_user_consent) && $approval_prompt == OAuth2Protocol::OAuth2Protocol_Approval_Prompt_Auto)){ - if ($authorization_response == IAuthService::AuthorizationResponse_None) { - $this->memento_service->saveCurrentAuthorizationRequest(); - return $this->auth_strategy->doConsent($this->memento_service->getCurrentAuthorizationRequest()); - } - else if ($authorization_response == IAuthService::AuthorizationResponse_DenyOnce) { - throw new AccessDeniedException; - } - //save possitive consent - if(is_null($former_user_consent)){ - $this->user_consent_service->add($user->getId(),$client->getId(),$scope); - } - } - // build current audience ... - $audience = $this->scope_service->getStrAudienceByScopeNames(explode(' ',$scope)); - - $auth_code = $this->token_service->createAuthorizationCode($user->getId(), $client_id, $scope, $audience, $redirect_uri,$access_type,$approval_prompt,!is_null($former_user_consent)); - - if (is_null($auth_code)) - throw new OAuth2GenericException("Invalid Auth Code"); - // clear save data ... - $this->auth_service->clearUserAuthorizationResponse(); - $this->memento_service->clearCurrentRequest(); - return new OAuth2AuthorizationResponse($redirect_uri, $auth_code->getValue() , $scope, $state); - } - throw new InvalidOAuth2Request; - } - /** - * @return mixed|string + * @return array */ public function getResponseType() { - return OAuth2Protocol::OAuth2Protocol_ResponseType_Code; + return OAuth2Protocol::getValidResponseTypes(OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode); } /** @@ -225,59 +173,103 @@ class AuthorizationCodeGrantType extends AbstractGrantType public function completeFlow(OAuth2Request $request) { - $reflector = new ReflectionClass($request); - $class_name = $reflector->getName(); - try{ - if ($class_name == 'oauth2\requests\OAuth2AccessTokenRequestAuthCode') { + if (!($request instanceof OAuth2AccessTokenRequestAuthCode)) + { + throw new InvalidOAuth2Request; + } - parent::completeFlow($request); + try + { + parent::completeFlow($request); - //only confidential clients could use this grant type + $this->checkClientTypeAccess($this->client_auth_context->getClient()); - if ($this->current_client->getApplicationType() != IClient::ApplicationType_Web_App) - throw new InvalidApplicationType($this->current_client_id,sprintf("client id %s - Application type must be WEB_APPLICATION",$this->current_client_id)); - - $current_redirect_uri = $request->getRedirectUri(); - //verify redirect uri - if (!$this->current_client->isUriAllowed($current_redirect_uri)) - throw new UriNotAllowedException(sprintf('redirect url %s is not allowed for cliend id %s',$current_redirect_uri,$this->current_client_id)); - - $code = $request->getCode(); - // verify that the authorization code is valid - // The client MUST NOT use the authorization code - // more than once. If an authorization code is used more than - // once, the authorization server MUST deny the request and SHOULD - // revoke (when possible) all tokens previously issued based on - // that authorization code. The authorization code is bound to - // the client identifier and redirection URI. - $auth_code = $this->token_service->getAuthorizationCode($code); - - $client_id = $auth_code->getClientId(); - - //ensure that the authorization code was issued to the authenticated - //confidential client, or if the client is public, ensure that the - //code was issued to "client_id" in the request - if ($client_id != $this->current_client_id) - throw new InvalidRedeemAuthCodeException($this->current_client_id,sprintf("auth code was issued for another client id!.")); - - // ensure that the "redirect_uri" parameter is present if the - // "redirect_uri" parameter was included in the initial authorization - // and if included ensure that their values are identical. - $redirect_uri = $auth_code->getRedirectUri(); - if (!empty($redirect_uri) && $redirect_uri !== $current_redirect_uri) - throw new UriNotAllowedException(); - - $access_token = $this->token_service->createAccessToken($auth_code, $current_redirect_uri); - $refresh_token = $access_token->getRefreshToken(); - $response = new OAuth2AccessTokenResponse($access_token->getValue(), $access_token->getLifetime(), !is_null($refresh_token) ? $refresh_token->getValue() : null); - return $response; + $current_redirect_uri = $request->getRedirectUri(); + //verify redirect uri + if (!$this->current_client->isUriAllowed($current_redirect_uri)) + { + throw new UriNotAllowedException + ( + $redirect_uri + ); } + + $code = $request->getCode(); + // verify that the authorization code is valid + // The client MUST NOT use the authorization code + // more than once. If an authorization code is used more than + // once, the authorization server MUST deny the request and SHOULD + // revoke (when possible) all tokens previously issued based on + // that authorization code. The authorization code is bound to + // the client identifier and redirection URI. + $auth_code = $this->token_service->getAuthorizationCode($code); + + // reload session state + $client_id = $auth_code->getClientId(); + + $this->security_context_service->save + ( + $this->security_context_service->get() + ->setAuthTimeRequired + ( + $auth_code->isAuthTimeRequested() + ) + ->setRequestedUserId + ( + $auth_code->getUserId() + ) + ); + + $this->principal_service->register + ( + $auth_code->getUserId(), + $auth_code->getAuthTime() + ); + + //ensure that the authorization code was issued to the authenticated + //confidential client, or if the client is public, ensure that the + //code was issued to "client_id" in the request + if ($client_id != $this->client_auth_context->getId()) + { + throw new InvalidRedeemAuthCodeException + ( + sprintf + ( + "auth code was issued for another client id!." + ) + ); + } + + // ensure that the "redirect_uri" parameter is present if the + // "redirect_uri" parameter was included in the initial authorization + // and if included ensure that their values are identical. + $redirect_uri = $auth_code->getRedirectUri(); + + if (!empty($redirect_uri) && $redirect_uri !== $current_redirect_uri) + { + throw new UriNotAllowedException($current_redirect_uri); + } + + $response = OAuth2AccessTokenResponseFactory::build + ( + $this->token_service, + $auth_code, + $request + ); + + $this->security_context_service->clear(); + + return $response; } - catch(InvalidAuthorizationCodeException $ex){ + catch (InvalidAuthorizationCodeException $ex) + { $this->log_service->error($ex); - throw new InvalidRedeemAuthCodeException($this->current_client_id,$ex->getMessage()); + $this->security_context_service->clear(); + throw new InvalidRedeemAuthCodeException + ( + $ex->getMessage() + ); } - throw new InvalidOAuth2Request; } /** @@ -286,13 +278,114 @@ class AuthorizationCodeGrantType extends AbstractGrantType */ public function buildTokenRequest(OAuth2Request $request) { - $reflector = new ReflectionClass($request); - $class_name = $reflector->getName(); - if ($class_name == 'oauth2\requests\OAuth2TokenRequest') { + if ($request instanceof OAuth2TokenRequest) + { if ($request->getGrantType() !== $this->getType()) + { return null; + } return new OAuth2AccessTokenRequestAuthCode($request->getMessage()); } return null; } + + /** + * @param IClient $client + * @throws InvalidApplicationType + * @throws InvalidClientType + * @return void + */ + protected function checkClientTypeAccess(IClient $client) + { + if + ( + !( + $client->getClientType() === IClient::ClientType_Confidential || + $client->getApplicationType() === IClient::ApplicationType_Native + ) + ) + { + throw new InvalidApplicationType + ( + sprintf + ( + "client id %s - Application type must be %s or %s", + $client->getClientId(), + IClient::ClientType_Confidential, + IClient::ApplicationType_Native + ) + ); + } + } + + /** + * @param OAuth2AuthorizationRequest $request + * @param bool $has_former_consent + * @return OAuth2AuthorizationResponse + * @throws OAuth2GenericException + */ + protected function buildResponse(OAuth2AuthorizationRequest $request, $has_former_consent) + { + $user = $this->auth_service->getCurrentUser(); + $client = $this->client_service->getClientById($request->getClientId()); + + // build current audience ... + $audience = $this->scope_service->getStrAudienceByScopeNames + ( + explode + ( + OAuth2Protocol::OAuth2Protocol_Scope_Delimiter, + $request->getScope() + ) + ); + + $nonce = null; + + if($request instanceof OAuth2AuthenticationRequest) + { + $nonce = $request->getNonce(); + } + + $auth_code = $this->token_service->createAuthorizationCode + ( + $user->getId(), + $request->getClientId(), + $request->getScope(), + $audience, + $request->getRedirectUri(), + $request->getAccessType(), + $request->getApprovalPrompt(), + $has_former_consent, + $request->getState(), + $nonce, + $request->getResponseType() + ); + + if (is_null($auth_code)) + { + throw new OAuth2GenericException("Invalid Auth Code"); + } + // http://openid.net/specs/openid-connect-session-1_0.html#CreatingUpdatingSessions + $session_state = self::getSessionState + ( + self::getOrigin + ( + $request->getRedirectUri() + ), + $request->getClientId(), + + $this->principal_service->get()->getOPBrowserState() + ); + + return new OAuth2AuthorizationResponse + ( + $request->getRedirectUri(), + $auth_code->getValue(), + $request->getScope(), + $request->getState(), + $session_state + ); + } + + } \ No newline at end of file diff --git a/app/libs/oauth2/grant_types/ClientCredentialsGrantType.php b/app/libs/oauth2/grant_types/ClientCredentialsGrantType.php index c9c89de5..3b874f6f 100644 --- a/app/libs/oauth2/grant_types/ClientCredentialsGrantType.php +++ b/app/libs/oauth2/grant_types/ClientCredentialsGrantType.php @@ -2,22 +2,19 @@ namespace oauth2\grant_types; +use oauth2\exceptions\InvalidApplicationType; use oauth2\exceptions\InvalidGrantTypeException; use oauth2\exceptions\InvalidOAuth2Request; use oauth2\exceptions\ScopeNotAllowedException; -use oauth2\exceptions\InvalidApplicationType; - use oauth2\models\IClient; use oauth2\OAuth2Protocol; use oauth2\requests\OAuth2AccessTokenRequestClientCredentials; - use oauth2\requests\OAuth2Request; +use oauth2\requests\OAuth2TokenRequest; use oauth2\responses\OAuth2AccessTokenResponse; use oauth2\services\IApiScopeService; use oauth2\services\IClientService; - use oauth2\services\ITokenService; -use ReflectionClass; use utils\services\ILogService; /** @@ -34,12 +31,27 @@ use utils\services\ILogService; class ClientCredentialsGrantType extends AbstractGrantType { - + /** + * @var IApiScopeService + */ private $scope_service; - public function __construct(IApiScopeService $scope_service, IClientService $client_service, ITokenService $token_service, ILogService $log_service) + /** + * @param IApiScopeService $scope_service + * @param IClientService $client_service + * @param ITokenService $token_service + * @param ILogService $log_service + */ + public function __construct + ( + IApiScopeService $scope_service, + IClientService $client_service, + ITokenService $token_service, + ILogService $log_service + ) { parent::__construct($client_service, $token_service, $log_service); + $this->scope_service = $scope_service; } @@ -49,10 +61,7 @@ class ClientCredentialsGrantType extends AbstractGrantType */ public function canHandle(OAuth2Request $request) { - $reflector = new ReflectionClass($request); - $class_name = $reflector->getName(); - return - ($class_name == 'oauth2\requests\OAuth2TokenRequest' && $request->isValid() && $request->getGrantType() == $this->getType()); + return $request instanceof OAuth2TokenRequest && $request->isValid() && $request->getGrantType() == $this->getType(); } @@ -86,34 +95,45 @@ class ClientCredentialsGrantType extends AbstractGrantType */ public function completeFlow(OAuth2Request $request) { - $reflector = new ReflectionClass($request); - $class_name = $reflector->getName(); - if ($class_name == 'oauth2\requests\OAuth2AccessTokenRequestClientCredentials') { - - if($request->getGrantType()!=$this->getType()) - throw new InvalidGrantTypeException; - - parent::completeFlow($request); - - //only confidential clients could use this grant type - if ($this->current_client->getApplicationType() != IClient::ApplicationType_Service) - throw new InvalidApplicationType($this->current_client_id,sprintf('client id %s client type must be SERVICE',$this->current_client_id)); - - //check requested scope - $scope = $request->getScope(); - if (is_null($scope) || empty($scope) || !$this->current_client->isScopeAllowed($scope)) - throw new ScopeNotAllowedException(sprintf("scope %s", $scope)); - - // build current audience ... - $audience = $this->scope_service->getStrAudienceByScopeNames(explode(' ', $scope)); - - //build access token - $access_token = $this->token_service->createAccessTokenFromParams($this->current_client_id,$scope, $audience); - - $response = new OAuth2AccessTokenResponse($access_token->getValue(), $access_token->getLifetime(), null); - return $response; + if (!($request instanceof OAuth2AccessTokenRequestClientCredentials)) { + throw new InvalidOAuth2Request; } - throw new InvalidOAuth2Request; + + if ($request->getGrantType() != $this->getType()) { + throw new InvalidGrantTypeException; + } + + parent::completeFlow($request); + + //only confidential clients could use this grant type + if ($this->current_client->getApplicationType() != IClient::ApplicationType_Service) { + throw new InvalidApplicationType + ( + sprintf + ( + 'client id %s client type must be %s', + $this->client_auth_context->getId(), + IClient::ApplicationType_Service + ) + ); + } + + //check requested scope + $scope = $request->getScope(); + if (is_null($scope) || empty($scope) || !$this->current_client->isScopeAllowed($scope)) { + throw new ScopeNotAllowedException(sprintf("scope %s", $scope)); + } + + // build current audience ... + $audience = $this->scope_service->getStrAudienceByScopeNames(explode(' ', $scope)); + + //build access token + $access_token = $this->token_service->createAccessTokenFromParams($this->client_auth_context->getId(), $scope, $audience); + + $response = new OAuth2AccessTokenResponse($access_token->getValue(), $access_token->getLifetime(), null); + + return $response; + } /** builds specific Token request @@ -122,13 +142,16 @@ class ClientCredentialsGrantType extends AbstractGrantType */ public function buildTokenRequest(OAuth2Request $request) { - $reflector = new ReflectionClass($request); - $class_name = $reflector->getName(); - if ($class_name == 'oauth2\requests\OAuth2TokenRequest') { + + if ($request instanceof OAuth2TokenRequest) + { if ($request->getGrantType() !== $this->getType()) + { return null; + } return new OAuth2AccessTokenRequestClientCredentials($request->getMessage()); } + return null; } diff --git a/app/libs/oauth2/grant_types/HybridGrantType.php b/app/libs/oauth2/grant_types/HybridGrantType.php new file mode 100644 index 00000000..2b5b9bf5 --- /dev/null +++ b/app/libs/oauth2/grant_types/HybridGrantType.php @@ -0,0 +1,258 @@ +isValid() && + OAuth2Protocol::responseTypeBelongsToFlow + ( + $request->getResponseType(false), + OAuth2Protocol::OAuth2Protocol_GrantType_Hybrid + ) + ); + } + + /** + * get grant type + * @return mixed + */ + public function getType() + { + return OAuth2Protocol::OAuth2Protocol_GrantType_Hybrid; + } + + /** + * get grant type response type + * @return array + */ + public function getResponseType() + { + return OAuth2Protocol::getValidResponseTypes(OAuth2Protocol::OAuth2Protocol_GrantType_Hybrid); + } + + /** builds specific Token request + * @param OAuth2Request $request + * @return mixed + */ + public function buildTokenRequest(OAuth2Request $request) + { + throw new InvalidOAuth2Request('not implemented!'); + } + + /** + * @param OAuth2AuthorizationRequest $request + * @param bool $has_former_consent + * @return OAuth2Response + */ + protected function buildResponse(OAuth2AuthorizationRequest $request, $has_former_consent) + { + if (!($request instanceof OAuth2AuthenticationRequest)) { + throw new InvalidOAuth2Request; + } + + $user = $this->auth_service->getCurrentUser(); + + // build current audience ... + $audience = $this->scope_service->getStrAudienceByScopeNames + ( + explode + ( + OAuth2Protocol::OAuth2Protocol_Scope_Delimiter, + $request->getScope() + ) + ); + + // http://openid.net/specs/openid-connect-session-1_0.html#CreatingUpdatingSessions + $session_state = self::getSessionState + ( + self::getOrigin + ( + $request->getRedirectUri() + ), + $request->getClientId(), + + $this->principal_service->get()->getOPBrowserState() + ); + + $auth_code = $this->token_service->createAuthorizationCode + ( + $user->getId(), + $request->getClientId(), + $request->getScope(), + $audience, + $request->getRedirectUri(), + $request->getAccessType(), + $request->getApprovalPrompt(), + $has_former_consent, + $request->getState(), + $request->getNonce(), + $request->getResponseType() + ); + + if (is_null($auth_code)) { + throw new OAuth2GenericException("Invalid Auth Code"); + } + + $access_token = null; + $id_token = null; + + + if (in_array(OAuth2Protocol::OAuth2Protocol_ResponseType_Token, $request->getResponseType(false))) + { + $access_token = $this->token_service->createAccessToken + ( + $auth_code, + $request->getRedirectUri() + ); + } + + if (in_array(OAuth2Protocol::OAuth2Protocol_ResponseType_IdToken, $request->getResponseType(false))) + { + + $id_token = $this->token_service->createIdToken + ( + $request->getNonce(), + $request->getClientId(), + $access_token, + $auth_code + ); + } + + return new OAuth2HybridTokenFragmentResponse + ( + $request->getRedirectUri(), + $auth_code->getValue(), + is_null($access_token) ? null : $access_token->getValue(), + is_null($access_token) ? null : $access_token->getLifetime(), + is_null($access_token) ? null : $request->getScope(), + $request->getState(), + $session_state, + is_null($id_token) ? null : $id_token->toCompactSerialization() + ); + } + + /** + * @param IClient $client + * @throws InvalidApplicationType + * @throws InvalidClientType + * @return void + */ + protected function checkClientTypeAccess(IClient $client) + { + if + ( + !( + $client->getClientType() === IClient::ClientType_Confidential || + $client->getApplicationType() === IClient::ApplicationType_Native + ) + ) + { + throw new InvalidApplicationType + ( + sprintf + ( + "client id %s - Application type must be %s or %s", + $client->getClientId(), + IClient::ClientType_Confidential, + IClient::ApplicationType_Native + ) + ); + } + } +} \ No newline at end of file diff --git a/app/libs/oauth2/grant_types/IGrantType.php b/app/libs/oauth2/grant_types/IGrantType.php index 9ab599d5..e9dbea09 100644 --- a/app/libs/oauth2/grant_types/IGrantType.php +++ b/app/libs/oauth2/grant_types/IGrantType.php @@ -9,7 +9,8 @@ use oauth2\requests\OAuth2Request; * Defines a common interface for new OAuth2 Grant Types * @package oauth2\grant_types */ -interface IGrantType { +interface IGrantType +{ /** Given an OAuth2Request, returns true if it can handle it, false otherwise * @param OAuth2Request $request @@ -35,8 +36,9 @@ interface IGrantType { */ public function getType(); - /** get grant type response type - * @return mixed + /** + * get grant type response type + * @return array */ public function getResponseType(); diff --git a/app/libs/oauth2/grant_types/ImplicitGrantType.php b/app/libs/oauth2/grant_types/ImplicitGrantType.php index a8fcb8ea..12c0ca05 100644 --- a/app/libs/oauth2/grant_types/ImplicitGrantType.php +++ b/app/libs/oauth2/grant_types/ImplicitGrantType.php @@ -2,31 +2,28 @@ namespace oauth2\grant_types; -use oauth2\exceptions\AccessDeniedException; -use oauth2\exceptions\InvalidClientException; -use oauth2\exceptions\InvalidOAuth2Request; -use oauth2\exceptions\ScopeNotAllowedException; -use oauth2\exceptions\OAuth2GenericException; use oauth2\exceptions\InvalidApplicationType; -use oauth2\exceptions\LockedClientException; - -use oauth2\exceptions\UnsupportedResponseTypeException; -use oauth2\exceptions\UriNotAllowedException; +use oauth2\exceptions\InvalidClientType; +use oauth2\exceptions\InvalidOAuth2Request; +use oauth2\factories\OAuth2AccessTokenFragmentResponseFactory; use oauth2\models\IClient; use oauth2\OAuth2Protocol; +use oauth2\repositories\IServerPrivateKeyRepository; +use oauth2\requests\OAuth2AuthorizationRequest; use oauth2\requests\OAuth2Request; - use oauth2\responses\OAuth2AccessTokenFragmentResponse; use oauth2\services\IApiScopeService; +use oauth2\services\IClientJWKSetReader; use oauth2\services\IClientService; +use oauth2\services\IMementoOAuth2SerializerService; +use oauth2\services\IPrincipalService; +use oauth2\services\ISecurityContextService; use oauth2\services\ITokenService; -use oauth2\services\IMementoOAuth2AuthenticationRequestService; - +use oauth2\services\IUserConsentService; use oauth2\strategies\IOAuth2AuthenticationStrategy; -use ReflectionClass; use utils\services\IAuthService; use utils\services\ILogService; -use oauth2\services\IUserConsentService; + /** * Class ImplicitGrantType * http://tools.ietf.org/html/rfc6749#section-4.2 @@ -50,21 +47,54 @@ use oauth2\services\IUserConsentService; * applications residing on the same device. * @package oauth2\grant_types */ -class ImplicitGrantType extends AbstractGrantType +class ImplicitGrantType extends InteractiveGrantType { - private $auth_service; - private $auth_strategy; - private $scope_service; - - public function __construct(IApiScopeService $scope_service, IClientService $client_service, ITokenService $token_service, IAuthService $auth_service, IMementoOAuth2AuthenticationRequestService $memento_service, IOAuth2AuthenticationStrategy $auth_strategy, ILogService $log_service, IUserConsentService $user_consent_service) + /*** + * @param IApiScopeService $scope_service + * @param IClientService $client_service + * @param ITokenService $token_service + * @param IAuthService $auth_service + * @param IOAuth2AuthenticationStrategy $auth_strategy + * @param ILogService $log_service + * @param IUserConsentService $user_consent_service + * @param IMementoOAuth2SerializerService $memento_service + * @param ISecurityContextService $security_context_service + * @param IPrincipalService $principal_service + * @param IServerPrivateKeyRepository $server_private_key_repository + * @param IClientJWKSetReader $jwk_set_reader_service + */ + public function __construct + ( + IApiScopeService $scope_service, + IClientService $client_service, + ITokenService $token_service, + IAuthService $auth_service, + IOAuth2AuthenticationStrategy $auth_strategy, + ILogService $log_service, + IUserConsentService $user_consent_service, + IMementoOAuth2SerializerService $memento_service, + ISecurityContextService $security_context_service, + IPrincipalService $principal_service, + IServerPrivateKeyRepository $server_private_key_repository, + IClientJWKSetReader $jwk_set_reader_service + ) { - parent::__construct($client_service, $token_service, $log_service); - $this->user_consent_service = $user_consent_service; - $this->scope_service = $scope_service; - $this->auth_service = $auth_service; - $this->memento_service = $memento_service; - $this->auth_strategy = $auth_strategy; + parent::__construct + ( + $client_service, + $token_service, + $log_service, + $security_context_service, + $principal_service, + $auth_service, + $user_consent_service, + $scope_service, + $auth_strategy, + $memento_service, + $server_private_key_repository, + $jwk_set_reader_service + ); } /** Given an OAuth2Request, returns true if it can handle it, false otherwise @@ -73,129 +103,36 @@ class ImplicitGrantType extends AbstractGrantType */ public function canHandle(OAuth2Request $request) { - $reflector = new ReflectionClass($request); - $class_name = $reflector->getName(); return - ($class_name == 'oauth2\requests\OAuth2AuthorizationRequest' && $request->isValid() && $request->getResponseType() == $this->getResponseType()); - } - - /** get grant type response type - * @return mixed - */ - public function getResponseType() - { - return OAuth2Protocol::OAuth2Protocol_ResponseType_Token; + ( + $request instanceof OAuth2AuthorizationRequest && + $request->isValid() && + OAuth2Protocol::responseTypeBelongsToFlow + ( + $request->getResponseType(false), + OAuth2Protocol::OAuth2Protocol_GrantType_Implicit + ) + ); } /** - * @param OAuth2Request $request - * @return mixed|OAuth2AccessTokenFragmentResponse - * @throws \oauth2\exceptions\UnsupportedResponseTypeException - * @throws \oauth2\exceptions\LockedClientException - * @throws \oauth2\exceptions\InvalidClientException - * @throws \oauth2\exceptions\ScopeNotAllowedException - * @throws \oauth2\exceptions\OAuth2GenericException - * @throws \oauth2\exceptions\InvalidApplicationType - * @throws \oauth2\exceptions\AccessDeniedException - * @throws \oauth2\exceptions\UriNotAllowedException - * @throws \oauth2\exceptions\InvalidOAuth2Request + * get grant type response type + * OAuth 2.0 Response Type value that determines the authorization processing flow to be used, including what + * parameters are returned from the endpoints used. When using the Implicit Flow, this value is id_token token or + * id_token. The meanings of both of these values are defined in OAuth 2.0 Multiple Response Type Encoding Practices + * [OAuth.Responses]. No Access Token is returned when the value is id_token. + * NOTE: While OAuth 2.0 also defines the token Response Type value for the Implicit Flow, OpenID Connect does not + * use this Response Type, since no ID Token would be returned. + * @return array */ - public function handle(OAuth2Request $request) + public function getResponseType() { - $reflector = new ReflectionClass($request); - $class_name = $reflector->getName(); - if ($class_name == 'oauth2\requests\OAuth2AuthorizationRequest') { - - $client_id = $request->getClientId(); - - $response_type = $request->getResponseType(); - - if ($response_type !== $this->getResponseType()) - throw new UnsupportedResponseTypeException(sprintf("response_type %s", $response_type)); - - $client = $this->client_service->getClientById($client_id); - - if (is_null($client)) - throw new InvalidClientException($client_id, sprintf("client_id %s", $client_id)); - - if (!$client->isActive() || $client->isLocked()) { - throw new LockedClientException($client,sprintf('client id %s',$client)); - } - - //check client type - // only public clients could use this grant type - if ($client->getApplicationType() != IClient::ApplicationType_JS_Client) - throw new InvalidApplicationType($client_id,sprintf('client id %s client type must be JS CLIENT',$client_id)); - - //check redirect uri - $redirect_uri = $request->getRedirectUri(); - if (!$client->isUriAllowed($redirect_uri)) - throw new UriNotAllowedException(sprintf("redirect_to %s", $redirect_uri)); - - //check requested scope - $scope = $request->getScope(); - - if (is_null($scope) || empty($scope) || !$client->isScopeAllowed($scope)) - throw new ScopeNotAllowedException(sprintf("scope %s", $scope)); - - $state = $request->getState(); - //check user logged - - $authentication_response = $this->auth_service->getUserAuthenticationResponse(); - - if($authentication_response == IAuthService::AuthenticationResponse_Cancel){ - //clear saved data ... - $this->memento_service->clearCurrentRequest(); - $this->auth_service->clearUserAuthenticationResponse(); - $this->auth_service->clearUserAuthorizationResponse(); - throw new AccessDeniedException; - } - - if (!$this->auth_service->isUserLogged()) { - $this->memento_service->saveCurrentAuthorizationRequest(); - return $this->auth_strategy->doLogin($this->memento_service->getCurrentAuthorizationRequest()); - } - - $approval_prompt = $request->getApprovalPrompt(); - - $user = $this->auth_service->getCurrentUser(); - - if(is_null($user)) - throw new OAuth2GenericException("Invalid Current User"); - //validate authorization - //check for former user consents - $authorization_response = $this->auth_service->getUserAuthorizationResponse(); - $former_user_consent = $this->user_consent_service->get($user->getId(),$client->getId(),$scope); - if( !(!is_null($former_user_consent) && $approval_prompt == OAuth2Protocol::OAuth2Protocol_Approval_Prompt_Auto)){ - if ($authorization_response == IAuthService::AuthorizationResponse_None) { - $this->memento_service->saveCurrentAuthorizationRequest(); - return $this->auth_strategy->doConsent($this->memento_service->getCurrentAuthorizationRequest()); - } - else if ($authorization_response == IAuthService::AuthorizationResponse_DenyOnce) { - //clear saved data ... - $this->memento_service->clearCurrentRequest(); - $this->auth_service->clearUserAuthorizationResponse(); - throw new AccessDeniedException; - } - //save possitive consent - if(is_null($former_user_consent)) - $this->user_consent_service->add($user->getId(),$client->getId(),$scope); - } - - - // build current audience ... - $audience = $this->scope_service->getStrAudienceByScopeNames(explode(' ',$scope)); - //build access token - $access_token = $this->token_service->createAccessTokenFromParams($client_id,$scope, $audience,$user->getId()); - //clear saved data ... - $this->memento_service->clearCurrentRequest(); - $this->auth_service->clearUserAuthorizationResponse(); - return new OAuth2AccessTokenFragmentResponse($redirect_uri, $access_token->getValue(), $access_token->getLifetime(), $scope, $state); - } - throw new InvalidOAuth2Request; + return OAuth2Protocol::getValidResponseTypes(OAuth2Protocol::OAuth2Protocol_GrantType_Implicit); } - public function completeFlow(OAuth2Request $request){ + + public function completeFlow(OAuth2Request $request) + { throw new InvalidOAuth2Request('not implemented!'); } @@ -217,4 +154,67 @@ class ImplicitGrantType extends AbstractGrantType { throw new InvalidOAuth2Request('not implemented!'); } + + /** + * @param OAuth2AuthorizationRequest $request + * @param bool $has_former_consent + * @return OAuth2AccessTokenFragmentResponse + */ + protected function buildResponse(OAuth2AuthorizationRequest $request, $has_former_consent) + { + // build current audience ... + $audience = $this->scope_service->getStrAudienceByScopeNames + ( + explode + ( + OAuth2Protocol::OAuth2Protocol_Scope_Delimiter, + $request->getScope() + ) + ); + + // http://openid.net/specs/openid-connect-session-1_0.html#CreatingUpdatingSessions + $session_state = self::getSessionState + ( + self::getOrigin + ( + $request->getRedirectUri() + ), + $request->getClientId(), + + $this->principal_service->get()->getOPBrowserState() + ); + + return OAuth2AccessTokenFragmentResponseFactory::build + ( + $request, + $audience, + $session_state, + $this->auth_service->getCurrentUser(), + $this->token_service + ); + } + + /** + * @param IClient $client + * @throws InvalidApplicationType + * @throws InvalidClientType + * @return void + */ + protected function checkClientTypeAccess(IClient $client) + { + //check client type + // only public clients could use this grant type + if( $client->getClientType() != IClient::ClientType_Public ) + { + throw new InvalidClientType + ( + sprintf + ( + 'client id %s client type must be %s', + $client->getClientId(), + IClient::ClientType_Public + ) + ); + } + } } \ No newline at end of file diff --git a/app/libs/oauth2/grant_types/InteractiveGrantType.php b/app/libs/oauth2/grant_types/InteractiveGrantType.php new file mode 100644 index 00000000..7bd0b23d --- /dev/null +++ b/app/libs/oauth2/grant_types/InteractiveGrantType.php @@ -0,0 +1,628 @@ +security_context_service = $security_context_service; + $this->principal_service = $principal_service; + $this->auth_service = $auth_service; + $this->user_consent_service = $user_consent_service; + $this->scope_service = $scope_service; + $this->auth_strategy = $auth_strategy; + $this->memento_service = $memento_service; + $this->server_private_key_repository = $server_private_key_repository; + $this->jwk_set_reader_service = $jwk_set_reader_service; + } + + public function handle(OAuth2Request $request) + { + try + { + + if (!($request instanceof OAuth2AuthorizationRequest)) + { + throw new InvalidOAuth2Request; + } + + $client_id = $request->getClientId(); + $client = $this->client_service->getClientById($client_id); + + if (is_null($client)) { + throw new InvalidClientException + ( + sprintf + ( + "client_id %s does not exists!", + $client_id + ) + ); + } + + if (!$client->isActive() || $client->isLocked()) { + throw new LockedClientException + ( + sprintf + ( + 'client id %s is locked', + $client_id + ) + ); + } + + $this->checkClientTypeAccess($client); + + //check redirect uri + $redirect_uri = $request->getRedirectUri(); + + if (!$client->isUriAllowed($redirect_uri)) { + throw new UriNotAllowedException + ( + $redirect_uri + ); + } + + //check requested scope + $scope = $request->getScope(); + + if (!$client->isScopeAllowed($scope)) { + throw new ScopeNotAllowedException(sprintf("scope %s", $scope)); + } + + $authentication_response = $this->auth_service->getUserAuthenticationResponse(); + + // user has cancelled login action + if ($authentication_response == IAuthService::AuthenticationResponse_Cancel) { + //clear saved data ... + $this->memento_service->forget(); + $this->auth_service->clearUserAuthenticationResponse(); + $this->auth_service->clearUserAuthorizationResponse(); + + if ($this->shouldPromptLogin($request)) { + throw new LoginRequiredException; + } + + throw new AccessDeniedException; + } + + //check user logged + if ($this->mustAuthenticateUser($request, $client)) { + if (!$this->canInteractWithEndUser($request)) { + throw new LoginRequiredException; + } + + $this->memento_service->serialize($request->getMessage()->createMemento()); + + return $this->auth_strategy->doLogin($request); + } + + $approval_prompt = $request->getApprovalPrompt(); + $user = $this->auth_service->getCurrentUser(); + + // check if logged user its the same as login hint + $requested_user_id = $this->security_context_service->get()->getRequestedUserId(); + + if (is_null($user)) { + throw new OAuth2GenericException("Invalid Current User"); + } + + if (!is_null($requested_user_id) && $requested_user_id !== $user->getId()) { + $this->auth_service->logout(); + throw new InvalidLoginHint('invalid login hint'); + } + + $authorization_response = $this->auth_service->getUserAuthorizationResponse(); + //check for former user consents + $former_user_consent = $this->user_consent_service->get + ( + $user->getId(), + $client->getId(), + $scope + ); + + $auto_approval = $approval_prompt == OAuth2Protocol::OAuth2Protocol_Approval_Prompt_Auto; + $has_former_consent = !is_null($former_user_consent); + $should_prompt_consent = $this->shouldPromptConsent($request); + + if ($should_prompt_consent || !($has_former_consent && $auto_approval)) { + if ($should_prompt_consent || $authorization_response == IAuthService::AuthorizationResponse_None) { + if (!$this->canInteractWithEndUser($request)) { + throw new InteractionRequiredException; + } + + $this->memento_service->serialize($request->getMessage()->createMemento()); + + return $this->auth_strategy->doConsent($request); + } else { + if ($authorization_response == IAuthService::AuthorizationResponse_DenyOnce) { + if ($this->hadPromptConsent($request)) { + throw new ConsentRequiredException('the user denied access to your application'); + } + + throw new AccessDeniedException; + } + } + //save possitive consent + if (is_null($former_user_consent)) { + $this->user_consent_service->add($user->getId(), $client->getId(), $scope); + } + } + $this->auth_service->registerRPLogin($client_id); + + $response = $this->buildResponse($request, $has_former_consent); + + // clear save data ... + $this->auth_service->clearUserAuthorizationResponse(); + $this->memento_service->forget(); + + return $response; + } + catch(\Exception $ex) + { + // clear save data ... + $this->auth_service->clearUserAuthorizationResponse(); + $this->memento_service->forget(); + throw $ex; + } + } + + /** + * @param string $origin + * @param string $client_id + * @param string $session_id + * @return string + */ + static public function getSessionState($origin, $client_id, $session_id) + { + $salt = bin2hex(mcrypt_create_iv(16, MCRYPT_DEV_URANDOM)); + $session_state = hash('sha256', "{$client_id}{$origin}{$session_id}{$salt}") . '.' . $salt; + return $session_state; + } + + /** + * @param string $url + * @return string + */ + static public function getOrigin($url) + { + $url_parts = @parse_url($url); + return sprintf("%s://%s%s", $url_parts['scheme'], $url_parts['host'], isset($url_parts['port']) ? ':' . $url_parts['port'] : ''); + } + + /** + * @param OAuth2AuthorizationRequest $request + * @param bool $has_former_consent + * @return OAuth2Response + */ + abstract protected function buildResponse(OAuth2AuthorizationRequest $request, $has_former_consent); + + /** + * @param IClient $client + * @throws InvalidApplicationType + * @throws InvalidClientType + * @return void + */ + abstract protected function checkClientTypeAccess(IClient $client); + + /** + * @param OAuth2AuthorizationRequest $request + * @return bool + */ + protected function canInteractWithEndUser(OAuth2AuthorizationRequest $request) + { + if($request instanceof OAuth2AuthenticationRequest && in_array(OAuth2Protocol::OAuth2Protocol_Prompt_None, $request->getPrompt())) + { + return false; + } + return true; + } + + /** + * @param OAuth2AuthenticationRequest $request + * @return void + */ + protected function processUserHint(OAuth2AuthenticationRequest $request) + { + $login_hint = $request->getLoginHint(); + $token_hint = $request->getIdTokenHint(); + + // process login hint + $user = null; + + if(!empty ($login_hint)) + { + + if (filter_var($login_hint, FILTER_VALIDATE_EMAIL)) + { + $user = $this->auth_service->getUserByUsername($login_hint); + } + else + { + $user_id = $this->auth_service->unwrapUserId($login_hint); + $user = $this->auth_service->getUserByExternaldId($user_id); + } + } + else if(!empty($token_hint)) + { + $client_id = $request->getClientId(); + $client = $this->client_service->getClientById($client_id); + + if (is_null($client)) + { + throw new InvalidClientException + ( + sprintf + ( + "client_id %s does not exists!", + $client_id + ) + ); + } + + $jwt = BasicJWTFactory::build($token_hint); + + if($jwt instanceof IJWE) + { + // decrypt using server key + + $recipient_key = RSAJWKFactory::build + ( + new RSAJWKPEMPrivateKeySpecification + ( + TestSeeder::$client_private_key_1, + RSAJWKPEMPrivateKeySpecification::WithoutPassword, + $jwt->getJOSEHeader()->getAlgorithm()->getString() + ) + ); + + $heuristic = new ServerEncryptionKeyFinder($this->server_private_key_repository); + $server_enc_private_key = $heuristic->find + ( + $client, + $client->getIdTokenResponseInfo()->getEncryptionKeyAlgorithm() + ); + + $jwt->setRecipientKey($server_enc_private_key); + + $payload = $jwt->getPlainText(); + $jwt = BasicJWTFactory::build($payload); + } + if($jwt instanceof IJWS) + { + // signed by client ? + try + { + $heuristic = new ClientSigningKeyFinder($this->jwk_set_reader_service); + $client_public_sig_key = $heuristic->find + ( + $client, + $client->getIdTokenResponseInfo()->getSigningAlgorithm() + ); + + $jwt->setKey($client_public_sig_key); + } + catch(RecipientKeyNotFoundException $ex) + { + // try to find the server signing key used ... + + $heuristic = new ServerSigningKeyFinder($this->server_private_key_repository); + $server_private_sig_key = $heuristic->find + ( + $client, + $client->getIdTokenResponseInfo()->getSigningAlgorithm(), + $jwt->getJOSEHeader()->getKeyID()->getValue() + ); + $jwt->setKey($server_private_sig_key); + } + + $verified = $jwt->verify($jwt->getJOSEHeader()->getAlgorithm()->getString()); + + if(!$verified) + throw new InvalidLoginHint('invalid id_token_hint'); + } + + $sub = $jwt->getClaimSet()->getSubject(); + $user_id = $this->auth_service->unwrapUserId($sub->getString()); + $user = $this->auth_service->getUserByExternaldId($user_id); + + $jti = $jwt->getClaimSet()->getJWTID(); + if(is_null($jti)) throw new InvalidLoginHint('invalid jti!'); + + $this->auth_service->reloadSession($jti->getValue()); + + } + + if($user) + { + $principal = $this->principal_service->get(); + + if + ( + !is_null($principal) && + !is_null($principal->getUserId()) && + $principal->getUserId() !== $user->getId() + ) + { + if(!$this->canInteractWithEndUser($request)) + throw new InteractionRequiredException; + + $this->auth_service->logout(); + } + + $this->security_context_service->save + ( + $this->security_context_service->get()->setRequestedUserId + ( + $user->getId() + ) + ); + } + } + + /** + * @param OAuth2AuthorizationRequest $request + * @return bool + */ + protected function shouldPromptLogin(OAuth2AuthorizationRequest $request) + { + if + ( + $request instanceof OAuth2AuthenticationRequest && + !$request->isProcessedParam(OAuth2Protocol::OAuth2Protocol_Prompt_Login) && + in_array(OAuth2Protocol::OAuth2Protocol_Prompt_Login, $request->getPrompt()) + ) + { + $request->markParamAsProcessed(OAuth2Protocol::OAuth2Protocol_Prompt_Login); + return true; + } + return false; + } + + /** + * @param OAuth2AuthorizationRequest $request + * @return bool + */ + protected function shouldPromptConsent(OAuth2AuthorizationRequest $request) + { + if + ( + $request instanceof OAuth2AuthenticationRequest && + !$request->isProcessedParam(OAuth2Protocol::OAuth2Protocol_Prompt_Consent) && + in_array(OAuth2Protocol::OAuth2Protocol_Prompt_Consent, $request->getPrompt()) + ) + { + $request->markParamAsProcessed(OAuth2Protocol::OAuth2Protocol_Prompt_Consent); + return true; + } + return false; + } + + /** + * @param OAuth2AuthorizationRequest $request + * @return bool + */ + protected function hadPromptConsent(OAuth2AuthorizationRequest $request) + { + if + ( + $request instanceof OAuth2AuthenticationRequest && + in_array(OAuth2Protocol::OAuth2Protocol_Prompt_Consent, $request->getPrompt()) + ) + { + return true; + } + return false; + } + + /** + * @param OAuth2AuthorizationRequest $request + * @param IClient $client + * @return bool + */ + protected function shouldForceReLogin(OAuth2AuthorizationRequest $request, IClient $client) + { + $now = time(); + $principal = $this->principal_service->get(); + + if($request instanceof OAuth2AuthenticationRequest) + { + + $max_age = $request->getMaxAge(); + $default_max_age = $client->getDefaultMaxAge(); + + if(is_null($max_age) && $default_max_age > 0) + $max_age = $default_max_age; + + if(!is_null($max_age) && $max_age > 0) + { + // must required teh auth_time claim + $this->security_context_service->save + ( + $this->security_context_service->get()->setAuthTimeRequired(true) + ); + + if + ( + !is_null($principal) && + !is_null($principal->getAuthTime()) && + ($now - $principal->getAuthTime()) > $max_age + ) + { + if (!$this->canInteractWithEndUser($request)) + { + throw new InteractionRequiredException; + } + + return true; + } + } + } + return false; + } + + /** + * @param OAuth2AuthorizationRequest $request + * @param IClient $client + * @return bool + * @throws LoginRequiredException + */ + protected function mustAuthenticateUser(OAuth2AuthorizationRequest $request, IClient $client) + { + + if($request instanceof OAuth2AuthenticationRequest) + { + $this->processUserHint($request); + } + + if($this->shouldPromptLogin($request)) + { + $this->auth_service->logout(); + return true; + } + + if($this->shouldForceReLogin($request, $client)) + { + $this->auth_service->logout(); + return true; + } + + if (!$this->auth_service->isUserLogged()) + return true; + + return false; + } +} \ No newline at end of file diff --git a/app/libs/oauth2/grant_types/RefreshBearerTokenGrantType.php b/app/libs/oauth2/grant_types/RefreshBearerTokenGrantType.php index 6993fe60..8aba2907 100644 --- a/app/libs/oauth2/grant_types/RefreshBearerTokenGrantType.php +++ b/app/libs/oauth2/grant_types/RefreshBearerTokenGrantType.php @@ -3,43 +3,50 @@ namespace oauth2\grant_types; use Exception; +use oauth2\exceptions\InvalidApplicationType; +use oauth2\exceptions\InvalidGrantTypeException; +use oauth2\exceptions\InvalidOAuth2Request; +use oauth2\exceptions\UseRefreshTokenException; use oauth2\models\IClient; -use oauth2\requests\OAuth2Request; use oauth2\OAuth2Protocol; +use oauth2\requests\OAuth2RefreshAccessTokenRequest; +use oauth2\requests\OAuth2Request; +use oauth2\requests\OAuth2TokenRequest; +use oauth2\responses\OAuth2AccessTokenResponse; use oauth2\services\IClientService; use oauth2\services\ITokenService; use utils\services\ILogService; -use oauth2\requests\OAuth2RefreshAccessTokenRequest; - -use ReflectionClass; - -use oauth2\exceptions\InvalidApplicationType; -use oauth2\exceptions\UseRefreshTokenException; -use oauth2\exceptions\InvalidOAuth2Request; -use oauth2\exceptions\InvalidGrantTypeException; - -use oauth2\responses\OAuth2AccessTokenResponse; - /** * Class RefreshBearerTokenGrantType * http://tools.ietf.org/html/rfc6749#section-6 * @package oauth2\grant_types */ +final class RefreshBearerTokenGrantType extends AbstractGrantType +{ -class RefreshBearerTokenGrantType extends AbstractGrantType { - - public function __construct(IClientService $client_service, ITokenService $token_service, ILogService $log_service) + /** + * @param IClientService $client_service + * @param ITokenService $token_service + * @param ILogService $log_service + */ + public function __construct + ( + IClientService $client_service, + ITokenService $token_service, + ILogService $log_service + ) { - parent::__construct($client_service, $token_service,$log_service); + parent::__construct($client_service, $token_service, $log_service); } - + /** + * @param OAuth2Request $request + * @return bool + */ public function canHandle(OAuth2Request $request) { - $reflector = new ReflectionClass($request); - $class_name = $reflector->getName(); - return $class_name == 'oauth2\requests\OAuth2TokenRequest' && $request->isValid() && $request->getGrantType() == $this->getType(); + return $request instanceof OAuth2TokenRequest && $request->isValid() && $request->getGrantType() == $this->getType(); } /** Not implemented , there is no first process phase on this grant type @@ -52,12 +59,9 @@ class RefreshBearerTokenGrantType extends AbstractGrantType { throw new InvalidOAuth2Request('not implemented!'); } - - /** * Access Token issuance using a refresh token * The authorization server MUST: - * * o require client authentication for confidential clients or for any * client that was issued client credentials (or with other * authentication requirements), @@ -65,7 +69,6 @@ class RefreshBearerTokenGrantType extends AbstractGrantType { * ensure that the refresh token was issued to the authenticated * client, and * o validate the refresh token. - * * @param OAuth2Request $request * @return mixed|OAuth2AccessTokenResponse|void * @throws \oauth2\exceptions\UseRefreshTokenException @@ -75,71 +78,130 @@ class RefreshBearerTokenGrantType extends AbstractGrantType { */ public function completeFlow(OAuth2Request $request) { - $reflector = new ReflectionClass($request); - $class_name = $reflector->getName(); - if ($class_name == 'oauth2\requests\OAuth2RefreshAccessTokenRequest') { - - parent::completeFlow($request); - - if($this->current_client->getApplicationType()!=IClient::ApplicationType_Web_App) - throw new InvalidApplicationType($this->current_client_id,sprintf('client id %s client type must be WEB_APPLICATION',$this->current_client_id)); - - if(!$this->current_client->use_refresh_token) - throw new UseRefreshTokenException("current client id %s could not use refresh tokens",$this->current_client_id); - - $refresh_token_value = $request->getRefreshToken(); - $scope = $request->getScope(); - $refresh_token = $this->token_service->getRefreshToken($refresh_token_value); - - if(is_null($refresh_token)) - throw new InvalidGrantTypeException(sprintf("refresh token %s does not exists!",$refresh_token_value)); - - if($refresh_token->getClientId() !== $this->current_client->client_id) - throw new InvalidGrantTypeException(sprintf("refresh token %s does not belongs to client %s",$refresh_token_value,$this->current_client->client_id)); - - $access_token = $this->token_service->createAccessTokenFromRefreshToken($refresh_token, $scope); - - $new_refresh_token = null; - /* - * the authorization server could employ refresh token - * rotation in which a new refresh token is issued with every access - * token refresh response. The previous refresh token is invalidated - * but retained by the authorization server. If a refresh token is - * compromised and subsequently used by both the attacker and the - * legitimate client, one of them will present an invalidated refresh - * token, which will inform the authorization server of the breach. - */ - if($this->current_client->rotate_refresh_token){ - $this->token_service->invalidateRefreshToken($refresh_token_value); - $new_refresh_token = $this->token_service->createRefreshToken($access_token); - } - - $response = new OAuth2AccessTokenResponse($access_token->getValue(), $access_token->getLifetime(), !is_null($new_refresh_token) ? $new_refresh_token->getValue() : $scope); - return $response; + if (!($request instanceof OAuth2RefreshAccessTokenRequest)) + { + throw new InvalidOAuth2Request; } - throw new InvalidOAuth2Request; + + parent::completeFlow($request); + + if + ( + $this->current_client->getApplicationType() != IClient::ApplicationType_Web_App && + $this->current_client->getApplicationType() != IClient::ApplicationType_Native + ) + { + throw new InvalidApplicationType + ( + sprintf + ( + 'client id %s client type must be %s or ', + $this->client_auth_context->getId(), + IClient::ApplicationType_Web_App, + IClient::ApplicationType_Native + ) + ); + } + + if (!$this->current_client->use_refresh_token) + { + throw new UseRefreshTokenException + ( + sprintf + ( + "current client id %s could not use refresh tokens", + $this->client_auth_context->getId() + ) + ); + } + + $refresh_token_value = $request->getRefreshToken(); + $scope = $request->getScope(); + $refresh_token = $this->token_service->getRefreshToken($refresh_token_value); + + if (is_null($refresh_token)) + { + throw new InvalidGrantTypeException + ( + sprintf + ( + "refresh token %s does not exists!", + $refresh_token_value + ) + ); + } + + if ($refresh_token->getClientId() !== $this->current_client->client_id) + { + throw new InvalidGrantTypeException + ( + sprintf + ( + "refresh token %s does not belongs to client %s", + $refresh_token_value, $this->current_client->client_id + ) + ); + } + + $access_token = $this->token_service->createAccessTokenFromRefreshToken($refresh_token, $scope); + + $new_refresh_token = null; + /* + * the authorization server could employ refresh token + * rotation in which a new refresh token is issued with every access + * token refresh response. The previous refresh token is invalidated + * but retained by the authorization server. If a refresh token is + * compromised and subsequently used by both the attacker and the + * legitimate client, one of them will present an invalidated refresh + * token, which will inform the authorization server of the breach. + */ + if ($this->current_client->rotate_refresh_token) + { + $this->token_service->invalidateRefreshToken($refresh_token_value); + $new_refresh_token = $this->token_service->createRefreshToken($access_token); + } + + $response = new OAuth2AccessTokenResponse + ( + $access_token->getValue(), + $access_token->getLifetime(), + !is_null($new_refresh_token) ? $new_refresh_token->getValue() : $scope + ); + + return $response; } + /** + * @return string + */ public function getType() { return OAuth2Protocol::OAuth2Protocol_GrantType_RefreshToken; } + /** + * @throws InvalidOAuth2Request + */ public function getResponseType() { throw new InvalidOAuth2Request('not implemented!'); } + /** + * @param OAuth2Request $request + * @return null|OAuth2RefreshAccessTokenRequest + */ public function buildTokenRequest(OAuth2Request $request) { - $reflector = new ReflectionClass($request); - $class_name = $reflector->getName(); - if ($class_name == 'oauth2\requests\OAuth2TokenRequest') { - if($request->getGrantType() !== $this->getType()) + if ($request instanceof OAuth2TokenRequest) { + if ($request->getGrantType() !== $this->getType()) { return null; + } + return new OAuth2RefreshAccessTokenRequest($request->getMessage()); } + return null; } } \ No newline at end of file diff --git a/app/libs/oauth2/grant_types/RevokeBearerTokenGrantType.php b/app/libs/oauth2/grant_types/RevokeBearerTokenGrantType.php index 4028f0d9..5e24c8dd 100644 --- a/app/libs/oauth2/grant_types/RevokeBearerTokenGrantType.php +++ b/app/libs/oauth2/grant_types/RevokeBearerTokenGrantType.php @@ -2,23 +2,20 @@ namespace oauth2\grant_types; +use Exception; +use oauth2\exceptions\BearerTokenDisclosureAttemptException; +use oauth2\exceptions\ExpiredAccessTokenException; +use oauth2\exceptions\InvalidGrantTypeException; use oauth2\exceptions\InvalidOAuth2Request; use oauth2\exceptions\UnAuthorizedClientException; -use oauth2\exceptions\BearerTokenDisclosureAttemptException; -use oauth2\exceptions\InvalidGrantTypeException; -use oauth2\exceptions\ExpiredAccessTokenException; - use oauth2\OAuth2Protocol; use oauth2\requests\OAuth2Request; +use oauth2\requests\OAuth2TokenRevocationRequest; use oauth2\responses\OAuth2TokenRevocationResponse; use oauth2\services\IClientService; use oauth2\services\ITokenService; - -use ReflectionClass; use utils\services\ILogService; -use Exception; - /** * Class RevokeTokenGrantType * http://tools.ietf.org/html/rfc7009 @@ -62,10 +59,7 @@ class RevokeBearerTokenGrantType extends AbstractGrantType */ public function canHandle(OAuth2Request $request) { - $reflector = new ReflectionClass($request); - $class_name = $reflector->getName(); - - return $class_name == 'oauth2\requests\OAuth2TokenRevocationRequest' && $request->isValid(); + return $request instanceof Auth2TokenRevocationRequest && $request->isValid(); } /** defines entry point for first request processing @@ -78,89 +72,168 @@ class RevokeBearerTokenGrantType extends AbstractGrantType throw new InvalidOAuth2Request('not implemented!'); } + /** + * @param OAuth2Request $request + * @return OAuth2TokenRevocationResponse + * @throws BearerTokenDisclosureAttemptException + * @throws Exception + * @throws ExpiredAccessTokenException + * @throws InvalidOAuth2Request + * @throws UnAuthorizedClientException + * @throws \oauth2\exceptions\InvalidClientCredentials + * @throws \oauth2\exceptions\InvalidClientException + * @throws \oauth2\exceptions\LockedClientException + * @throws \oauth2\exceptions\MissingClientIdParam + */ public function completeFlow(OAuth2Request $request) { - $reflector = new ReflectionClass($request); - $class_name = $reflector->getName(); - if ($class_name == 'oauth2\requests\OAuth2TokenRevocationRequest') { + if (!($request instanceof OAuth2TokenRevocationRequest)) + { + throw new InvalidOAuth2Request; + } - parent::completeFlow($request); - $token_value = $request->getToken(); - $token_hint = $request->getTokenHint(); + parent::completeFlow($request); - try{ - if (!is_null($token_hint) && !empty($token_hint)) { - //we have been provided with a token hint... - switch ($token_hint) { - case OAuth2Protocol::OAuth2Protocol_AccessToken: - { - //check ownership - $access_token = $this->token_service->getAccessToken($token_value); + $token_value = $request->getToken(); + $token_hint = $request->getTokenHint(); - if(is_null($access_token)) - throw new ExpiredAccessTokenException(sprintf('Access token %s is expired!', $token_value)); - - if ($access_token->getClientId() !== $this->current_client_id) - throw new BearerTokenDisclosureAttemptException($this->current_client_id,sprintf('access token %s does not belongs to client id %s',$token_value, $this->current_client_id)); - - $this->token_service->revokeAccessToken($token_value, false); - } - break; - case OAuth2Protocol::OAuth2Protocol_RefreshToken: - { - //check ownership - $refresh_token = $this->token_service->getRefreshToken($token_value); - - if ($refresh_token->getClientId() !== $this->current_client_id) - throw new BearerTokenDisclosureAttemptException($this->current_client_id,sprintf('refresh token %s does not belongs to client id %s',$token_value, $this->current_client_id)); - - $this->token_service->revokeRefreshToken($token_value, false); - } - break; - } - } else { - /* - * no token hint given :( - * if the server is unable to locate the token using - * the given hint, it MUST extend its search across all of its - * supported token types. - */ - - //check and handle access token first .. - try{ + try + { + if (!is_null($token_hint) && !empty($token_hint)) + { + //we have been provided with a token hint... + switch ($token_hint) + { + case OAuth2Protocol::OAuth2Protocol_AccessToken: + { //check ownership $access_token = $this->token_service->getAccessToken($token_value); - if(is_null($access_token)) + if (is_null($access_token)) + { throw new ExpiredAccessTokenException(sprintf('Access token %s is expired!', $token_value)); + } - if ($access_token->getClientId() !== $this->current_client_id) - throw new BearerTokenDisclosureAttemptException($this->current_client_id,sprintf('access token %s does not belongs to client id %s',$token_value, $this->current_client_id)); + if ($access_token->getClientId() !== $this->client_auth_context->getId()) + { + throw new BearerTokenDisclosureAttemptException + ( + sprintf + ( + 'access token %s does not belongs to client id %s', + $token_value, + $this->client_auth_context->getId() + ) + ); + } $this->token_service->revokeAccessToken($token_value, false); } - catch(UnAuthorizedClientException $ex1){ - $this->log_service->error($ex1); - throw $ex1; - } - catch(Exception $ex){ - $this->log_service->warning($ex); - //access token was not found, check refresh token + break; + case OAuth2Protocol::OAuth2Protocol_RefreshToken: + { //check ownership $refresh_token = $this->token_service->getRefreshToken($token_value); - if ($refresh_token->getClientId() !== $this->current_client_id) - throw new BearerTokenDisclosureAttemptException($this->current_client_id,sprintf('refresh token %s does not belongs to client id %s',$token_value, $this->current_client_id)); + + if ($refresh_token->getClientId() !== $this->client_auth_context->getId()) + { + throw new BearerTokenDisclosureAttemptException + ( + sprintf + ( + 'refresh token %s does not belongs to client id %s', + $token_value, + $this->client_auth_context->getId() + ) + ); + } + $this->token_service->revokeRefreshToken($token_value, false); } + break; } - return new OAuth2TokenRevocationResponse; } - catch(InvalidGrantTypeException $ex){ - throw new BearerTokenDisclosureAttemptException($this->current_client_id,$ex->getMessage()); + else + { + /* + * no token hint given :( + * if the server is unable to locate the token using + * the given hint, it MUST extend its search across all of its + * supported token types. + */ + + //check and handle access token first .. + try { + //check ownership + $access_token = $this->token_service->getAccessToken($token_value); + + if (is_null($access_token)) + { + throw new ExpiredAccessTokenException + ( + sprintf + ( + 'Access token %s is expired!', + $token_value + ) + ); + } + + if ($access_token->getClientId() !== $this->client_auth_context->getId()) + { + throw new BearerTokenDisclosureAttemptException + ( + sprintf + ( + 'access token %s does not belongs to client id %s', + $token_value, + $this->client_auth_context->getId() + ) + ); + } + + $this->token_service->revokeAccessToken($token_value, false); + + } + catch (UnAuthorizedClientException $ex1) + { + $this->log_service->error($ex1); + throw $ex1; + } + catch (Exception $ex) + { + $this->log_service->warning($ex); + //access token was not found, check refresh token + //check ownership + $refresh_token = $this->token_service->getRefreshToken($token_value); + + if ($refresh_token->getClientId() !== $this->client_auth_context->getId()) + { + throw new BearerTokenDisclosureAttemptException + ( + sprintf + ( + 'refresh token %s does not belongs to client id %s', + $token_value, + $this->client_auth_context->getId() + ) + ); + } + $this->token_service->revokeRefreshToken($token_value, false); + } } + + return new OAuth2TokenRevocationResponse; } - throw new InvalidOAuth2Request; + catch (InvalidGrantTypeException $ex) + { + throw new BearerTokenDisclosureAttemptException + ( + $ex->getMessage() + ); + } + } /** diff --git a/app/libs/oauth2/grant_types/ValidateBearerTokenGrantType.php b/app/libs/oauth2/grant_types/ValidateBearerTokenGrantType.php index f0c57874..6f0cb8e9 100644 --- a/app/libs/oauth2/grant_types/ValidateBearerTokenGrantType.php +++ b/app/libs/oauth2/grant_types/ValidateBearerTokenGrantType.php @@ -2,24 +2,22 @@ namespace oauth2\grant_types; -use oauth2\exceptions\ExpiredAccessTokenException; -use oauth2\exceptions\InvalidApplicationType; -use oauth2\exceptions\InvalidOAuth2Request; -use oauth2\exceptions\InvalidAccessTokenException; use oauth2\exceptions\BearerTokenDisclosureAttemptException; -use oauth2\exceptions\LockedClientException; +use oauth2\exceptions\ExpiredAccessTokenException; +use oauth2\exceptions\InvalidAccessTokenException; +use oauth2\exceptions\InvalidApplicationType; use oauth2\exceptions\InvalidGrantTypeException; - +use oauth2\exceptions\InvalidOAuth2Request; +use oauth2\exceptions\LockedClientException; +use oauth2\models\IClient; +use oauth2\requests\OAuth2AccessTokenValidationRequest; use oauth2\requests\OAuth2Request; use oauth2\responses\OAuth2AccessTokenValidationResponse; use oauth2\services\IClientService; use oauth2\services\ITokenService; use utils\IPHelper; +use utils\services\IAuthService; use utils\services\ILogService; -use oauth2\models\IClient; - -use ReflectionClass; - /** * Class ValidateBearerTokenGrantType @@ -55,18 +53,40 @@ class ValidateBearerTokenGrantType extends AbstractGrantType const OAuth2Protocol_GrantType_Extension_ValidateBearerToken = 'urn:tools.ietf.org:oauth2:grant_type:validate_bearer'; - public function __construct(IClientService $client_service, ITokenService $token_service, ILogService $log_service) + /** + * @var IAuthService + */ + private $auth_service; + /** + * @param IClientService $client_service + * @param ITokenService $token_service + * @param IAuthService $auth_service + * @param ILogService $log_service + */ + public function __construct + ( + IClientService $client_service, + ITokenService $token_service, + IAuthService $auth_service, + ILogService $log_service + ) { - parent::__construct($client_service, $token_service,$log_service); + parent::__construct($client_service, $token_service, $log_service); + $this->auth_service = $auth_service; } + /** + * @param OAuth2Request $request + * @return bool + */ public function canHandle(OAuth2Request $request) { - $reflector = new ReflectionClass($request); - $class_name = $reflector->getName(); - return $class_name == 'oauth2\requests\OAuth2AccessTokenValidationRequest' && $request->isValid(); + return $request instanceof OAuth2AccessTokenValidationRequest && $request->isValid(); } + /** + * @return string + */ public function getType() { return self::OAuth2Protocol_GrantType_Extension_ValidateBearerToken; @@ -92,79 +112,150 @@ class ValidateBearerTokenGrantType extends AbstractGrantType */ public function completeFlow(OAuth2Request $request) { - $reflector = new ReflectionClass($request); - $class_name = $reflector->getName(); - if ($class_name == 'oauth2\requests\OAuth2AccessTokenValidationRequest') { - parent::completeFlow($request); - - $token_value = $request->getToken(); - - try{ - - $access_token = $this->token_service->getAccessToken($token_value); - if(is_null($access_token)) - throw new ExpiredAccessTokenException(sprintf('Access token %s is expired!', $token_value)); - if(!$this->current_client->isResourceServerClient()){ - // if current client is not a resource server, then we could only access to our own tokens - if($access_token->getClientId()!== $this->current_client_id) - throw new BearerTokenDisclosureAttemptException($this->current_client_id,sprintf('access token %s does not belongs to client id %s',$token_value, $this->current_client_id)); - } - else{ - // current client is a resource server, validate client type (must be confidential) - if($this->current_client->getClientType()!== IClient::ClientType_Confidential) - throw new InvalidApplicationType($this->current_client_id,'resource server client is not of confidential type!'); - //validate resource server IP address - $current_ip = IPHelper::getUserIp(); - $resource_server = $this->current_client->getResourceServer(); - //check if resource server is active - if(!$resource_server->active) - throw new LockedClientException($this->current_client_id,'resource server is disabled!'); - //check resource server ip address - if($current_ip !== $resource_server->ip) - throw new BearerTokenDisclosureAttemptException($this->current_client_id,sprintf('resource server ip (%s) differs from current request ip %s',$resource_server->ip,$current_ip)); - // check if current ip belongs to a registered resource server audience - if(!$this->token_service->checkAccessTokenAudience($access_token,$current_ip)) - throw new BearerTokenDisclosureAttemptException($this->current_client_id,sprintf('access token current audience does not match with current request ip %s', $current_ip)); - } - - $allowed_origins = array(); - $allowed_urls = array(); - $issued_client = $this->client_service->getClientById($access_token->getClientId()); - - if(is_null($issued_client)) - throw new BearerTokenDisclosureAttemptException($this->current_client_id,sprintf('access token %s does not belongs to client id %s',$token_value, $access_token->getClientId())); - - foreach($issued_client->getClientAllowedOrigins() as $origin){ - array_push($allowed_origins, $origin->allowed_origin); - } - - foreach($issued_client->getClientRegisteredUris() as $url){ - array_push($allowed_urls, $url->uri); - } - - return new OAuth2AccessTokenValidationResponse($token_value, $access_token->getScope(), $access_token->getAudience(), $access_token->getClientId(), $access_token->getRemainingLifetime(), $access_token->getUserId(), $issued_client->getApplicationType(), $allowed_urls, $allowed_origins); - } - catch(InvalidAccessTokenException $ex1){ - $this->log_service->error($ex1); - throw new BearerTokenDisclosureAttemptException($this->current_client_id,$ex1->getMessage()); - } - catch(InvalidGrantTypeException $ex2){ - $this->log_service->error($ex2); - throw new BearerTokenDisclosureAttemptException($this->current_client_id,$ex2->getMessage()); - } + if (!($request instanceof OAuth2AccessTokenValidationRequest)) { + throw new InvalidOAuth2Request; + } + + parent::completeFlow($request); + + $token_value = $request->getToken(); + + try { + + $access_token = $this->token_service->getAccessToken($token_value); + + if (is_null($access_token)) + { + throw new ExpiredAccessTokenException + ( + sprintf + ( + 'Access token %s is expired!', + $token_value + ) + ); + } + if (!$this->current_client->isResourceServerClient()) + { + // if current client is not a resource server, then we could only access to our own tokens + if ($access_token->getClientId() !== $this->client_auth_context->getId()) + { + throw new BearerTokenDisclosureAttemptException + ( + sprintf + ( + 'access token %s does not belongs to client id %s', + $token_value, + $this->client_auth_context->getId() + ) + ); + } + } + else + { + // current client is a resource server, validate client type (must be confidential) + if ($this->current_client->getClientType() !== IClient::ClientType_Confidential) + { + throw new InvalidApplicationType + ( + 'resource server client is not of confidential type!' + ); + } + //validate resource server IP address + $current_ip = IPHelper::getUserIp(); + $resource_server = $this->current_client->getResourceServer(); + //check if resource server is active + if (!$resource_server->active) + { + throw new LockedClientException + ( + 'resource server is disabled!' + ); + } + //check resource server ip address + if ($current_ip !== $resource_server->ip) + { + throw new BearerTokenDisclosureAttemptException + ( + sprintf + ( + 'resource server ip (%s) differs from current request ip %s', + $resource_server->ip, + $current_ip + ) + ); + } + // check if current ip belongs to a registered resource server audience + if (!$this->token_service->checkAccessTokenAudience($access_token, $current_ip)) + { + throw new BearerTokenDisclosureAttemptException + ( + sprintf + ( + 'access token current audience does not match with current request ip %s', + $current_ip + ) + ); + } + } + + $issued_client = $this->client_service->getClientById($access_token->getClientId()); + + if (is_null($issued_client)) + { + throw new BearerTokenDisclosureAttemptException + ( + sprintf + ( + 'access token %s does not belongs to client id %s', + $token_value, + $access_token->getClientId() + ) + ); + } + + $user_id = $access_token->getUserId(); + $user = is_null($user_id) ? null: $this->auth_service->getUserById($user_id); + + return new OAuth2AccessTokenValidationResponse + ( + $token_value, + $access_token->getScope(), + $access_token->getAudience(), + $issued_client, + $access_token->getRemainingLifetime(), + $user, + $issued_client->getRedirectUris(), + $issued_client->getClientAllowedOrigins() + ); + } + catch (InvalidAccessTokenException $ex1) + { + $this->log_service->error($ex1); + throw new BearerTokenDisclosureAttemptException($ex1->getMessage()); + } + catch (InvalidGrantTypeException $ex2) + { + $this->log_service->error($ex2); + throw new BearerTokenDisclosureAttemptException($ex2->getMessage()); } - throw new InvalidOAuth2Request; } + /** + * @throws InvalidOAuth2Request + */ public function getResponseType() { throw new InvalidOAuth2Request('Not Implemented!'); } + /** + * @param OAuth2Request $request + * @throws InvalidOAuth2Request + */ public function buildTokenRequest(OAuth2Request $request) { throw new InvalidOAuth2Request('Not Implemented!'); } - } \ No newline at end of file diff --git a/app/libs/oauth2/heuristics/ClientEncryptionKeyFinder.php b/app/libs/oauth2/heuristics/ClientEncryptionKeyFinder.php new file mode 100644 index 00000000..cc9f547a --- /dev/null +++ b/app/libs/oauth2/heuristics/ClientEncryptionKeyFinder.php @@ -0,0 +1,127 @@ +jwk_set_reader_service = $jwk_set_reader_service; + } + + /** + * @param IClient $client + * @param ICryptoAlgorithm $alg + * @param string|null $kid_hint + * @return IJWK + * @throws RecipientKeyNotFoundException + */ + public function find(IClient $client, ICryptoAlgorithm $alg, $kid_hint = null) + { + if($alg instanceof DirectEncryption) + { + // use secret + if($client->getClientType() !== IClient::ClientType_Confidential) + throw new InvalidClientType; + + $jwk = OctetSequenceJWKFactory::build + ( + new OctetSequenceJWKSpecification + ( + $client->getClientSecret(), + $alg->getName() + ) + ); + + $jwk->setId('shared_secret'); + + return $jwk; + } + + + $recipient_key = null; + + if(!is_null($kid_hint)) + { + $recipient_key = $client->getPublicKeyByIdentifier($kid_hint); + if(!$recipient_key->isActive()) $recipient_key = null; + if($recipient_key->getAlg()->getName() !== $alg->getName()) $recipient_key = null; + } + + if(is_null($recipient_key)) + { + $recipient_key = $client->getCurrentPublicKeyByUse + ( + JSONWebKeyPublicKeyUseValues::Encryption, + $alg->getName() + ); + } + + if(!is_null($recipient_key)) + { + $recipient_key->markAsUsed(); + $recipient_key->save(); + $recipient_key = $recipient_key->toJWK(); + } + else + { + // check on jwk uri + $jwk_set = $this->jwk_set_reader_service->read($client); + + if(is_null($jwk_set)) + throw new RecipientKeyNotFoundException; + + foreach($jwk_set->getKeys() as $jwk) + { + if + ( + $jwk->getKeyUse() === JSONWebKeyPublicKeyUseValues::Encryption && + $jwk->getAlgorithm()->getString() === $alg->getName() + ) + { + + $recipient_key = $jwk; + break; + } + } + } + + if(is_null($recipient_key)) + throw new RecipientKeyNotFoundException; + + return $recipient_key; + } +} \ No newline at end of file diff --git a/app/libs/oauth2/heuristics/ClientSigningKeyFinder.php b/app/libs/oauth2/heuristics/ClientSigningKeyFinder.php new file mode 100644 index 00000000..8f5cfd98 --- /dev/null +++ b/app/libs/oauth2/heuristics/ClientSigningKeyFinder.php @@ -0,0 +1,129 @@ +jwk_set_reader_service = $jwk_set_reader_service; + } + + /** + * @param IClient $client + * @param ICryptoAlgorithm $alg + * @param string|null $kid_hint + * @return IJWK + * @throws RecipientKeyNotFoundException + */ + public function find(IClient $client, ICryptoAlgorithm $alg, $kid_hint = null) + { + if($alg instanceof MAC_Algorithm ) + { + // use secret + if($client->getClientType() !== IClient::ClientType_Confidential) + throw new InvalidClientType; + + $jwk = OctetSequenceJWKFactory::build + ( + new OctetSequenceJWKSpecification + ( + $client->getClientSecret(), + $alg->getName() + ) + ); + + $jwk->setId('shared_secret'); + + return $jwk; + } + + $recipient_key = null; + + if(!is_null($kid_hint)) + { + $recipient_key = $client->getPublicKeyByIdentifier($kid_hint); + if(!$recipient_key->isActive()) $recipient_key = null; + if($recipient_key->getAlg()->getName() !== $alg->getName()) $recipient_key = null; + } + + if(is_null($recipient_key)) + { + $recipient_key = $client->getCurrentPublicKeyByUse + ( + JSONWebKeyPublicKeyUseValues::Signature, + $alg->getName() + ); + } + + if(!is_null($recipient_key)) + { + $recipient_key->markAsUsed(); + $recipient_key->save(); + $recipient_key = $recipient_key->toJWK(); + } + else + { + // check on jwk uri + $jwk_set = $this->jwk_set_reader_service->read($client); + + if(is_null($jwk_set)) + throw new RecipientKeyNotFoundException; + + foreach($jwk_set->getKeys() as $jwk) + { + if + ( + $jwk->getKeyUse() === JSONWebKeyPublicKeyUseValues::Signature && + $jwk->getAlgorithm()->getString() === $alg->getName() + ) + { + + $recipient_key = $jwk; + break; + } + } + } + + if(is_null($recipient_key)) + throw new RecipientKeyNotFoundException; + + return $recipient_key; + } +} \ No newline at end of file diff --git a/app/libs/oauth2/heuristics/IKeyFinder.php b/app/libs/oauth2/heuristics/IKeyFinder.php new file mode 100644 index 00000000..9560b06b --- /dev/null +++ b/app/libs/oauth2/heuristics/IKeyFinder.php @@ -0,0 +1,34 @@ +server_private_key_repository = $server_private_key_repository; + } + + /** + * @param IClient $client + * @param ICryptoAlgorithm $alg + * @param string|null $kid_hint + * @return IJWK + * @throws InvalidClientType + * @throws ServerKeyNotFoundException + * @throws InvalidJWKAlgorithm + * @throws JWKInvalidSpecException + */ + public function find(IClient $client, ICryptoAlgorithm $alg, $kid_hint = null) + { + if($alg instanceof DirectEncryption) + { + // use secret + if($client->getClientType() !== IClient::ClientType_Confidential) + throw new InvalidClientType; + + $jwk = OctetSequenceJWKFactory::build + ( + new OctetSequenceJWKSpecification + ( + $client->getClientSecret(), + $alg->getName() + ) + ); + + $jwk->setId('shared_secret'); + + return $jwk; + } + + $key = null; + + if(!is_null($kid_hint)) + { + $key = $this->server_private_key_repository->get($kid_hint); + if(!$key->isActive()) $key = null; + if($key->getAlg()->getName() !== $alg->getName()) $key = null; + } + + if(is_null($key)) + { + $key = $this->server_private_key_repository->getActiveByCriteria + ( + JSONWebKeyTypes::RSA, + JSONWebKeyPublicKeyUseValues::Encryption, + $alg->getName() + ); + } + + if(is_null($key)) + throw new ServerKeyNotFoundException; + + $jwk = $key->toJWK(); + + $key->markAsUsed(); + $key->save(); + + return $jwk; + } +} \ No newline at end of file diff --git a/app/libs/oauth2/heuristics/ServerSigningKeyFinder.php b/app/libs/oauth2/heuristics/ServerSigningKeyFinder.php new file mode 100644 index 00000000..64688a41 --- /dev/null +++ b/app/libs/oauth2/heuristics/ServerSigningKeyFinder.php @@ -0,0 +1,122 @@ +server_private_key_repository = $server_private_key_repository; + } + + /** + * @param IClient $client + * @param ICryptoAlgorithm $alg + * @param string|null $kid_hint + * @return IJWK + * @throws InvalidClientType + * @throws ServerKeyNotFoundException + * @throws InvalidJWKAlgorithm + * @throws JWKInvalidSpecException + */ + public function find(IClient $client, ICryptoAlgorithm $alg, $kid_hint = null) + { + $jwk = null; + + if ($alg instanceof MAC_Algorithm) { + // use secret + if ($client->getClientType() !== IClient::ClientType_Confidential) { + throw new InvalidClientType; + } + + $jwk = OctetSequenceJWKFactory::build + ( + new OctetSequenceJWKSpecification + ( + $client->getClientSecret(), + $alg->getName() + ) + ); + + $jwk->setId('shared_secret'); + + return $jwk; + } + + $key = null; + + if (!is_null($kid_hint)) + { + $key = $this->server_private_key_repository->get($kid_hint); + if (!$key->isActive()) + { + $key = null; + } + if ($key->getAlg()->getName() !== $alg->getName()) + { + $key = null; + } + } + + if(is_null($key)) + { + $key = $this->server_private_key_repository->getActiveByCriteria + ( + JSONWebKeyTypes::RSA, + JSONWebKeyPublicKeyUseValues::Signature, + $alg->getName() + ); + } + + if (is_null($key)) + { + throw new ServerKeyNotFoundException; + } + + $jwk = $key->toJWK(); + + $key->markAsUsed(); + $key->save(); + + return $jwk; + } +} \ No newline at end of file diff --git a/app/libs/oauth2/models/AuthorizationCode.php b/app/libs/oauth2/models/AuthorizationCode.php index e6c86967..9666e336 100644 --- a/app/libs/oauth2/models/AuthorizationCode.php +++ b/app/libs/oauth2/models/AuthorizationCode.php @@ -10,14 +10,39 @@ use oauth2\OAuth2Protocol; * http://tools.ietf.org/html/rfc6749#section-1.3.1 * @package oauth2\models */ -class AuthorizationCode extends Token { +class AuthorizationCode extends Token +{ private $redirect_uri; private $access_type; private $approval_prompt; private $has_previous_user_consent; + /** + * @var string + */ + private $state; + /** + * @var string + */ + private $nonce; - public function __construct(){ + /** + * @var string + */ + private $response_type; + + /** + * @var bool + */ + private $requested_auth_time; + + /** + * @var int + */ + private $auth_time; + + public function __construct() + { parent::__construct(64); } @@ -31,9 +56,29 @@ class AuthorizationCode extends Token { * @param string $approval_prompt * @param bool $has_previous_user_consent * @param int $lifetime + * @param string|null $state + * @param string|null $nonce + * @param string|null $response_type + * @param $requested_auth_time + * @param $auth_time * @return AuthorizationCode */ - public static function create($user_id, $client_id, $scope, $audience='' ,$redirect_uri = null,$access_type = OAuth2Protocol::OAuth2Protocol_AccessType_Online,$approval_prompt =OAuth2Protocol::OAuth2Protocol_Approval_Prompt_Auto,$has_previous_user_consent=false, $lifetime = 600){ + public static function create( + $user_id, + $client_id, + $scope, + $audience = '', + $redirect_uri = null, + $access_type = OAuth2Protocol::OAuth2Protocol_AccessType_Online, + $approval_prompt = OAuth2Protocol::OAuth2Protocol_Approval_Prompt_Auto, + $has_previous_user_consent = false, + $lifetime = 600, + $state = null, + $nonce = null, + $response_type = null, + $requested_auth_time = false, + $auth_time = -1 + ) { $instance = new self(); $instance->scope = $scope; $instance->user_id = $user_id; @@ -46,6 +91,12 @@ class AuthorizationCode extends Token { $instance->access_type = $access_type; $instance->approval_prompt = $approval_prompt; $instance->has_previous_user_consent = $has_previous_user_consent; + $instance->state = $state; + $instance->nonce = $nonce; + $instance->response_type = $response_type; + $instance->requested_auth_time = $requested_auth_time; + $instance->auth_time = $auth_time; + return $instance; } @@ -62,10 +113,35 @@ class AuthorizationCode extends Token { * @param string $access_type * @param string $approval_prompt * @param bool $has_previous_user_consent + * @param string|null $state + * @param string|null $nonce + * @param string|null $response_type + * @param $requested_auth_time + * @param $auth_time * @param bool $is_hashed * @return AuthorizationCode */ - public static function load($value, $user_id, $client_id, $scope,$audience='', $redirect_uri = null, $issued = null, $lifetime = 600, $from_ip = '127.0.0.1',$access_type = OAuth2Protocol::OAuth2Protocol_AccessType_Online,$approval_prompt = OAuth2Protocol::OAuth2Protocol_Approval_Prompt_Auto,$has_previous_user_consent=false,$is_hashed = false){ + public static function load + ( + $value, + $user_id, + $client_id, + $scope,$audience = '', + $redirect_uri = null, + $issued = null, + $lifetime = 600, + $from_ip = '127.0.0.1', + $access_type = OAuth2Protocol::OAuth2Protocol_AccessType_Online, + $approval_prompt = OAuth2Protocol::OAuth2Protocol_Approval_Prompt_Auto, + $has_previous_user_consent = false, + $state, + $nonce, + $response_type, + $requested_auth_time = false, + $auth_time = -1, + $is_hashed = false + ) + { $instance = new self(); $instance->value = $value; $instance->user_id = $user_id; @@ -80,27 +156,97 @@ class AuthorizationCode extends Token { $instance->access_type = $access_type; $instance->approval_prompt = $approval_prompt; $instance->has_previous_user_consent = $has_previous_user_consent; + $instance->state = $state; + $instance->nonce = $nonce; + $instance->response_type = $response_type; + $instance->requested_auth_time = $requested_auth_time; + $instance->auth_time = $auth_time; + return $instance; } - - public function getRedirectUri(){ + /** + * @return string + */ + public function getRedirectUri() + { return $this->redirect_uri; } - - public function getAccessType(){ + /** + * @return string + */ + public function getAccessType() + { return $this->access_type; } - public function getApprovalPrompt(){ + /** + * @return string + */ + public function getApprovalPrompt() + { return $this->approval_prompt; } - public function getHasPreviousUserConsent(){ + /** + * @return string + */ + public function getHasPreviousUserConsent() + { return $this->has_previous_user_consent; } + /** + * @return string + */ + public function getState() + { + return $this->state; + } + + /** + * @return string + */ + public function getNonce() + { + return $this->nonce; + } + + /** + * @return string + */ + public function getResponseType() + { + return $this->response_type; + } + + /** + * @return bool + */ + public function isAuthTimeRequested() + { + $res = $this->requested_auth_time; + if (!is_string($res)) return (bool) $res; + switch (strtolower($res)) { + case '1': + case 'true': + case 'on': + case 'yes': + case 'y': + return true; + } + return false; + } + + /** + * @return int + */ + public function getAuthTime() + { + return $this->auth_time; + } + public function toJSON() { return '{}'; diff --git a/app/libs/oauth2/models/ClientAssertionAuthenticationContext.php b/app/libs/oauth2/models/ClientAssertionAuthenticationContext.php new file mode 100644 index 00000000..76f38c46 --- /dev/null +++ b/app/libs/oauth2/models/ClientAssertionAuthenticationContext.php @@ -0,0 +1,100 @@ +type = $type; + $this->jwt_assertion = $jwt_assertion; + + $this->jws = BasicJWTFactory::build($jwt_assertion); + + if(!($this->jws instanceof IJWS)) + throw new InvalidClientAssertionException; + + $alg = $this->jws->getJOSEHeader()->getAlgorithm()->getValue(); + + if( !(DigitalSignatures_MACs_Registry::getInstance()->isSupported($alg) && + in_array($alg, OAuth2Protocol::$supported_signing_algorithms))) + throw new InvalidClientAssertionAlgorithmException($alg); + + $alg = DigitalSignatures_MACs_Registry::getInstance()->get($alg); + + $auth_type = $alg instanceof MAC_Algorithm ? + OAuth2Protocol::TokenEndpoint_AuthMethod_ClientSecretJwt : + OAuth2Protocol::TokenEndpoint_AuthMethod_PrivateKeyJwt; + + parent::__construct($this->jws->getClaimSet()->getIssuer()->getString(), $auth_type); + } + + /** + * @return IJWS + */ + public function getAssertion() + { + return $this->jws; + } +} \ No newline at end of file diff --git a/app/libs/oauth2/models/ClientAuthenticationContext.php b/app/libs/oauth2/models/ClientAuthenticationContext.php new file mode 100644 index 00000000..25215db2 --- /dev/null +++ b/app/libs/oauth2/models/ClientAuthenticationContext.php @@ -0,0 +1,89 @@ +client_id = $client_id; + $this->auth_type = $auth_type; + } + + /** + * @return string + */ + public function getAuthType() + { + return $this->auth_type; + } + + /** + * @return string + */ + public function getId() + { + return $this->client_id; + } + + /** + * @param IClient $client + * @return $this + */ + public function setClient(IClient $client) + { + $this->client = $client; + return $this; + } + + /** + * @return IClient + */ + public function getClient() + { + return $this->client; + } + +} \ No newline at end of file diff --git a/app/libs/oauth2/models/ClientCredentialsAuthenticationContext.php b/app/libs/oauth2/models/ClientCredentialsAuthenticationContext.php new file mode 100644 index 00000000..14c56d5b --- /dev/null +++ b/app/libs/oauth2/models/ClientCredentialsAuthenticationContext.php @@ -0,0 +1,58 @@ +client_secret = $client_secret; + } + + /** + * @return string + */ + public function getSecret() + { + return $this->client_secret; + } +} \ No newline at end of file diff --git a/app/libs/oauth2/models/IApiScope.php b/app/libs/oauth2/models/IApiScope.php index 01058094..a7997256 100644 --- a/app/libs/oauth2/models/IApiScope.php +++ b/app/libs/oauth2/models/IApiScope.php @@ -7,12 +7,25 @@ namespace oauth2\models; * http://tools.ietf.org/html/rfc6749#section-3.3 * @package oauth2\models */ -interface IApiScope { - public function getShortDescription(); - public function getName(); - public function getDescription(); - public function isActive(); +interface IApiScope extends IScope { + + /** + * @return string + */ public function getApiName(); + + /** + * @return string + */ public function getApiDescription(); + + /** + * @return string + */ public function getApiLogo(); -} \ No newline at end of file + + /** + * @return IApi + */ + public function getApi(); +} \ No newline at end of file diff --git a/app/libs/oauth2/models/IApiScopeGroup.php b/app/libs/oauth2/models/IApiScopeGroup.php new file mode 100644 index 00000000..3f7037ab --- /dev/null +++ b/app/libs/oauth2/models/IApiScopeGroup.php @@ -0,0 +1,55 @@ +sig_alg = $sig_alg; + $this->alg = $alg; + $this->enc = $enc; + } + + /** + * @return IntegrityCheckingAlgorithm + */ + public function getSigningAlgorithm() + { + return $this->sig_alg; + } + + /** + * @return EncryptionAlgorithm + */ + public function getEncryptionKeyAlgorithm() + { + return $this->alg; + } + + /** + * @return ContentEncryptionAlgorithm + */ + public function getEncryptionContentAlgorithm() + { + return $this->enc; + } +} \ No newline at end of file diff --git a/app/libs/oauth2/models/Principal.php b/app/libs/oauth2/models/Principal.php new file mode 100644 index 00000000..8f31a0da --- /dev/null +++ b/app/libs/oauth2/models/Principal.php @@ -0,0 +1,74 @@ +auth_time; + } + + /** + * @return int + */ + public function getUserId() + { + return (int)$this->user_id; + } + + /** + * @return string + */ + public function getOPBrowserState() + { + return $this->opbs; + } + + /** + * @param array $state + * @return $this + */ + public function setState(array $state) + { + $this->user_id = intval($state[0]); + $this->auth_time = intval($state[1]); + $this->opbs = $state[2]; + return $this; + } +} \ No newline at end of file diff --git a/app/libs/oauth2/models/SecurityContext.php b/app/libs/oauth2/models/SecurityContext.php new file mode 100644 index 00000000..ff62ed0b --- /dev/null +++ b/app/libs/oauth2/models/SecurityContext.php @@ -0,0 +1,91 @@ +requested_user_id; + } + + /** + * @param int $requested_user_id + * @return $this + */ + public function setRequestedUserId($requested_user_id) + { + $this->requested_user_id = $requested_user_id; + return $this; + } + + /** + * @return bool + */ + public function isAuthTimeRequired() + { + return $this->requested_auth_time; + } + + /** + * @param bool $requested_auth_time + * @return $this + */ + public function setAuthTimeRequired($requested_auth_time) + { + $this->requested_auth_time = $requested_auth_time; + return $this; + } + + /** + * @return array + */ + public function getState() + { + return array + ( + $this->requested_user_id, + $this->requested_auth_time, + ); + } + + /** + * @param array $state + * @return $this + */ + public function setState(array $state) + { + $this->requested_user_id = $state[0]; + $this->requested_auth_time = $state[1]; + return $this; + } +} \ No newline at end of file diff --git a/app/libs/oauth2/models/Token.php b/app/libs/oauth2/models/Token.php index 0d8f76d3..76da6454 100644 --- a/app/libs/oauth2/models/Token.php +++ b/app/libs/oauth2/models/Token.php @@ -51,19 +51,28 @@ abstract class Token extends Identifier return $this->client_id; } + /** + * @return mixed + */ public function getAudience() { return $this->audience; } + /** + * @return string + */ public function getFromIp() { return $this->from_ip; } + /** + * @return int + */ public function getUserId() { - return $this->user_id; + return intval($this->user_id); } public function getRemainingLifetime() diff --git a/app/libs/oauth2/models/TokenEndpointAuthInfo.php b/app/libs/oauth2/models/TokenEndpointAuthInfo.php new file mode 100644 index 00000000..03023f20 --- /dev/null +++ b/app/libs/oauth2/models/TokenEndpointAuthInfo.php @@ -0,0 +1,59 @@ +auth_method = $auth_method; + $this->auth_signing_alg = $auth_signing_alg; + } + + /** + * @return string + */ + public function getAuthenticationMethod(){ + return $this->auth_method; + } + + /** + * @return IntegrityCheckingAlgorithm + */ + public function getSigningAlgorithm(){ + return $this->auth_signing_alg; + } + +} \ No newline at end of file diff --git a/app/libs/oauth2/repositories/IApiScopeGroupRepository.php b/app/libs/oauth2/repositories/IApiScopeGroupRepository.php new file mode 100644 index 00000000..fffee52d --- /dev/null +++ b/app/libs/oauth2/repositories/IApiScopeGroupRepository.php @@ -0,0 +1,26 @@ +getParam(OAuth2Protocol::OAuth2Protocol_Nonce); + } + + /** + * @return null|string + */ + public function getDisplay() + { + return $this->getParam(OAuth2Protocol::OAuth2Protocol_Display); + } + + /** + * @return string[] + */ + public function getPrompt() + { + $prompt = $this->getParam(OAuth2Protocol::OAuth2Protocol_Prompt); + if(!empty($prompt)) + return explode(' ', $prompt); + return array(); + } + + /** + * @return null|int + */ + public function getMaxAge() + { + $value = $this->getParam(OAuth2Protocol::OAuth2Protocol_MaxAge); + if(!is_null($value)) + $value = intval($value); + return $value; + } + + /** + * @return null|string + */ + public function getUILocales() + { + return $this->getParam(OAuth2Protocol::OAuth2Protocol_UILocales); + } + + /** + * @return null|string + */ + public function getIdTokenHint() + { + return $this->getParam(OAuth2Protocol::OAuth2Protocol_IDTokenHint); + } + + /** + * @return null|string + */ + public function getLoginHint() + { + return $this->getParam(OAuth2Protocol::OAuth2Protocol_LoginHint); + } + + /** + * @return null|string + */ + public function getACRValues() + { + return $this->getParam(OAuth2Protocol::OAuth2Protocol_ACRValues); + } + + /** + * @return bool + */ + public function offlineAccessRequested() + { + return str_contains($this->getScope(), OAuth2Protocol::OfflineAccess_Scope); + } + + /** + * @param OAuth2AuthorizationRequest $auth_request + */ + public function __construct(OAuth2AuthorizationRequest $auth_request) + { + parent::__construct($auth_request->getMessage()); + } + + /** + * http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes + * The Response Mode request parameter response_mode informs the Authorization Server of the mechanism to be used + * for returning Authorization Response parameters from the Authorization Endpoint + */ + public function getResponseMode() + { + return $this->getParam(OAuth2Protocol::OAuth2Protocol_ResponseMode); + } + + /** + * Validates current request + * @return bool + */ + public function isValid() + { + $res = parent::isValid(); + if(!$res) throw new InvalidOAuth2Request('invalid parent request!'); + + if($res) + { + // Reject requests without nonce unless using the code flow + $nonce = $this->getNonce(); + if(empty($nonce) && !OAuth2Protocol::responseTypeBelongsToFlow($this->getResponseType(false), OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode)) + throw new InvalidOAuth2Request('missing nonce!'); + + $current_scope = trim($this->getScope()); + + if(!str_contains($current_scope, OAuth2Protocol::OpenIdConnect_Scope)) + throw new InvalidOAuth2Request('missing openid scope!'); + + if($current_scope === OAuth2Protocol::OpenIdConnect_Scope) + { + // add as default scope to current request + $this->setParam(OAuth2Protocol::OAuth2Protocol_Scope, sprintf('%s %s', $current_scope, IUserService::UserProfileScope_Profile)); + } + + $display = $this->getDisplay(); + + if(!empty($display) && !in_array($display, OAuth2Protocol::$valid_display_values)) + throw new InvalidOAuth2Request('not valid display value'); + + $prompt = $this->getPrompt(); + + if(!empty($prompt) && is_array($prompt)) + { + // if this parameter contains none with any other value, an error is returned. + if(in_array(OAuth2Protocol::OAuth2Protocol_Prompt_None, $prompt) && count($prompt) > 1) + throw new InvalidOAuth2Request('not valid prompt!'); + + foreach($prompt as $p) + { + if(!in_array($p, OAuth2Protocol::$valid_prompt_values)) + throw new InvalidOAuth2Request('not valid prompt!'); + } + } + + $response_mode = $this->getResponseMode(); + + if(!empty($response_mode)) + { + if(!in_array($response_mode, OAuth2Protocol::$valid_response_modes)) throw new InvalidOAuth2Request('invalid response_mode!'); + + $default_response_mode = OAuth2Protocol::getDefaultResponseMode($this->getResponseType(false)); + + if($default_response_mode === $response_mode) throw new InvalidOAuth2Request('invalid response_mode!'); + } + + // http://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess + // MUST ensure that the prompt parameter contains consent unless other conditions for processing the request + // permitting offline access to the requested resources are in place + if($this->offlineAccessRequested() && empty($prompt)) + throw new InvalidOAuth2Request('invalid offline access!'); + + if($this->offlineAccessRequested() && !in_array(OAuth2Protocol::OAuth2Protocol_Prompt_Consent, $prompt)) + throw new InvalidOAuth2Request('invalid offline access!'); + } + + return $res; + } + + /** + * @param string $param_name + * @return bool + */ + public function isProcessedParam($param_name){ + $res = explode(' ', $this->getParam('processed_params')); + return in_array($param_name, $res); + } + + /** + * @param string $param_name + * @return $this + */ + public function markParamAsProcessed($param_name) + { + $res = $this->getParam('processed_params'); + if(!empty($res)) + { + $res = $res .' '; + } + $res = $res .$param_name; + $this->setParam('processed_params', $res); + return $this; + } +} \ No newline at end of file diff --git a/app/libs/oauth2/requests/OAuth2AuthorizationRequest.php b/app/libs/oauth2/requests/OAuth2AuthorizationRequest.php index 9cd79fe2..6408f3e3 100644 --- a/app/libs/oauth2/requests/OAuth2AuthorizationRequest.php +++ b/app/libs/oauth2/requests/OAuth2AuthorizationRequest.php @@ -2,36 +2,49 @@ namespace oauth2\requests; -use oauth2\OAuth2Protocol; use oauth2\OAuth2Message; +use oauth2\OAuth2Protocol; /** * Class OAuth2AuthorizationRequest * http://tools.ietf.org/html/rfc6749#section-4.1.1 * @package oauth2\requests */ -class OAuth2AuthorizationRequest extends OAuth2Request { +class OAuth2AuthorizationRequest extends OAuth2Request +{ public function __construct(OAuth2Message $msg) { parent::__construct($msg); } - public static $params = array( - OAuth2Protocol::OAuth2Protocol_ResponseType => OAuth2Protocol::OAuth2Protocol_ResponseType, - OAuth2Protocol::OAuth2Protocol_ClientId => OAuth2Protocol::OAuth2Protocol_ClientId, - OAuth2Protocol::OAuth2Protocol_RedirectUri => OAuth2Protocol::OAuth2Protocol_RedirectUri, - OAuth2Protocol::OAuth2Protocol_Scope => OAuth2Protocol::OAuth2Protocol_Scope, - OAuth2Protocol::OAuth2Protocol_State => OAuth2Protocol::OAuth2Protocol_State, - OAuth2Protocol::OAuth2Protocol_Approval_Prompt => OAuth2Protocol::OAuth2Protocol_Approval_Prompt, - OAuth2Protocol::OAuth2Protocol_AccessType => OAuth2Protocol::OAuth2Protocol_AccessType, + public static $params = array + ( + OAuth2Protocol::OAuth2Protocol_ResponseType => OAuth2Protocol::OAuth2Protocol_ResponseType, + OAuth2Protocol::OAuth2Protocol_ClientId => OAuth2Protocol::OAuth2Protocol_ClientId, + OAuth2Protocol::OAuth2Protocol_RedirectUri => OAuth2Protocol::OAuth2Protocol_RedirectUri, + OAuth2Protocol::OAuth2Protocol_Scope => OAuth2Protocol::OAuth2Protocol_Scope, + OAuth2Protocol::OAuth2Protocol_State => OAuth2Protocol::OAuth2Protocol_State, + OAuth2Protocol::OAuth2Protocol_Approval_Prompt => OAuth2Protocol::OAuth2Protocol_Approval_Prompt, + OAuth2Protocol::OAuth2Protocol_AccessType => OAuth2Protocol::OAuth2Protocol_AccessType, ); /** - * @return null|string + * The Response Type request parameter response_type informs the Authorization Server of the desired authorization + * processing flow + * @param bool|true $raw + * @return array|string|null */ - public function getResponseType(){ - return $this->getParam(OAuth2Protocol::OAuth2Protocol_ResponseType); + public function getResponseType($raw = true) + { + if($raw) + return $this->getParam(OAuth2Protocol::OAuth2Protocol_ResponseType); + + return explode + ( + OAuth2Protocol::OAuth2Protocol_ResponseType_Delimiter, + $this->getParam(OAuth2Protocol::OAuth2Protocol_ResponseType) + ); } /** @@ -39,7 +52,8 @@ class OAuth2AuthorizationRequest extends OAuth2Request { * The value passed in this parameter must exactly match the value shown in the Admin Console. * @return null|string */ - public function getClientId(){ + public function getClientId() + { return $this->getParam(OAuth2Protocol::OAuth2Protocol_ClientId); } @@ -47,7 +61,8 @@ class OAuth2AuthorizationRequest extends OAuth2Request { * One of the redirect_uri values registered * @return null|string */ - public function getRedirectUri(){ + public function getRedirectUri() + { return $this->getParam(OAuth2Protocol::OAuth2Protocol_RedirectUri); } @@ -55,7 +70,8 @@ class OAuth2AuthorizationRequest extends OAuth2Request { * Space-delimited set of permissions that the application requests. * @return null|string */ - public function getScope(){ + public function getScope() + { return $this->getParam(OAuth2Protocol::OAuth2Protocol_Scope); } @@ -66,7 +82,8 @@ class OAuth2AuthorizationRequest extends OAuth2Request { * cross-site-request-forgery mitigations. * @return null|string */ - public function getState(){ + public function getState() + { return $this->getParam(OAuth2Protocol::OAuth2Protocol_State); } @@ -77,10 +94,14 @@ class OAuth2AuthorizationRequest extends OAuth2Request { * previously gave consent to your application for a given set of scopes. * @return null|string */ - public function getApprovalPrompt(){ + public function getApprovalPrompt() + { $approval = $this->getParam(OAuth2Protocol::OAuth2Protocol_Approval_Prompt); - if(is_null($approval)) + if (is_null($approval)) + { $approval = OAuth2Protocol::OAuth2Protocol_Approval_Prompt_Auto; + } + return $approval; } @@ -91,10 +112,14 @@ class OAuth2AuthorizationRequest extends OAuth2Request { * token the first time your application exchanges an authorization code for a user. * @return null|string */ - public function getAccessType(){ + public function getAccessType() + { $access_type = $this->getParam(OAuth2Protocol::OAuth2Protocol_AccessType); - if(is_null($access_type)) + if (is_null($access_type)) + { $access_type = OAuth2Protocol::OAuth2Protocol_AccessType_Online; + } + return $access_type; } @@ -104,19 +129,36 @@ class OAuth2AuthorizationRequest extends OAuth2Request { */ public function isValid() { - if(is_null($this->getResponseType())) - return false; - - if(is_null($this->getClientId())) - return false; - - if(is_null($this->getRedirectUri())) - return false; - //approval_prompt - $valid_approvals = array(OAuth2Protocol::OAuth2Protocol_Approval_Prompt_Auto,OAuth2Protocol::OAuth2Protocol_Approval_Prompt_Force); - if(!in_array($this->getApprovalPrompt(),$valid_approvals)){ + if (is_null($this->getResponseType())) + { return false; } + + if(!OAuth2Protocol::responseTypeBelongsToFlow($this->getResponseType(false), 'all')) + return false; + + if (is_null($this->getClientId())) + { + return false; + } + + if (is_null($this->getRedirectUri())) + { + return false; + } + + //approval_prompt + $valid_approvals = array + ( + OAuth2Protocol::OAuth2Protocol_Approval_Prompt_Auto, + OAuth2Protocol::OAuth2Protocol_Approval_Prompt_Force + ); + + if (!in_array($this->getApprovalPrompt(), $valid_approvals)) + { + return false; + } + return true; } } diff --git a/app/libs/oauth2/requests/OAuth2LogoutRequest.php b/app/libs/oauth2/requests/OAuth2LogoutRequest.php new file mode 100644 index 00000000..cb6641cd --- /dev/null +++ b/app/libs/oauth2/requests/OAuth2LogoutRequest.php @@ -0,0 +1,74 @@ +getIdTokenHint(); + if(empty($id_token_hint)) return false; + $log_out_uri = $this->getPostLogoutRedirectUri(); + $state = $this->getState(); + if(!empty($log_out_uri)) + { + return !empty($state); + } + return true; + } + + /** + * @return string|null + */ + public function getIdTokenHint() + { + return $this->getParam(OAuth2Protocol::OAuth2Protocol_IDTokenHint); + } + + /** + * @return string|null + */ + public function getPostLogoutRedirectUri() + { + return $this->getParam(OAuth2Protocol::OAuth2Protocol_PostLogoutRedirectUri); + } + + /** + * @return string|null + */ + public function getState() + { + return $this->getParam(OAuth2Protocol::OAuth2Protocol_State); + } +} \ No newline at end of file diff --git a/app/libs/oauth2/requests/OAuth2Request.php b/app/libs/oauth2/requests/OAuth2Request.php index c36896f7..6ed6dbe6 100644 --- a/app/libs/oauth2/requests/OAuth2Request.php +++ b/app/libs/oauth2/requests/OAuth2Request.php @@ -3,29 +3,66 @@ namespace oauth2\requests; use oauth2\OAuth2Message; -abstract class OAuth2Request extends OAuth2Message { +/** + * Class OAuth2Request + * @package oauth2\requests + */ +abstract class OAuth2Request { + /** + * @var OAuth2Message + */ protected $message; + /** + * @param OAuth2Message $msg + */ public function __construct(OAuth2Message $msg) { $this->message = $msg; } + /** + * @return OAuth2Message + */ public function getMessage(){ return $this->message; } + /** + * @param string $param + * @return null + */ public function getParam($param) { - return $this->message->getParam($param); + $value = $this->message->getParam($param); + if(!empty($value)) $value = urldecode($value); + return $value; } + /** + * @param string $param + * @param mixed $value + * @return $this + */ + public function setParam($param, $value) + { + $this->message->setParam($param, $value); + return $this; + } + + /** + * @return string + */ public function toString() { $string = $this->message->toString(); return $string; } + /** + * @return bool + */ public abstract function isValid(); + } \ No newline at end of file diff --git a/app/libs/oauth2/requests/OAuth2RequestMemento.php b/app/libs/oauth2/requests/OAuth2RequestMemento.php new file mode 100644 index 00000000..b0f6ac9a --- /dev/null +++ b/app/libs/oauth2/requests/OAuth2RequestMemento.php @@ -0,0 +1,65 @@ +state = $state; + } + + /** + * @return array + */ + public function getState() + { + return $this->state; + } + + /** + * @param OAuth2Message $request + * @return OAuth2RequestMemento + */ + static public function buildFromRequest(OAuth2Message $request) + { + $r = new ReflectionObject($request); + $p = $r->getProperty('container'); + $p->setAccessible(true); + return new self($p->getValue($request)); + } + + /** + * @param array $state + * @return OAuth2RequestMemento + */ + static public function buildFromState(array $state){ + return new self($state); + } +} \ No newline at end of file diff --git a/app/libs/oauth2/requests/OAuth2TokenRequest.php b/app/libs/oauth2/requests/OAuth2TokenRequest.php index 015b59a7..ecb36b55 100644 --- a/app/libs/oauth2/requests/OAuth2TokenRequest.php +++ b/app/libs/oauth2/requests/OAuth2TokenRequest.php @@ -10,7 +10,8 @@ use oauth2\OAuth2Protocol; * Base Token Request * @package oauth2\requests */ -class OAuth2TokenRequest extends OAuth2Request { +class OAuth2TokenRequest extends OAuth2Request +{ public function __construct(OAuth2Message $msg) { @@ -27,8 +28,8 @@ class OAuth2TokenRequest extends OAuth2Request { return true; } - public function getGrantType(){ - + public function getGrantType() + { return $this->getParam(OAuth2Protocol::OAuth2Protocol_GrantType); } } \ No newline at end of file diff --git a/app/libs/oauth2/resource_server/IUserService.php b/app/libs/oauth2/resource_server/IUserService.php index 640c80e0..09977d04 100644 --- a/app/libs/oauth2/resource_server/IUserService.php +++ b/app/libs/oauth2/resource_server/IUserService.php @@ -2,11 +2,14 @@ namespace oauth2\resource_server; +use jwt\impl\JWTClaimSet; + /** * Interface IUserService * @package oauth2\resource_server */ -interface IUserService { +interface IUserService +{ /** * This scope value requests access to the End-User's default profile Claims, which are: * name, family_name, given_name, middle_name, nickname, preferred_username, profile, picture, @@ -23,4 +26,9 @@ interface IUserService { const UserProfileScope_Address = 'address'; public function getCurrentUserInfo(); + + /** + * @return JWTClaimSet + */ + public function getCurrentUserInfoClaims(); } \ No newline at end of file diff --git a/app/libs/oauth2/resource_server/OAuth2ProtectedService.php b/app/libs/oauth2/resource_server/OAuth2ProtectedService.php index 41c852e0..2aa51300 100644 --- a/app/libs/oauth2/resource_server/OAuth2ProtectedService.php +++ b/app/libs/oauth2/resource_server/OAuth2ProtectedService.php @@ -10,9 +10,16 @@ use utils\services\ILogService; * Base Class for OAUTH2 protected endpoints * @package oauth2\resource_server */ -abstract class OAuth2ProtectedService { +abstract class OAuth2ProtectedService +{ + /** + * @var IResourceServerContext + */ protected $resource_server_context; + /** + * @var ILogService + */ protected $log_service; /** @@ -21,7 +28,7 @@ abstract class OAuth2ProtectedService { */ public function __construct(IResourceServerContext $resource_server_context, ILogService $log_service) { - $this->log_service = $log_service; + $this->log_service = $log_service; $this->resource_server_context = $resource_server_context; } } \ No newline at end of file diff --git a/app/libs/oauth2/responses/OAuth2AccessTokenFragmentResponse.php b/app/libs/oauth2/responses/OAuth2AccessTokenFragmentResponse.php index 85e588dd..d8c694aa 100644 --- a/app/libs/oauth2/responses/OAuth2AccessTokenFragmentResponse.php +++ b/app/libs/oauth2/responses/OAuth2AccessTokenFragmentResponse.php @@ -4,18 +4,33 @@ namespace oauth2\responses; use oauth2\OAuth2Protocol; -class OAuth2AccessTokenFragmentResponse extends OAuth2IndirectFragmentResponse { +/** + * Class OAuth2AccessTokenFragmentResponse + * @package oauth2\responses + */ +class OAuth2AccessTokenFragmentResponse extends OAuth2IndirectFragmentResponse +{ - public function __construct($return_to, $access_token, $expires_in, $scope=null, $state=null) + /** + * @param $return_to + * @param $access_token + * @param $expires_in + * @param null $scope + * @param null $state + */ + public function __construct($return_to, $access_token, $expires_in, $scope = null, $state = null) { parent::__construct(); $this->setReturnTo($return_to); - $this[OAuth2Protocol::OAuth2Protocol_AccessToken] = $access_token; - $this[OAuth2Protocol::OAuth2Protocol_AccessToken_ExpiresIn] = $expires_in; - $this[OAuth2Protocol::OAuth2Protocol_TokenType] = 'Bearer'; + if(!is_null($access_token) && !empty($access_token)) + { + $this[OAuth2Protocol::OAuth2Protocol_AccessToken] = $access_token; + $this[OAuth2Protocol::OAuth2Protocol_AccessToken_ExpiresIn] = $expires_in; + $this[OAuth2Protocol::OAuth2Protocol_TokenType] = 'Bearer'; + } if(!is_null($scope) && !empty($scope)) $this[OAuth2Protocol::OAuth2Protocol_Scope] = $scope; diff --git a/app/libs/oauth2/responses/OAuth2AccessTokenResponse.php b/app/libs/oauth2/responses/OAuth2AccessTokenResponse.php index 64f7707f..03b8d7d6 100644 --- a/app/libs/oauth2/responses/OAuth2AccessTokenResponse.php +++ b/app/libs/oauth2/responses/OAuth2AccessTokenResponse.php @@ -3,6 +3,7 @@ namespace oauth2\responses; use oauth2\OAuth2Protocol; +use utils\http\HttpContentType; /** @@ -10,17 +11,27 @@ use oauth2\OAuth2Protocol; * http://tools.ietf.org/html/rfc6749#section-4.1.4 * @package oauth2\responses */ -class OAuth2AccessTokenResponse extends OAuth2DirectResponse { +class OAuth2AccessTokenResponse extends OAuth2DirectResponse +{ - public function __construct($access_token, $expires_in, $refresh_token=null, $scope=null) + /** + * @param string $access_token + * @param string $expires_in + * @param null|string $refresh_token + * @param null|string $scope + */ + public function __construct($access_token = null, $expires_in = null, $refresh_token = null, $scope = null) { // Successful Responses: A server receiving a valid request MUST send a // response with an HTTP status code of 200. - parent::__construct(self::HttpOkResponse, self::DirectResponseContentType); + parent::__construct(self::HttpOkResponse, HttpContentType::Json); - $this[OAuth2Protocol::OAuth2Protocol_AccessToken] = $access_token; - $this[OAuth2Protocol::OAuth2Protocol_AccessToken_ExpiresIn] = $expires_in; - $this[OAuth2Protocol::OAuth2Protocol_TokenType] = 'Bearer'; + if(!empty($access_token)) + { + $this[OAuth2Protocol::OAuth2Protocol_AccessToken] = $access_token; + $this[OAuth2Protocol::OAuth2Protocol_AccessToken_ExpiresIn] = $expires_in; + $this[OAuth2Protocol::OAuth2Protocol_TokenType] = 'Bearer'; + } if(!is_null($refresh_token) && !empty($refresh_token)) $this[OAuth2Protocol::OAuth2Protocol_RefreshToken] = $refresh_token; diff --git a/app/libs/oauth2/responses/OAuth2AccessTokenValidationResponse.php b/app/libs/oauth2/responses/OAuth2AccessTokenValidationResponse.php index 1a542ff2..2382dbeb 100644 --- a/app/libs/oauth2/responses/OAuth2AccessTokenValidationResponse.php +++ b/app/libs/oauth2/responses/OAuth2AccessTokenValidationResponse.php @@ -2,39 +2,54 @@ namespace oauth2\responses; +use oauth2\models\IClient; use oauth2\OAuth2Protocol; +use openid\model\IOpenIdUser; +use utils\http\HttpContentType; +/** + * Class OAuth2AccessTokenValidationResponse + * @package oauth2\responses + */ class OAuth2AccessTokenValidationResponse extends OAuth2DirectResponse { /** * @param array|int $access_token * @param string $scope * @param $audience - * @param $client_id + * @param IClient $client * @param $expires_in - * @param null $user_id - * @param null $application_type + * @param IOpenIdUser|null $user * @param array $allowed_urls * @param array $allowed_origins */ - public function __construct($access_token,$scope, $audience, $client_id, $expires_in, $user_id = null, $application_type = null, $allowed_urls = array(), $allowed_origins = array()) + public function __construct + ( + $access_token, + $scope, + $audience, + IClient $client, + $expires_in, + IOpenIdUser $user = null, + $allowed_urls = array(), + $allowed_origins = array() + ) { // Successful Responses: A server receiving a valid request MUST send a // response with an HTTP status code of 200. - parent::__construct(self::HttpOkResponse, self::DirectResponseContentType); + parent::__construct(self::HttpOkResponse, HttpContentType::Json); $this[OAuth2Protocol::OAuth2Protocol_AccessToken] = $access_token; - $this[OAuth2Protocol::OAuth2Protocol_ClientId] = $client_id; + $this[OAuth2Protocol::OAuth2Protocol_ClientId] = $client->getClientId(); + $this['application_type'] = $client->getApplicationType(); $this[OAuth2Protocol::OAuth2Protocol_TokenType] = 'Bearer'; $this[OAuth2Protocol::OAuth2Protocol_Scope] = $scope; $this[OAuth2Protocol::OAuth2Protocol_Audience] = $audience; $this[OAuth2Protocol::OAuth2Protocol_AccessToken_ExpiresIn] = $expires_in; - if(!is_null($user_id)){ - $this[OAuth2Protocol::OAuth2Protocol_UserId] = $user_id; - } - - if(!is_null($application_type)){ - $this['application_type'] = $application_type; + if(!is_null($user)) + { + $this[OAuth2Protocol::OAuth2Protocol_UserId] = $user->getId(); + $this['user_external_id'] = $user->getExternalIdentifier(); } if(count($allowed_urls)){ diff --git a/app/libs/oauth2/responses/OAuth2AuthorizationResponse.php b/app/libs/oauth2/responses/OAuth2AuthorizationResponse.php index f693c07a..ee1150ab 100644 --- a/app/libs/oauth2/responses/OAuth2AuthorizationResponse.php +++ b/app/libs/oauth2/responses/OAuth2AuthorizationResponse.php @@ -9,37 +9,51 @@ use oauth2\OAuth2Protocol; * http://tools.ietf.org/html/rfc6749#section-4.1.2 * @package oauth2\responses */ -class OAuth2AuthorizationResponse extends OAuth2IndirectResponse { +class OAuth2AuthorizationResponse extends OAuth2IndirectResponse +{ /** * @param $return_url * @param $code * @param null $scope * @param null $state + * @param null $session_state */ - public function __construct($return_url, $code, $scope=null, $state=null) + public function __construct($return_url, $code, $scope = null, $state = null, $session_state = null) { parent::__construct(); $this[OAuth2Protocol::OAuth2Protocol_ResponseType_Code] = $code; $this->setReturnTo($return_url); - if(!is_null($scope)) + if(!empty($scope)) $this[OAuth2Protocol::OAuth2Protocol_Scope] = $scope; - if(!is_null($state)) + if(!empty($state)) $this[OAuth2Protocol::OAuth2Protocol_State] = $state; + + if(!empty($session_state)) + $this[OAuth2Protocol::OAuth2Protocol_Session_State] = $session_state; } - public function getAuthCode(){ + public function getAuthCode() + { return isset($this[OAuth2Protocol::OAuth2Protocol_ResponseType_Code])?$this[OAuth2Protocol::OAuth2Protocol_ResponseType_Code] :null; } - public function getState(){ + public function getState() + { return isset($this[OAuth2Protocol::OAuth2Protocol_State])?$this[OAuth2Protocol::OAuth2Protocol_State] :null; } - public function getScope(){ + public function getScope() + { return isset($this[OAuth2Protocol::OAuth2Protocol_Scope])?$this[OAuth2Protocol::OAuth2Protocol_Scope] :null; } + + public function getSessionState() + { + return isset($this[OAuth2Protocol::OAuth2Protocol_Session_State])?$this[OAuth2Protocol::OAuth2Protocol_Session_State] :null; + } + } \ No newline at end of file diff --git a/app/libs/oauth2/responses/OAuth2DirectErrorResponse.php b/app/libs/oauth2/responses/OAuth2DirectErrorResponse.php index 79cffda5..b7a0a45d 100644 --- a/app/libs/oauth2/responses/OAuth2DirectErrorResponse.php +++ b/app/libs/oauth2/responses/OAuth2DirectErrorResponse.php @@ -3,13 +3,58 @@ namespace oauth2\responses; use oauth2\OAuth2Protocol; +use utils\http\HttpContentType; -class OAuth2DirectErrorResponse extends OAuth2DirectResponse { - public function __construct($error) +/** + * Class OAuth2DirectErrorResponse + * @package oauth2\responses + */ +class OAuth2DirectErrorResponse extends OAuth2DirectResponse +{ + + /** + * @param string $error + * @param null|string $error_description + * @param null|string $state + */ + public function __construct($error, $error_description = null, $state = null) { // Error Response: A server receiving an invalid request MUST send a // response with an HTTP status code of 400. - parent::__construct(self::HttpErrorResponse, self::DirectResponseContentType); + parent::__construct(self::HttpErrorResponse, HttpContentType::Json); + $this->setError($error); + + if(!empty ($error_description)) + $this->setErrorDescription($error_description); + + if(!empty($state)) + $this->setState($state); + } + + /** + * @param string $error + */ + public function setError($error) + { $this[OAuth2Protocol::OAuth2Protocol_Error] = $error; + return $this; + } + + /** + * @param string $state + */ + public function setState($state) + { + $this[OAuth2Protocol::OAuth2Protocol_State] = $state; + return $this; + } + + /** + * @param string $error_description + */ + public function setErrorDescription($error_description) + { + $this[OAuth2Protocol::OAuth2Protocol_ErrorDescription] = $error_description; + return $this; } } \ No newline at end of file diff --git a/app/libs/oauth2/responses/OAuth2DirectResponse.php b/app/libs/oauth2/responses/OAuth2DirectResponse.php index 86521f07..52db994f 100644 --- a/app/libs/oauth2/responses/OAuth2DirectResponse.php +++ b/app/libs/oauth2/responses/OAuth2DirectResponse.php @@ -2,12 +2,14 @@ namespace oauth2\responses; -class OAuth2DirectResponse extends OAuth2Response { +use utils\http\HttpContentType; + +class OAuth2DirectResponse extends OAuth2Response +{ - const DirectResponseContentType = "application/json;charset=UTF-8"; const OAuth2DirectResponse = 'OAuth2DirectResponse'; - public function __construct($http_code=self::HttpOkResponse, $content_type=self::DirectResponseContentType) + public function __construct($http_code = self::HttpOkResponse, $content_type = HttpContentType::Json) { // Successful Responses: A server receiving a valid request MUST send a // response with an HTTP status code of 200. diff --git a/app/libs/oauth2/responses/OAuth2HybridTokenFragmentResponse.php b/app/libs/oauth2/responses/OAuth2HybridTokenFragmentResponse.php new file mode 100644 index 00000000..163de476 --- /dev/null +++ b/app/libs/oauth2/responses/OAuth2HybridTokenFragmentResponse.php @@ -0,0 +1,55 @@ +return_to = $return_to; + /** + * @param string $error + * @param string $error_description + * @param null|string $return_to + * @param null|string $state + */ + public function __construct($error, $error_description, $return_to = null, $state = null) + { + parent::__construct(); + + if(!empty($state)) + $this[OAuth2Protocol::OAuth2Protocol_State] = $state; + + if(!empty($error_description)) + $this->setErrorDescription($error_description); + + $this->setHttpCode(HttpResponse::HttpErrorResponse); + + $this->setError($error); + + $this->setReturnTo($return_to); } - public function setError($error){ + /** + * @param string $error + */ + public function setError($error) + { $this[OAuth2Protocol::OAuth2Protocol_Error] = $error; } + + /** + * @param string $state + */ + public function setState($state) + { + $this[OAuth2Protocol::OAuth2Protocol_State] = $state; + } + + /** + * @param string $error_description + */ + public function setErrorDescription($error_description) + { + $this[OAuth2Protocol::OAuth2Protocol_ErrorDescription] = $error_description; + } } \ No newline at end of file diff --git a/app/libs/oauth2/responses/OAuth2IndirectFragmentErrorResponse.php b/app/libs/oauth2/responses/OAuth2IndirectFragmentErrorResponse.php index aef2df14..00a7d487 100644 --- a/app/libs/oauth2/responses/OAuth2IndirectFragmentErrorResponse.php +++ b/app/libs/oauth2/responses/OAuth2IndirectFragmentErrorResponse.php @@ -4,16 +4,42 @@ namespace oauth2\responses; use oauth2\OAuth2Protocol; -class OAuth2IndirectFragmentErrorResponse extends OAuth2IndirectFragmentResponse { +/** + * Class OAuth2IndirectFragmentErrorResponse + * @package oauth2\responses + */ +class OAuth2IndirectFragmentErrorResponse extends OAuth2IndirectFragmentResponse +{ - public function __construct($error, $return_to=null){ + /** + * @param string $error + * @param string $error_description + * @param null|string $return_to + * @param null|string $state + */ + public function __construct($error, $error_description, $return_to = null, $state = null) + { parent::__construct(); + + if(!empty($state)) + $this[OAuth2Protocol::OAuth2Protocol_State] = $state; + + if(!empty($error_description)) + $this->setErrorDescription($error_description); + $this->setError($error); + $this->setReturnTo($return_to); } - public function setError($error){ + public function setError($error) + { $this[OAuth2Protocol::OAuth2Protocol_Error] = $error; } + public function setErrorDescription($error_description) + { + $this[OAuth2Protocol::OAuth2Protocol_ErrorDescription] = $error_description; + } + } \ No newline at end of file diff --git a/app/libs/oauth2/responses/OAuth2IndirectFragmentResponse.php b/app/libs/oauth2/responses/OAuth2IndirectFragmentResponse.php index 88148ea7..00938965 100644 --- a/app/libs/oauth2/responses/OAuth2IndirectFragmentResponse.php +++ b/app/libs/oauth2/responses/OAuth2IndirectFragmentResponse.php @@ -2,11 +2,13 @@ namespace oauth2\responses; -class OAuth2IndirectFragmentResponse extends OAuth2IndirectResponse { +class OAuth2IndirectFragmentResponse extends OAuth2IndirectResponse +{ const OAuth2IndirectFragmentResponse ='OAuth2IndirectFragmentResponse'; - public function __construct(){ + public function __construct() + { parent::__construct(); } diff --git a/app/libs/oauth2/responses/OAuth2IndirectResponse.php b/app/libs/oauth2/responses/OAuth2IndirectResponse.php index 5c7141a6..7448cc52 100644 --- a/app/libs/oauth2/responses/OAuth2IndirectResponse.php +++ b/app/libs/oauth2/responses/OAuth2IndirectResponse.php @@ -2,19 +2,27 @@ namespace oauth2\responses; -abstract class OAuth2IndirectResponse extends OAuth2Response { +use utils\http\HttpContentType; +/** + * Class OAuth2IndirectResponse + * @package oauth2\responses + */ +abstract class OAuth2IndirectResponse extends OAuth2Response +{ + + /** + * @var string + */ protected $return_to; - const IndirectResponseContentType = "application/x-www-form-urlencoded"; - const OAuth2IndirectResponse ='OAuth2IndirectResponse'; + const OAuth2IndirectResponse = "OAuth2IndirectResponse"; public function __construct() { // Successful Responses: A server receiving a valid request MUST send a // response with an HTTP status code of 200. - parent::__construct(self::HttpOkResponse, self::IndirectResponseContentType); - + parent::__construct(self::HttpOkResponse, HttpContentType::Form); } public function getType() @@ -22,21 +30,26 @@ abstract class OAuth2IndirectResponse extends OAuth2Response { return self::OAuth2IndirectResponse; } - public function setReturnTo($return_to){ + public function setReturnTo($return_to) + { $this->return_to = $return_to; } - public function getReturnTo(){ + public function getReturnTo() + { return $this->return_to; } public function getContent() { $url_encoded_format = ""; - if ($this->container !== null) { + if ($this->container !== null) + { ksort($this->container); - foreach ($this->container as $key => $value) { - if (is_array($value)) { + foreach ($this->container as $key => $value) + { + if (is_array($value)) + { list($key, $value) = array($value[0], $value[1]); } $value = urlencode($value); @@ -49,6 +62,6 @@ abstract class OAuth2IndirectResponse extends OAuth2Response { public function getContentType() { - return self::IndirectResponseContentType; + return HttpContentType::Form; } } \ No newline at end of file diff --git a/app/libs/oauth2/responses/OAuth2LogoutResponse.php b/app/libs/oauth2/responses/OAuth2LogoutResponse.php new file mode 100644 index 00000000..364ec707 --- /dev/null +++ b/app/libs/oauth2/responses/OAuth2LogoutResponse.php @@ -0,0 +1,39 @@ +setReturnTo($return_to); + + if(!is_null($state) && !empty($state)) + $this[OAuth2Protocol::OAuth2Protocol_State] = $state; + } +} \ No newline at end of file diff --git a/app/libs/oauth2/responses/OAuth2PostResponse.php b/app/libs/oauth2/responses/OAuth2PostResponse.php new file mode 100644 index 00000000..d8bc0579 --- /dev/null +++ b/app/libs/oauth2/responses/OAuth2PostResponse.php @@ -0,0 +1,103 @@ +return_to; + $fields = ''; + if ($this->container !== null) + { + ksort($this->container); + foreach ($this->container as $key => $value) + { + if (is_array($value)) + { + list($key, $value) = array($value[0], $value[1]); + } + + $fields .= ''; + } + } + $content = << + Submit This Form + +
+ $fields +
+ + +HTML; + return $content; + + } + + public function getType() + { + return self::OAuth2PostResponse; + } + + /** + * @param string $return_to + */ + public function setReturnTo($return_to) + { + $this->return_to = $return_to; + } + + /** + * @return string + */ + public function getReturnTo() + { + return $this->return_to; + } + + public function getContentType() + { + return HttpContentType::Html; + } +} \ No newline at end of file diff --git a/app/libs/oauth2/responses/OAuth2TokenRevocationResponse.php b/app/libs/oauth2/responses/OAuth2TokenRevocationResponse.php index f9b6f2e3..9fbb9437 100644 --- a/app/libs/oauth2/responses/OAuth2TokenRevocationResponse.php +++ b/app/libs/oauth2/responses/OAuth2TokenRevocationResponse.php @@ -2,6 +2,8 @@ namespace oauth2\responses; +use utils\http\HttpContentType; + /** * Class OAuth2TokenRevocationResponse * http://tools.ietf.org/html/rfc7009#section-2.2 @@ -24,6 +26,6 @@ class OAuth2TokenRevocationResponse extends OAuth2DirectResponse { { // Successful Responses: A server receiving a valid request MUST send a // response with an HTTP status code of 200. - parent::__construct(self::HttpOkResponse, self::DirectResponseContentType); + parent::__construct(self::HttpOkResponse, HttpContentType::Json); } } \ No newline at end of file diff --git a/app/libs/oauth2/responses/OAuth2WWWAuthenticateErrorResponse.php b/app/libs/oauth2/responses/OAuth2WWWAuthenticateErrorResponse.php index 48d8d5aa..628e1848 100644 --- a/app/libs/oauth2/responses/OAuth2WWWAuthenticateErrorResponse.php +++ b/app/libs/oauth2/responses/OAuth2WWWAuthenticateErrorResponse.php @@ -2,6 +2,8 @@ namespace oauth2\responses; +use utils\http\HttpContentType; + /** * Class OAuth2WWWAuthenticateErrorResponse * http://tools.ietf.org/html/rfc6750#section-3 @@ -16,7 +18,7 @@ class OAuth2WWWAuthenticateErrorResponse extends OAuth2DirectResponse { private $http_error; public function __construct($realm, $error, $error_description, $scope, $http_error){ - parent::__construct($http_error, self::DirectResponseContentType); + parent::__construct($http_error, HttpContentType::Json); $this->realm = $realm; $this->error = $error; $this->error_description = $error_description; diff --git a/app/libs/oauth2/services/IAllowedOriginService.php b/app/libs/oauth2/services/IAllowedOriginService.php deleted file mode 100644 index 6c6094bb..00000000 --- a/app/libs/oauth2/services/IAllowedOriginService.php +++ /dev/null @@ -1,17 +0,0 @@ -token_endpoint_url = $token_endpoint_url; + return $this; + } + + /** + * @param ClientAuthenticationContext $context + * @param JsonValue $kid + * @throws InvalidClientAuthenticationContextException + * @return IJWK + */ + abstract protected function getKey(ClientAuthenticationContext $context, JsonValue $kid = null); + + /** + * @param ClientAuthenticationContext $context + * @return bool + */ + public function validate(ClientAuthenticationContext $context) + { + if(!($context instanceof ClientAssertionAuthenticationContext)) + throw new InvalidClientAuthenticationContextException; + + if(empty($this->token_endpoint_url)) + throw new InvalidClientAuthenticationContextException('token_endpoint_url not set!'); + + if( $context->getClient()->getTokenEndpointAuthInfo()->getAuthenticationMethod() !== $context->getAuthType()) + return false; + + $jws = $context->getAssertion(); + + $client = $context->getClient(); + + $original_alg = $client->getTokenEndpointAuthInfo()->getSigningAlgorithm()->getName(); + + if($original_alg !== $jws->getJOSEHeader()->getAlgorithm()->getString()) + return false; + + $key = $this->getKey + ( + $context, + $jws->getJOSEHeader()->getKeyID() + ); + + if(is_null($key)) + return false; + + $verified = $jws->setKey($key)->verify($original_alg); + + if(!$verified) return false; + + $iss = $jws->getClaimSet()->getIssuer()->getString(); + $aud = $jws->getClaimSet()->getAudience()->getString(); + $sub = $jws->getClaimSet()->getSubject()->getString(); + $jti = $jws->getClaimSet()->getJWTID(); + + //todo: prevent reuse of the token + if(empty($jti)) return false; + + if($iss !== $sub) + throw new InvalidClientAuthenticationContextException('iss not match with sub!'); + + if($aud !== $this->token_endpoint_url) + return false; + + if($jws->isExpired(180)) return false; + + return true; + + } +} \ No newline at end of file diff --git a/app/libs/oauth2/strategies/ClientAuthContextValidatorFactory.php b/app/libs/oauth2/strategies/ClientAuthContextValidatorFactory.php new file mode 100644 index 00000000..fd9c7692 --- /dev/null +++ b/app/libs/oauth2/strategies/ClientAuthContextValidatorFactory.php @@ -0,0 +1,86 @@ +getAuthType()) + { + case OAuth2Protocol::TokenEndpoint_AuthMethod_ClientSecretBasic: + case OAuth2Protocol::TokenEndpoint_AuthMethod_ClientSecretPost: + { + return new ClientPlainCredentialsAuthContextValidator; + } + break; + case OAuth2Protocol::TokenEndpoint_AuthMethod_ClientSecretJwt: + { + $validator = new ClientSharedSecretAssertionAuthContextValidator; + $validator->setTokenEndpointUrl(self::$token_endpoint_url); + return $validator; + } + break; + case OAuth2Protocol::TokenEndpoint_AuthMethod_PrivateKeyJwt: + { + $validator = new ClientPrivateKeyAssertionAuthContextValidator; + $validator->setTokenEndpointUrl(self::$token_endpoint_url); + $validator->setJWKSetReader(self::$jwks_reader ); + return $validator; + } + break; + } + throw new InvalidTokenEndpointAuthMethodException($context->getAuthType()); + } +} \ No newline at end of file diff --git a/app/libs/oauth2/strategies/ClientPlainCredentialsAuthContextValidator.php b/app/libs/oauth2/strategies/ClientPlainCredentialsAuthContextValidator.php new file mode 100644 index 00000000..79a5b0f0 --- /dev/null +++ b/app/libs/oauth2/strategies/ClientPlainCredentialsAuthContextValidator.php @@ -0,0 +1,53 @@ +getClient())) + throw new InvalidClientAuthenticationContextException('client not set!'); + + if($context->getClient()->getTokenEndpointAuthInfo()->getAuthenticationMethod() !== $context->getAuthType()) + throw new InvalidClientCredentials(sprintf('invalid token endpoint auth method %s', $context->getAuthType())); + + if($context->getClient()->getClientType() !== IClient::ClientType_Confidential) + throw new InvalidClientCredentials(sprintf('invalid client type %s', $context->getClient()->getClientType())); + + + return $context->getClient()->getClientId() === $context->getId() && + $context->getClient()->getClientSecret() === $context->getSecret(); + } +} \ No newline at end of file diff --git a/app/libs/oauth2/strategies/ClientPrivateKeyAssertionAuthContextValidator.php b/app/libs/oauth2/strategies/ClientPrivateKeyAssertionAuthContextValidator.php new file mode 100644 index 00000000..1756383a --- /dev/null +++ b/app/libs/oauth2/strategies/ClientPrivateKeyAssertionAuthContextValidator.php @@ -0,0 +1,108 @@ +jwks_reader = $jwks_reader; + + return $this; + } + + /** + * @param ClientAuthenticationContext $context + * @param JsonValue $kid + * @throws InvalidClientAuthenticationContextException + * @return IJWK + */ + protected function getKey(ClientAuthenticationContext $context, JsonValue $kid = null) + { + + if(!($context instanceof ClientAssertionAuthenticationContext)) + throw new InvalidClientAuthenticationContextException; + + $client = $context->getClient(); + + $jws = $context->getAssertion(); + + + if(!is_null($kid)) + { + $key = $client->getPublicKeyByIdentifier($kid->getValue()); + if($key->isActive() && !$key->isExpired()) return $key->toJWK(); + } + $alg = $jws->getJOSEHeader()->getAlgorithm()->getString(); + + $key = $client->getCurrentPublicKeyByUse + ( + JSONWebKeyPublicKeyUseValues::Signature, + $alg + ); + + if(!is_null($key)) return $key->toJWK(); + + // no public keys set, try with jwks_url ... + if (is_null($this->jwks_reader)) + { + throw new InvalidClientAuthenticationContextException('jwks_reader not set!'); + } + + $jwk_set = $this->jwks_reader->read($client); + + if(!is_null($kid)) + { + $key = $jwk_set->getKeyById($kid->getValue()); + if(!is_null($key)) return $key; + } + + foreach ($jwk_set->getKeys() as $key) + { + if + ( + $key->getKeyUse() === JSONWebKeyPublicKeyUseValues::Signature && + $key->getAlgorithm()->getString() === $alg + ) + { + return $key; + } + } + + return null; + } +} \ No newline at end of file diff --git a/app/libs/oauth2/strategies/ClientSharedSecretAssertionAuthContextValidator.php b/app/libs/oauth2/strategies/ClientSharedSecretAssertionAuthContextValidator.php new file mode 100644 index 00000000..bc685fa9 --- /dev/null +++ b/app/libs/oauth2/strategies/ClientSharedSecretAssertionAuthContextValidator.php @@ -0,0 +1,61 @@ +getClient(); + + $jws = $context->getAssertion(); + + $key = OctetSequenceJWKFactory::build + ( + new OctetSequenceJWKSpecification + ( + $client->getClientSecret(), + $jws->getJOSEHeader()->getAlgorithm()->getString() + ) + ); + + return $key; + } +} \ No newline at end of file diff --git a/app/libs/oauth2/strategies/IClientAuthContextValidator.php b/app/libs/oauth2/strategies/IClientAuthContextValidator.php new file mode 100644 index 00000000..d71c2516 --- /dev/null +++ b/app/libs/oauth2/strategies/IClientAuthContextValidator.php @@ -0,0 +1,30 @@ +getName(); - if($class_name =='oauth2\requests\OAuth2AuthorizationRequest'){ - $response_type = $request->getResponseType(); - switch($response_type){ - case OAuth2Protocol::OAuth2Protocol_ResponseType_Token: - return new OAuth2IndirectFragmentErrorResponse($error,$return_url); - break; - case OAuth2Protocol::OAuth2Protocol_ResponseType_Code: - return new OAuth2IndirectErrorResponse($error,$return_url); - break; - default: - throw new Exception(sprintf("invalid response type %s",$response_type)); - break; + + if($request instanceof OAuth2AuthorizationRequest) + { + $response_type = $request->getResponseType(false); + + if (OAuth2Protocol::responseTypeBelongsToFlow($response_type, OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode)) + { + return new OAuth2IndirectErrorResponse + ( + $error, + $error_description, + $return_url, + $request->getState() + ); } + if + ( + OAuth2Protocol::responseTypeBelongsToFlow($response_type, OAuth2Protocol::OAuth2Protocol_GrantType_Implicit) || + OAuth2Protocol::responseTypeBelongsToFlow($response_type, OAuth2Protocol::OAuth2Protocol_GrantType_Hybrid) + ) + { + return new OAuth2IndirectFragmentErrorResponse + ( + $error, + $error_description, + $return_url, + $request->getState() + ); + } + + throw new Exception + ( + sprintf + ( + "invalid response type %s", + $request->getResponseType() + ) + ); } - return $response; } } \ No newline at end of file diff --git a/app/libs/oauth2/strategies/OAuth2ResponseStrategyFactoryMethod.php b/app/libs/oauth2/strategies/OAuth2ResponseStrategyFactoryMethod.php index 19a419c1..fc5e9178 100644 --- a/app/libs/oauth2/strategies/OAuth2ResponseStrategyFactoryMethod.php +++ b/app/libs/oauth2/strategies/OAuth2ResponseStrategyFactoryMethod.php @@ -2,22 +2,67 @@ namespace oauth2\strategies; +use oauth2\requests\OAuth2AuthenticationRequest; +use oauth2\requests\OAuth2Request; use oauth2\responses\OAuth2DirectResponse; use oauth2\responses\OAuth2IndirectFragmentResponse; use oauth2\responses\OAuth2IndirectResponse; +use oauth2\responses\OAuth2PostResponse; use oauth2\responses\OAuth2Response; +use utils\IHttpResponseStrategy; use utils\services\ServiceLocator; +use oauth2\OAuth2Protocol; /** * Class OAuth2ResponseStrategyFactoryMethod * @package oauth2\strategies */ -final class OAuth2ResponseStrategyFactoryMethod { +final class OAuth2ResponseStrategyFactoryMethod +{ - public static function buildStrategy(OAuth2Response $response) + /** + * @param OAuth2Request $request + * @param OAuth2Response $response + * @return IHttpResponseStrategy + * @throws \Exception + */ + public static function buildStrategy(OAuth2Request $request, OAuth2Response $response) { $type = $response->getType(); - switch ($type) { + + if($request instanceof OAuth2AuthenticationRequest) + { + $response_mode = $request->getResponseMode(); + + if(is_null($response_mode)) + { + $response_mode = OAuth2Protocol::getDefaultResponseMode($request->getResponseType(false)); + } + + switch($response_mode) + { + case OAuth2Protocol::OAuth2Protocol_ResponseMode_Fragment: + $type = OAuth2IndirectFragmentResponse::OAuth2IndirectFragmentResponse; + break; + case OAuth2Protocol::OAuth2Protocol_ResponseMode_Query: + $type = OAuth2IndirectResponse::OAuth2IndirectResponse; + break; + case OAuth2Protocol::OAuth2Protocol_ResponseMode_FormPost: + $type = OAuth2PostResponse::OAuth2PostResponse; + break; + case OAuth2Protocol::OAuth2Protocol_ResponseMode_Direct: + $type = OAuth2DirectResponse::OAuth2DirectResponse; + break; + } + } + + switch ($type) + { + case OAuth2PostResponse::OAuth2PostResponse: + { + return ServiceLocator::getInstance()->getService(OAuth2PostResponse::OAuth2PostResponse); + } + break; case OAuth2IndirectResponse::OAuth2IndirectResponse: { return ServiceLocator::getInstance()->getService(OAuth2IndirectResponse::OAuth2IndirectResponse); @@ -29,15 +74,14 @@ final class OAuth2ResponseStrategyFactoryMethod { return ServiceLocator::getInstance()->getService(OAuth2IndirectFragmentResponse::OAuth2IndirectFragmentResponse); } break; - case OAuth2DirectResponse::OAuth2DirectResponse: { return ServiceLocator::getInstance()->getService(OAuth2DirectResponse::OAuth2DirectResponse); } break; default: - throw new \Exception("Invalid OAuth2 response Type"); - break; + throw new \Exception(sprintf("Invalid OAuth2 response Type %s", $type)); + break; } } } \ No newline at end of file diff --git a/app/libs/openid/OpenIdMessage.php b/app/libs/openid/OpenIdMessage.php index bd9e582e..e00f8a09 100644 --- a/app/libs/openid/OpenIdMessage.php +++ b/app/libs/openid/OpenIdMessage.php @@ -5,6 +5,7 @@ namespace openid; use openid\exceptions\InvalidOpenIdMessageMode; use openid\helpers\OpenIdErrorMessages; use utils\http\HttpMessage; +use openid\requests\OpenIdMessageMemento; /** * Class OpenIdMessage @@ -14,12 +15,14 @@ use utils\http\HttpMessage; class OpenIdMessage extends HttpMessage { - public function __construct(array $values) + /** + * @param array $values + */ + public function __construct(array $values = array()) { parent::__construct($values); } - public function getMode() { return $this->getParam(OpenIdProtocol::OpenIDProtocol_Mode); @@ -32,8 +35,12 @@ class OpenIdMessage extends HttpMessage public function getParam($param) { if (isset($this->container[OpenIdProtocol::param($param, "_")])) + { return $this->container[OpenIdProtocol::param($param, "_")]; - if (isset($this->container[OpenIdProtocol::param($param, ".")])) { + } + + if (isset($this->container[OpenIdProtocol::param($param, ".")])) + { return $this->container[OpenIdProtocol::param($param, ".")]; } return null; @@ -64,4 +71,33 @@ class OpenIdMessage extends HttpMessage throw new InvalidOpenIdMessageMode(sprintf(OpenIdErrorMessages::InvalidOpenIdMessageModeMessage, $mode)); $this->container[OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_Mode)] = $mode;; } + + /** + * @return OpenIdMessageMemento + */ + public function createMemento() + { + return OpenIdMessageMemento::buildFromRequest($this); + } + + /** + * @param OpenIdMessageMemento $memento + * @return $this + */ + public function setMemento(OpenIdMessageMemento $memento) + { + $this->container = $memento->getState(); + return $this; + } + + /** + * @param OpenIdMessageMemento $memento + * @return OpenIdMessage + */ + static public function buildFromMemento(OpenIdMessageMemento $memento) + { + $msg = new self; + $msg->setMemento($memento); + return $msg; + } } \ No newline at end of file diff --git a/app/libs/openid/OpenIdProtocol.php b/app/libs/openid/OpenIdProtocol.php index 21e4268e..51bfeef4 100644 --- a/app/libs/openid/OpenIdProtocol.php +++ b/app/libs/openid/OpenIdProtocol.php @@ -2,23 +2,21 @@ namespace openid; +use openid\handlers\IOpenIdAuthenticationStrategy; use openid\handlers\OpenIdAuthenticationRequestHandler; use openid\handlers\OpenIdCheckAuthenticationRequestHandler; use openid\handlers\OpenIdSessionAssociationRequestHandler; +use openid\services\IAssociationService; +use openid\services\IMementoOpenIdSerializerService; +use openid\services\INonceService; +use openid\services\IServerConfigurationService; +use openid\services\IServerExtensionsService; +use openid\services\ITrustedSitesService; use openid\XRDS\XRDSDocumentBuilder; use openid\XRDS\XRDSService; - -//services -use utils\services\ILogService; -use openid\services\IMementoOpenIdRequestService; -use openid\handlers\IOpenIdAuthenticationStrategy; -use openid\services\IServerExtensionsService; -use openid\services\IAssociationService; -use openid\services\ITrustedSitesService; -use openid\services\IServerConfigurationService; -use openid\services\INonceService; use utils\services\IAuthService; use utils\services\ICheckPointService; +use utils\services\ILogService; use utils\services\IServerConfigurationService as IUtilsServerConfigurationService; /** @@ -124,13 +122,35 @@ class OpenIdProtocol implements IOpenIdProtocol self::OpenIdProtocol_MacKey => self::OpenIdProtocol_MacKey, ); + /** + * @var OpenIdAuthenticationRequestHandler + */ private $request_handlers; + /** + * @var IServerExtensionsService + */ private $server_extension_service; + /** + * @var IServerConfigurationService + */ private $server_config_service; + /** + * @param IAuthService $auth_service + * @param IMementoOpenIdSerializerService $memento_request_service + * @param IOpenIdAuthenticationStrategy $auth_strategy + * @param IServerExtensionsService $server_extension_service + * @param IAssociationService $association_service + * @param ITrustedSitesService $trusted_sites_service + * @param IServerConfigurationService $server_config_service + * @param INonceService $nonce_service + * @param ILogService $log_service + * @param ICheckPointService $checkpoint_service + * @param IUtilsServerConfigurationService $utils_configuration_service + */ public function __construct( IAuthService $auth_service, - IMementoOpenIdRequestService $memento_request_service, + IMementoOpenIdSerializerService $memento_request_service, IOpenIdAuthenticationStrategy $auth_strategy, IServerExtensionsService $server_extension_service, IAssociationService $association_service, diff --git a/app/libs/openid/OpenIdServiceProvider.php b/app/libs/openid/OpenIdServiceProvider.php index c9a4e612..7b6229ab 100644 --- a/app/libs/openid/OpenIdServiceProvider.php +++ b/app/libs/openid/OpenIdServiceProvider.php @@ -30,9 +30,15 @@ class OpenIdServiceProvider extends ServiceProvider { $auth_extension_service = App::make('auth\\IAuthenticationExtensionService'); if(!is_null($auth_extension_service)){ - $memento_service = App::make(OpenIdServiceCatalog::MementoService); + $memento_service = App::make(OpenIdServiceCatalog::MementoSerializerService); $server_configuration_service = App::make(UtilsServiceCatalog::ServerConfigurationService); - $auth_extension_service->addExtension(new OpenIdAuthenticationExtension($memento_service,$server_configuration_service)); + + $auth_extension_service->addExtension( + new OpenIdAuthenticationExtension( + $memento_service, + $server_configuration_service + ) + ); } } diff --git a/app/libs/openid/extensions/OpenIdAuthenticationExtension.php b/app/libs/openid/extensions/OpenIdAuthenticationExtension.php index 7320f9f6..b47dacfd 100644 --- a/app/libs/openid/extensions/OpenIdAuthenticationExtension.php +++ b/app/libs/openid/extensions/OpenIdAuthenticationExtension.php @@ -6,9 +6,10 @@ use auth\exceptions\AuthenticationException; use auth\IAuthenticationExtension; use auth\User; use openid\helpers\OpenIdErrorMessages; +use openid\OpenIdMessage; use openid\requests\OpenIdAuthenticationRequest; +use openid\services\IMementoOpenIdSerializerService; use openid\services\IServerConfigurationService; -use openid\services\IMementoOpenIdRequestService; use Log; /** @@ -17,26 +18,41 @@ use Log; */ class OpenIdAuthenticationExtension implements IAuthenticationExtension { + /** + * @var IMementoOpenIdSerializerService + */ private $memento_service; + /** + * @var IServerConfigurationService + */ private $server_configuration; /** - * @param IMementoOpenIdRequestService $memento_service + * @param IMementoOpenIdSerializerService $memento_service * @param IServerConfigurationService $server_configuration */ - public function __construct(IMementoOpenIdRequestService $memento_service, IServerConfigurationService $server_configuration){ + public function __construct( + IMementoOpenIdSerializerService $memento_service, + IServerConfigurationService $server_configuration + ) + { $this->server_configuration = $server_configuration; $this->memento_service = $memento_service; } public function process(User $user) { + if(!$this->memento_service->exists()) return; + //check if we have a current openid message - $msg = $this->memento_service->getCurrentRequest(); - if (!is_null($msg) && $msg->isValid() && OpenIdAuthenticationRequest::IsOpenIdAuthenticationRequest($msg)) { + $msg = OpenIdMessage::buildFromMemento($this->memento_service->load()); + + if (!is_null($msg) && $msg->isValid() && OpenIdAuthenticationRequest::IsOpenIdAuthenticationRequest($msg)) + { //check if current user is has the same identity that the one claimed on openid message $auth_request = new OpenIdAuthenticationRequest($msg); - if (!$auth_request->isIdentitySelectByOP()) { + if (!$auth_request->isIdentitySelectByOP()) + { $claimed_id = $auth_request->getClaimedId(); $identity = $auth_request->getIdentity(); $current_identity = $this->server_configuration->getUserIdentityEndpointURL($user->getIdentifier()); @@ -44,7 +60,15 @@ class OpenIdAuthenticationExtension implements IAuthenticationExtension //if not return fail ( we cant log in with a different user that the one stated on the authentication message! if ($claimed_id !== $current_identity && $identity !== $current_identity) { Log::warning(sprintf(OpenIdErrorMessages::AlreadyExistSessionMessage, $current_identity, $identity)); - throw new AuthenticationException(sprintf(OpenIdErrorMessages::AlreadyExistSessionMessage, $current_identity, $identity)); + throw new AuthenticationException + ( + sprintf + ( + OpenIdErrorMessages::AlreadyExistSessionMessage, + $current_identity, + $identity + ) + ); } } diff --git a/app/libs/openid/handlers/OpenIdAuthenticationRequestHandler.php b/app/libs/openid/handlers/OpenIdAuthenticationRequestHandler.php index be55cca1..8b7ab4a1 100644 --- a/app/libs/openid/handlers/OpenIdAuthenticationRequestHandler.php +++ b/app/libs/openid/handlers/OpenIdAuthenticationRequestHandler.php @@ -8,6 +8,7 @@ use openid\exceptions\InvalidOpenIdAuthenticationRequestMode; use openid\exceptions\InvalidOpenIdMessageException; use openid\exceptions\OpenIdInvalidRealmException; use openid\exceptions\ReplayAttackException; +use openid\helpers\AssociationFactory; use openid\helpers\OpenIdErrorMessages; use openid\helpers\OpenIdSignatureBuilder; use openid\model\IAssociation; @@ -21,15 +22,14 @@ use openid\responses\OpenIdIndirectGenericErrorResponse; use openid\responses\OpenIdNonImmediateNegativeAssertion; use openid\responses\OpenIdPositiveAssertionResponse; use openid\services\IAssociationService; -use openid\services\IMementoOpenIdRequestService; +use openid\services\IMementoOpenIdSerializerService; use openid\services\INonceService; use openid\services\IServerConfigurationService; use openid\services\IServerExtensionsService; use openid\services\ITrustedSitesService; -use openid\helpers\AssociationFactory; use utils\services\IAuthService; -use utils\services\ILogService; use utils\services\ICheckPointService; +use utils\services\ILogService; /** * Class OpenIdAuthenticationRequestHandler @@ -40,39 +40,84 @@ use utils\services\ICheckPointService; */ final class OpenIdAuthenticationRequestHandler extends OpenIdMessageHandler { + /** + * @var IAuthService + */ private $auth_service; + /** + * @var IMementoOpenIdSerializerService + */ private $memento_service; + /** + * @var IOpenIdAuthenticationStrategy + */ private $auth_strategy; + /** + * @var IServerExtensionsService + */ private $server_extensions_service; + /** + * @var IAssociationService + */ private $association_service; + /** + * @var ITrustedSitesService + */ private $trusted_sites_service; + /** + * @var IServerConfigurationService + */ private $server_configuration_service; + /** + * @var + */ private $extensions; + /** + * @var + */ private $current_request_context; + /** + * @var INonceService + */ private $nonce_service; - public function __construct(IAuthService $authService, - IMementoOpenIdRequestService $memento_service, - IOpenIdAuthenticationStrategy $auth_strategy, - IServerExtensionsService $server_extensions_service, - IAssociationService $association_service, - ITrustedSitesService $trusted_sites_service, - IServerConfigurationService $server_configuration_service, - INonceService $nonce_service, - ILogService $log, - ICheckPointService $checkpoint_service, - $successor) - { - parent::__construct($successor, $log,$checkpoint_service); - $this->auth_service = $authService; - $this->memento_service = $memento_service; - $this->auth_strategy = $auth_strategy; - $this->server_extensions_service = $server_extensions_service; - $this->association_service = $association_service; - $this->trusted_sites_service = $trusted_sites_service; + /** + * @param IAuthService $authService + * @param IMementoOpenIdSerializerService $memento_service + * @param IOpenIdAuthenticationStrategy $auth_strategy + * @param IServerExtensionsService $server_extensions_service + * @param IAssociationService $association_service + * @param ITrustedSitesService $trusted_sites_service + * @param IServerConfigurationService $server_configuration_service + * @param INonceService $nonce_service + * @param ILogService $log + * @param ICheckPointService $checkpoint_service + * @param $successor + */ + public function __construct( + IAuthService $authService, + IMementoOpenIdSerializerService $memento_service, + IOpenIdAuthenticationStrategy $auth_strategy, + IServerExtensionsService $server_extensions_service, + IAssociationService $association_service, + ITrustedSitesService $trusted_sites_service, + IServerConfigurationService $server_configuration_service, + INonceService $nonce_service, + ILogService $log, + ICheckPointService $checkpoint_service, + $successor + ) { + parent::__construct($successor, $log, $checkpoint_service); + + $this->auth_service = $authService; + $this->memento_service = $memento_service; + $this->auth_strategy = $auth_strategy; + $this->server_extensions_service = $server_extensions_service; + $this->association_service = $association_service; + $this->trusted_sites_service = $trusted_sites_service; $this->server_configuration_service = $server_configuration_service; - $this->extensions = $this->server_extensions_service->getAllActiveExtensions(); - $this->nonce_service = $nonce_service; + $this->extensions = $this->server_extensions_service->getAllActiveExtensions(); + $this->nonce_service = $nonce_service; } /** @@ -83,11 +128,15 @@ final class OpenIdAuthenticationRequestHandler extends OpenIdMessageHandler protected function internalHandle(OpenIdMessage $message) { $this->current_request = null; + try { - $this->current_request = new OpenIdAuthenticationRequest($message,$this->server_configuration_service->getUserIdentityEndpointURL('@identifier')); + $this->current_request = new OpenIdAuthenticationRequest( + $message, + $this->server_configuration_service->getUserIdentityEndpointURL('@identifier') + ); - if (!$this->current_request->isValid()){ + if (!$this->current_request->isValid()) { throw new InvalidOpenIdMessageException(OpenIdErrorMessages::InvalidOpenIdAuthenticationRequestMessage); } @@ -95,49 +144,61 @@ final class OpenIdAuthenticationRequestHandler extends OpenIdMessageHandler $mode = $this->current_request->getMode(); switch ($mode) { - case OpenIdProtocol::SetupMode: - { + case OpenIdProtocol::SetupMode: { return $this->doSetupMode(); } break; - case OpenIdProtocol::ImmediateMode: - { + case OpenIdProtocol::ImmediateMode: { return $this->doImmediateMode(); } break; default: - throw new InvalidOpenIdAuthenticationRequestMode(sprintf(OpenIdErrorMessages::InvalidAuthenticationRequestModeMessage, $mode)); + throw new InvalidOpenIdAuthenticationRequestMode(sprintf(OpenIdErrorMessages::InvalidAuthenticationRequestModeMessage, + $mode)); break; } } catch (InvalidAssociationTypeException $inv_assoc_type) { $this->checkpoint_service->trackException($inv_assoc_type); $this->log_service->warning($inv_assoc_type); - if(!is_null($this->current_request)) - $this->log_service->error_msg("current request: ".$this->current_request->toString()); - return new OpenIdIndirectGenericErrorResponse($inv_assoc_type->getMessage(), null, null, $this->current_request); + if (!is_null($this->current_request)) { + $this->log_service->error_msg("current request: " . $this->current_request->toString()); + } + + return new OpenIdIndirectGenericErrorResponse($inv_assoc_type->getMessage(), null, null, + $this->current_request); } catch (OpenIdInvalidRealmException $inv_realm_ex) { $this->checkpoint_service->trackException($inv_realm_ex); $this->log_service->error($inv_realm_ex); - if(!is_null($this->current_request)) - $this->log_service->error_msg("current request: ".$this->current_request->toString()); - return new OpenIdIndirectGenericErrorResponse($inv_realm_ex->getMessage(), null, null, $this->current_request); + if (!is_null($this->current_request)) { + $this->log_service->error_msg("current request: " . $this->current_request->toString()); + } + + return new OpenIdIndirectGenericErrorResponse($inv_realm_ex->getMessage(), null, null, + $this->current_request); } catch (ReplayAttackException $replay_ex) { $this->checkpoint_service->trackException($replay_ex); $this->log_service->error($replay_ex); - if(!is_null($this->current_request)) - $this->log_service->error_msg("current request: ".$this->current_request->toString()); + if (!is_null($this->current_request)) { + $this->log_service->error_msg("current request: " . $this->current_request->toString()); + } + return new OpenIdIndirectGenericErrorResponse($replay_ex->getMessage(), null, null, $this->current_request); } catch (InvalidOpenIdMessageException $inv_msg_ex) { $this->checkpoint_service->trackException($inv_msg_ex); $this->log_service->error($inv_msg_ex); - if(!is_null($this->current_request)) - $this->log_service->error_msg("current request: ".$this->current_request->toString()); - return new OpenIdIndirectGenericErrorResponse($inv_msg_ex->getMessage(), null, null, $this->current_request); + if (!is_null($this->current_request)) { + $this->log_service->error_msg("current request: " . $this->current_request->toString()); + } + + return new OpenIdIndirectGenericErrorResponse($inv_msg_ex->getMessage(), null, null, + $this->current_request); } catch (Exception $ex) { $this->checkpoint_service->trackException($ex); $this->log_service->error($ex); - if(!is_null($this->current_request)) - $this->log_service->error_msg("current request: ".$this->current_request->toString()); + if (!is_null($this->current_request)) { + $this->log_service->error_msg("current request: " . $this->current_request->toString()); + } + return new OpenIdIndirectGenericErrorResponse("Server Error", null, null, $this->current_request); } } @@ -149,43 +210,49 @@ final class OpenIdAuthenticationRequestHandler extends OpenIdMessageHandler private function doSetupMode() { - $authentication_response = $this->auth_service->getUserAuthenticationResponse(); - if($authentication_response == IAuthService::AuthenticationResponse_Cancel){ - //clear saved data ... - $this->memento_service->clearCurrentRequest(); - $this->auth_service->clearUserAuthenticationResponse(); - $this->auth_service->clearUserAuthorizationResponse(); - return new OpenIdNonImmediateNegativeAssertion($this->current_request->getReturnTo()); - } + $authentication_response = $this->auth_service->getUserAuthenticationResponse(); + if ($authentication_response == IAuthService::AuthenticationResponse_Cancel) { + //clear saved data ... + $this->memento_service->forget(); + $this->auth_service->clearUserAuthenticationResponse(); + $this->auth_service->clearUserAuthorizationResponse(); - if (!$this->auth_service->isUserLogged()) + return new OpenIdNonImmediateNegativeAssertion($this->current_request->getReturnTo()); + } + + if (!$this->auth_service->isUserLogged()) { return $this->doLogin(); + } //user already logged $currentUser = $this->auth_service->getCurrentUser(); if (!$this->current_request->isIdentitySelectByOP()) { - $current_claimed_id = $this->current_request->getClaimedId(); - $current_identity = $this->current_request->getIdentity(); - // check is claimed identity match with current one - // if not logs out and do re login - $current_user = $this->auth_service->getCurrentUser(); - if (is_null($current_user)) - throw new Exception("User not set!"); + $current_claimed_id = $this->current_request->getClaimedId(); + $current_identity = $this->current_request->getIdentity(); + // check is claimed identity match with current one + // if not logs out and do re login + $current_user = $this->auth_service->getCurrentUser(); + if (is_null($current_user)) { + throw new Exception("User not set!"); + } $current_owned_identity = $this->server_configuration_service->getUserIdentityEndpointURL($current_user->getIdentifier()); if ($current_claimed_id != $current_owned_identity && $current_identity != $current_owned_identity) { - $this->log_service->warning_msg(sprintf(OpenIdErrorMessages::AlreadyExistSessionMessage, $current_owned_identity, $current_identity)); + $this->log_service->warning_msg(sprintf(OpenIdErrorMessages::AlreadyExistSessionMessage, + $current_owned_identity, $current_identity)); $this->auth_service->logout(); + return $this->doLogin(); } } $authorization_response = $this->auth_service->getUserAuthorizationResponse(); - if ($authorization_response !== IAuthService::AuthorizationResponse_None) + if ($authorization_response !== IAuthService::AuthorizationResponse_None) { return $this->checkAuthorizationResponse($authorization_response); + } // $authorization_response is none ... $this->current_request_context->cleanTrustedData(); @@ -195,27 +262,34 @@ final class OpenIdAuthenticationRequestHandler extends OpenIdMessageHandler } $requested_data = $this->current_request_context->getTrustedData(); - $sites = $this->trusted_sites_service->getTrustedSites($currentUser, $this->current_request->getRealm(), $requested_data); + $sites = $this->trusted_sites_service->getTrustedSites( + $currentUser, + $this->current_request->getRealm(), + $requested_data + ); + //check trusted sites - if (is_null($sites) || count($sites) == 0) + if (is_null($sites) || count($sites) == 0) { return $this->doConsentProcess(); + } //there are trusted sites ... check the former authorization decision - $site = $sites[0]; + $site = $sites[0]; $policy = $site->getAuthorizationPolicy(); switch ($policy) { - case IAuthService::AuthorizationResponse_AllowForever: - { - //save former user choice on session - $this->auth_service->setUserAuthorizationResponse($policy); - return $this->doAssertion(); - } + case IAuthService::AuthorizationResponse_AllowForever: { + //save former user choice on session + $this->auth_service->setUserAuthorizationResponse($policy); + + return $this->doAssertion(); + } break; - case IAuthService::AuthorizationResponse_DenyForever: - // black listed site - return new OpenIdIndirectGenericErrorResponse(sprintf(OpenIdErrorMessages::RealmNotAllowedByUserMessage, $site->getRealm()), null, null, $this->current_request); + case IAuthService::AuthorizationResponse_DenyForever: + // black listed site + return new OpenIdIndirectGenericErrorResponse(sprintf(OpenIdErrorMessages::RealmNotAllowedByUserMessage, + $site->getRealm()), null, null, $this->current_request); break; - default: - throw new Exception("Invalid Realm Policy"); + default: + throw new Exception("Invalid Realm Policy"); break; } } @@ -229,7 +303,9 @@ final class OpenIdAuthenticationRequestHandler extends OpenIdMessageHandler foreach ($this->extensions as $ext) { $ext->parseRequest($this->current_request, $this->current_request_context); } - $this->memento_service->saveCurrentRequest(); + + $this->memento_service->serialize($this->current_request->getMessage()->createMemento()); + return $this->auth_strategy->doLogin($this->current_request, $this->current_request_context); } @@ -256,11 +332,12 @@ final class OpenIdAuthenticationRequestHandler extends OpenIdMessageHandler $context->addSignParam(OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_Identity)); $op_endpoint = $this->server_configuration_service->getOPEndpointURL(); - $identity = $this->server_configuration_service->getUserIdentityEndpointURL($currentUser->getIdentifier()); - $nonce = $this->nonce_service->generateNonce(); - $realm = $this->current_request->getRealm(); + $identity = $this->server_configuration_service->getUserIdentityEndpointURL($currentUser->getIdentifier()); + $nonce = $this->nonce_service->generateNonce(); + $realm = $this->current_request->getRealm(); - $response = new OpenIdPositiveAssertionResponse($op_endpoint, $identity, $identity, $this->current_request->getReturnTo(), $nonce->getRawFormat(), $realm); + $response = new OpenIdPositiveAssertionResponse($op_endpoint, $identity, $identity, + $this->current_request->getReturnTo(), $nonce->getRawFormat(), $realm); foreach ($this->extensions as $ext) { $ext->prepareResponse($this->current_request, $response, $context); @@ -270,14 +347,16 @@ final class OpenIdAuthenticationRequestHandler extends OpenIdMessageHandler if (is_null($assoc_handle = $this->current_request->getAssocHandle()) || is_null($association = $this->association_service->getAssociation($assoc_handle))) { //create private association ... - $association = $this->association_service->addAssociation(AssociationFactory::getInstance()->buildPrivateAssociation($realm,$this->server_configuration_service->getConfigValue("Private.Association.Lifetime"))); + $association = $this->association_service->addAssociation(AssociationFactory::getInstance()->buildPrivateAssociation($realm, + $this->server_configuration_service->getConfigValue("Private.Association.Lifetime"))); $response->setAssocHandle($association->getHandle()); if (!empty($assoc_handle)) { $response->setInvalidateHandle($assoc_handle); } } else { - if ($association->getType() != IAssociation::TypeSession) + if ($association->getType() != IAssociation::TypeSession) { throw new InvalidAssociationTypeException(OpenIdErrorMessages::InvalidAssociationTypeMessage); + } $response->setAssocHandle($assoc_handle); } @@ -291,8 +370,9 @@ final class OpenIdAuthenticationRequestHandler extends OpenIdMessageHandler */ $this->nonce_service->associateNonce($nonce, $response->getSig(), $realm); //do cleaning ... - $this->memento_service->clearCurrentRequest(); + $this->memento_service->forget(); $this->auth_service->clearUserAuthorizationResponse(); + return $response; } @@ -302,10 +382,12 @@ final class OpenIdAuthenticationRequestHandler extends OpenIdMessageHandler private function doConsentProcess() { //do consent process - $this->memento_service->saveCurrentRequest(); + $this->memento_service->serialize($this->current_request->getMessage()->createMemento()); + foreach ($this->extensions as $ext) { $ext->parseRequest($this->current_request, $this->current_request_context); } + return $this->auth_strategy->doConsent($this->current_request, $this->current_request_context); } @@ -319,8 +401,7 @@ final class OpenIdAuthenticationRequestHandler extends OpenIdMessageHandler // check response $currentUser = $this->auth_service->getCurrentUser(); switch ($authorization_response) { - case IAuthService::AuthorizationResponse_AllowForever: - { + case IAuthService::AuthorizationResponse_AllowForever: { $this->current_request_context->cleanTrustedData(); foreach ($this->extensions as $ext) { @@ -328,22 +409,23 @@ final class OpenIdAuthenticationRequestHandler extends OpenIdMessageHandler $this->current_request_context->setTrustedData($data); } - $this->trusted_sites_service->addTrustedSite($currentUser, $this->current_request->getRealm(), IAuthService::AuthorizationResponse_AllowForever, $this->current_request_context->getTrustedData()); + $this->trusted_sites_service->addTrustedSite($currentUser, $this->current_request->getRealm(), + IAuthService::AuthorizationResponse_AllowForever, $this->current_request_context->getTrustedData()); + return $this->doAssertion(); } break; case IAuthService::AuthorizationResponse_AllowOnce: return $this->doAssertion(); break; - case IAuthService::AuthorizationResponse_DenyOnce: - { - $this->memento_service->clearCurrentRequest(); + case IAuthService::AuthorizationResponse_DenyOnce: { + $this->memento_service->forget(); $this->auth_service->clearUserAuthorizationResponse(); + return new OpenIdNonImmediateNegativeAssertion($this->current_request->getReturnTo()); } - break; - case IAuthService::AuthorizationResponse_DenyForever: - { + break; + case IAuthService::AuthorizationResponse_DenyForever: { $this->current_request_context->cleanTrustedData(); foreach ($this->extensions as $ext) { @@ -351,15 +433,16 @@ final class OpenIdAuthenticationRequestHandler extends OpenIdMessageHandler $this->current_request_context->setTrustedData($data); } - $this->trusted_sites_service->addTrustedSite($currentUser, $this->current_request->getRealm(), IAuthService::AuthorizationResponse_DenyForever,$this->current_request_context->getTrustedData()); - $this->memento_service->clearCurrentRequest(); + $this->trusted_sites_service->addTrustedSite($currentUser, $this->current_request->getRealm(), + IAuthService::AuthorizationResponse_DenyForever, $this->current_request_context->getTrustedData()); + $this->memento_service->forget(); $this->auth_service->clearUserAuthorizationResponse(); return new OpenIdNonImmediateNegativeAssertion($this->current_request->getReturnTo()); } break; default: - $this->memento_service->clearCurrentRequest(); + $this->memento_service->forget(); $this->auth_service->clearUserAuthorizationResponse(); throw new \Exception("Invalid Authorization response!"); break; @@ -385,31 +468,33 @@ final class OpenIdAuthenticationRequestHandler extends OpenIdMessageHandler $requested_data = $this->current_request_context->getTrustedData(); - $sites = $this->trusted_sites_service->getTrustedSites($currentUser, $this->current_request->getRealm(), $requested_data); + $sites = $this->trusted_sites_service->getTrustedSites($currentUser, $this->current_request->getRealm(), + $requested_data); if (is_null($sites) || count($sites) == 0) { //need setup to continue return new OpenIdImmediateNegativeAssertion($this->current_request->getReturnTo()); } - $site = $sites[0]; + $site = $sites[0]; $policy = $site->getAuthorizationPolicy(); switch ($policy) { - case IAuthService::AuthorizationResponse_DenyForever: - { + case IAuthService::AuthorizationResponse_DenyForever: { // black listed site by user - return new OpenIdIndirectGenericErrorResponse(sprintf(OpenIdErrorMessages::RealmNotAllowedByUserMessage, $site->getRealm()), null, null, $this->current_request); + return new OpenIdIndirectGenericErrorResponse(sprintf(OpenIdErrorMessages::RealmNotAllowedByUserMessage, + $site->getRealm()), null, null, $this->current_request); } break; - case IAuthService::AuthorizationResponse_AllowForever: - { + case IAuthService::AuthorizationResponse_AllowForever: { //save former user choice on session $this->auth_service->setUserAuthorizationResponse($policy); + return $this->doAssertion(); } - break; + break; default: - return new OpenIdIndirectGenericErrorResponse(sprintf(OpenIdErrorMessages::RealmNotAllowedByUserMessage, $this->current_request->getRealm()), null, null, $this->current_request); + return new OpenIdIndirectGenericErrorResponse(sprintf(OpenIdErrorMessages::RealmNotAllowedByUserMessage, + $this->current_request->getRealm()), null, null, $this->current_request); break; } } @@ -421,6 +506,7 @@ final class OpenIdAuthenticationRequestHandler extends OpenIdMessageHandler protected function canHandle(OpenIdMessage $message) { $res = OpenIdAuthenticationRequest::IsOpenIdAuthenticationRequest($message); + return $res; } } \ No newline at end of file diff --git a/app/libs/openid/handlers/OpenIdMessageHandler.php b/app/libs/openid/handlers/OpenIdMessageHandler.php index 8a808ccd..6b591af8 100644 --- a/app/libs/openid/handlers/OpenIdMessageHandler.php +++ b/app/libs/openid/handlers/OpenIdMessageHandler.php @@ -7,6 +7,7 @@ use openid\OpenIdMessage; use utils\services\ILogService; use utils\services\ICheckPointService; use openid\exceptions\InvalidOpenIdMessageException; +use openid\requests\OpenIdAuthenticationRequest; /** * Class OpenIdMessageHandler @@ -22,7 +23,7 @@ abstract class OpenIdMessageHandler */ protected $successor; /** - * @var OpenIdMessage + * @var OpenIdAuthenticationRequest */ protected $current_request; /** diff --git a/app/libs/openid/model/IOpenIdUser.php b/app/libs/openid/model/IOpenIdUser.php index cedda1e2..b78920e9 100644 --- a/app/libs/openid/model/IOpenIdUser.php +++ b/app/libs/openid/model/IOpenIdUser.php @@ -6,7 +6,8 @@ namespace openid\model; * Interface IOpenIdUser * @package openid\model */ -interface IOpenIdUser { +interface IOpenIdUser +{ /** * */ @@ -17,28 +18,100 @@ interface IOpenIdUser { */ public function isOpenstackIdAdmin(); + /** + * @return int + */ public function getId(); + + /** + * @return string + */ public function getIdentifier(); + + /** + * @return string + */ public function getEmail(); + + /** + * @return string + */ public function getFirstName(); + + /** + * @return string + */ public function getLastName(); + + /** + * @return string + */ public function getFullName(); + + /** + * @return string + */ public function getNickName(); + + /** + * @return string + */ public function getGender(); + + /** + * @return string + */ public function getCountry(); + + /** + * @return string + */ public function getStreetAddress(); + + /** + * @return string + */ public function getRegion(); + + /** + * @return string + */ public function getLocality(); + + /** + * @return string + */ public function getPostalCode(); + + /** + * @return string + */ + public function getFormattedAddress(); + public function getLanguage(); + public function getTimeZone(); + public function getDateOfBirth(); + public function getShowProfileFullName(); + public function getShowProfilePic(); + public function getShowProfileBio(); + public function getShowProfileEmail(); + public function getBio(); + public function getPic(); + public function getActions(); - public function getTrustedSites(); + + public function getTrustedSites(); + + /** + * @return int + */ + public function getExternalIdentifier(); } \ No newline at end of file diff --git a/app/libs/openid/requests/OpenIdAuthenticationRequest.php b/app/libs/openid/requests/OpenIdAuthenticationRequest.php index 70ecf5b9..92f2c759 100644 --- a/app/libs/openid/requests/OpenIdAuthenticationRequest.php +++ b/app/libs/openid/requests/OpenIdAuthenticationRequest.php @@ -14,81 +14,97 @@ use openid\OpenIdProtocol; class OpenIdAuthenticationRequest extends OpenIdRequest { - private $user_identity_endpoint; + /** + * @var null|string + */ + private $user_identity_endpoint; - /** - * @param OpenIdMessage $message - * @param string|null $user_identity_endpoint - * @throws InvalidOpenIdMessageException - */ - public function __construct(OpenIdMessage $message, $user_identity_endpoint = null) + /** + * @param OpenIdMessage $message + * @param string|null $user_identity_endpoint + * @throws InvalidOpenIdMessageException + */ + public function __construct(OpenIdMessage $message, $user_identity_endpoint = null) { parent::__construct($message); - $this->user_identity_endpoint = $user_identity_endpoint; - if(!empty($this->user_identity_endpoint)){ - if(!str_contains($this->user_identity_endpoint,'@identifier')){ - throw new InvalidOpenIdMessageException("user_identity_endpoint value must contain @identifier placeholder!."); - } - } + $this->user_identity_endpoint = $user_identity_endpoint; + if (!empty($this->user_identity_endpoint)) { + if (!str_contains($this->user_identity_endpoint, '@identifier')) { + throw new InvalidOpenIdMessageException("user_identity_endpoint value must contain @identifier placeholder!."); + } + } } - /** - * @param OpenIdMessage $message - * @return bool - */ - public static function IsOpenIdAuthenticationRequest(OpenIdMessage $message) + /** + * @param OpenIdMessage $message + * @return bool + */ + public static function IsOpenIdAuthenticationRequest(OpenIdMessage $message) { $mode = $message->getMode(); - if ($mode == OpenIdProtocol::ImmediateMode || $mode == OpenIdProtocol::SetupMode) return true; + if ($mode == OpenIdProtocol::ImmediateMode || $mode == OpenIdProtocol::SetupMode) { + return true; + } + return false; } - /** - * @return string - */ - public function getAssocHandle() + /** + * @return string + */ + public function getAssocHandle() { return $this->getParam(OpenIdProtocol::OpenIDProtocol_AssocHandle); } - /** - * @return bool - * @throws InvalidOpenIdMessageException - */ - public function isValid() + /** + * @return bool + * @throws InvalidOpenIdMessageException + */ + public function isValid() { - $return_to = $this->getReturnTo(); - $claimed_id = $this->getClaimedId(); - $identity = $this->getIdentity(); - $mode = $this->getMode(); - $realm = $this->getRealm(); - $valid_id = $this->isValidIdentifier($claimed_id, $identity); - $valid_realm = OpenIdUriHelper::checkRealm($realm, $return_to); + $return_to = $this->getReturnTo(); + $claimed_id = $this->getClaimedId(); + $identity = $this->getIdentity(); + $mode = $this->getMode(); + $realm = $this->getRealm(); + $valid_id = $this->isValidIdentifier($claimed_id, $identity); + $valid_realm = OpenIdUriHelper::checkRealm($realm, $return_to); - if(empty($return_to)) - throw new InvalidOpenIdMessageException('return_to is empty.'); + if (empty($return_to)) { + throw new InvalidOpenIdMessageException('return_to is empty.'); + } - if(empty($realm)) - throw new InvalidOpenIdMessageException('realm is empty.'); + if (empty($realm)) { + throw new InvalidOpenIdMessageException('realm is empty.'); + } - if(!$valid_realm) - throw new InvalidOpenIdMessageException(sprintf('realm check is not valid realm %s - return_to %s.',$realm,$return_to)); + if (!$valid_realm) { + throw new InvalidOpenIdMessageException(sprintf('realm check is not valid realm %s - return_to %s.', $realm, + $return_to)); + } - if(empty($claimed_id)) - throw new InvalidOpenIdMessageException('claimed_id is empty.'); + if (empty($claimed_id)) { + throw new InvalidOpenIdMessageException('claimed_id is empty.'); + } - if(empty($identity)) - throw new InvalidOpenIdMessageException('identity is empty.'); + if (empty($identity)) { + throw new InvalidOpenIdMessageException('identity is empty.'); + } - if(!$valid_id) - throw new InvalidOpenIdMessageException(sprintf('identity check is not valid claimed_id %s - identity %s.',$claimed_id,$identity)); + if (!$valid_id) { + throw new InvalidOpenIdMessageException(sprintf('identity check is not valid claimed_id %s - identity %s.', + $claimed_id, $identity)); + } - if(empty($mode)) - throw new InvalidOpenIdMessageException('mode is empty.'); + if (empty($mode)) { + throw new InvalidOpenIdMessageException('mode is empty.'); + } - if(!($mode == OpenIdProtocol::ImmediateMode || $mode == OpenIdProtocol::SetupMode)) - throw new InvalidOpenIdMessageException(sprintf('mode %s is invalid.',$mode)); + if (!($mode == OpenIdProtocol::ImmediateMode || $mode == OpenIdProtocol::SetupMode)) { + throw new InvalidOpenIdMessageException(sprintf('mode %s is invalid.', $mode)); + } return true; } @@ -96,6 +112,7 @@ class OpenIdAuthenticationRequest extends OpenIdRequest public function getReturnTo() { $return_to = $this->getParam(OpenIdProtocol::OpenIDProtocol_ReturnTo); + return (OpenIdUriHelper::checkReturnTo($return_to)) ? $return_to : ""; } @@ -112,25 +129,29 @@ class OpenIdAuthenticationRequest extends OpenIdRequest public function getRealm() { $realm = $this->getParam(OpenIdProtocol::OpenIDProtocol_Realm); + return $realm; } - public function isIdentitySelectByOP(){ + public function isIdentitySelectByOP() + { $claimed_id = $this->getClaimedId(); - $identity = $this->getIdentity(); + $identity = $this->getIdentity(); //http://specs.openid.net/auth/2.0/identifier_select - if ($claimed_id == $identity && $identity == OpenIdProtocol::IdentifierSelectType) + if ($claimed_id == $identity && $identity == OpenIdProtocol::IdentifierSelectType) { return true; + } + return false; } - /** - * @param $claimed_id - * @param $identity - * @return bool - * @throws \openid\exceptions\InvalidOpenIdMessageException - */ - private function isValidIdentifier($claimed_id, $identity) + /** + * @param $claimed_id + * @param $identity + * @return bool + * @throws \openid\exceptions\InvalidOpenIdMessageException + */ + private function isValidIdentifier($claimed_id, $identity) { /* * openid.claimed_id" and "openid.identity" SHALL be either both present or both absent. @@ -138,24 +159,30 @@ class OpenIdAuthenticationRequest extends OpenIdRequest * other information in its payload, using extensions. */ - if(empty($this->user_identity_endpoint)) - throw new InvalidOpenIdMessageException("user_identity_endpoint is not set."); + if (empty($this->user_identity_endpoint)) { + throw new InvalidOpenIdMessageException("user_identity_endpoint is not set."); + } - if (is_null($claimed_id) && is_null($identity)) + if (is_null($claimed_id) && is_null($identity)) { return false; + } //http://specs.openid.net/auth/2.0/identifier_select - if ($claimed_id == $identity && $identity == OpenIdProtocol::IdentifierSelectType) + if ($claimed_id == $identity && $identity == OpenIdProtocol::IdentifierSelectType) { return true; + } if (OpenIdUriHelper::isValidUrl($claimed_id) && OpenIdUriHelper::isValidUrl($identity)) { $identity_url_pattern = $this->user_identity_endpoint; $url_parts = explode("@", $identity_url_pattern, 2); $base_identity_url = $url_parts[0]; - if (strpos($identity, $base_identity_url) !== false) + if (strpos($identity, $base_identity_url) !== false) { return true; - if (strpos($claimed_id, $base_identity_url) !== false) + } + if (strpos($claimed_id, $base_identity_url) !== false) { return true; + } } + return false; } diff --git a/app/libs/openid/requests/OpenIdCheckAuthenticationRequest.php b/app/libs/openid/requests/OpenIdCheckAuthenticationRequest.php index d65ec81f..2f26cc1b 100644 --- a/app/libs/openid/requests/OpenIdCheckAuthenticationRequest.php +++ b/app/libs/openid/requests/OpenIdCheckAuthenticationRequest.php @@ -13,57 +13,61 @@ use openid\OpenIdProtocol; class OpenIdCheckAuthenticationRequest extends OpenIdAuthenticationRequest { - private $op_endpoint_url; + private $op_endpoint_url; - /** - * @param OpenIdMessage $message - * @param $op_endpoint_url - */ - public function __construct(OpenIdMessage $message, $op_endpoint_url) + /** + * @param OpenIdMessage $message + * @param $op_endpoint_url + */ + public function __construct(OpenIdMessage $message, $op_endpoint_url) { parent::__construct($message); - $this->op_endpoint_url = $op_endpoint_url; + $this->op_endpoint_url = $op_endpoint_url; } public static function IsOpenIdCheckAuthenticationRequest(OpenIdMessage $message) { $mode = $message->getMode(); - if ($mode == OpenIdProtocol::CheckAuthenticationMode) return true; + if ($mode == OpenIdProtocol::CheckAuthenticationMode) { + return true; + } + return false; } public function isValid() { - $mode = $this->getMode(); - $claimed_assoc = $this->getAssocHandle(); - $claimed_nonce = $this->getNonce(); - $claimed_sig = $this->getSig(); + $mode = $this->getMode(); + $claimed_assoc = $this->getAssocHandle(); + $claimed_nonce = $this->getNonce(); + $claimed_sig = $this->getSig(); $claimed_op_endpoint = $this->getOPEndpoint(); - $claimed_identity = $this->getClaimedId(); - $claimed_realm = $this->getRealm(); - $claimed_returnTo = $this->getReturnTo(); - $signed = $this->getSigned(); + $claimed_identity = $this->getClaimedId(); + $claimed_realm = $this->getRealm(); + $claimed_returnTo = $this->getReturnTo(); + $signed = $this->getSigned(); - $valid_realm = OpenIdUriHelper::checkRealm($claimed_realm, $claimed_returnTo); + $valid_realm = OpenIdUriHelper::checkRealm($claimed_realm, $claimed_returnTo); - $res = !is_null($mode) && !empty($mode) && $mode == OpenIdProtocol::CheckAuthenticationMode - && !is_null($claimed_returnTo) && !empty($claimed_returnTo) && OpenIdUriHelper::checkReturnTo($claimed_returnTo) - && !is_null($claimed_realm) && !empty($claimed_realm) && $valid_realm - && !is_null($claimed_assoc) && !empty($claimed_assoc) - && !is_null($claimed_sig) && !empty($claimed_sig) - && !is_null($signed) && !empty($signed) - && !is_null($claimed_nonce) && !empty($claimed_nonce) - && !is_null($claimed_op_endpoint) && !empty($claimed_op_endpoint) && $claimed_op_endpoint == $this->op_endpoint_url - && !is_null($claimed_identity) && !empty($claimed_identity) && OpenIdUriHelper::isValidUrl($claimed_identity); + $res = !is_null($mode) && !empty($mode) && $mode == OpenIdProtocol::CheckAuthenticationMode + && !is_null($claimed_returnTo) && !empty($claimed_returnTo) && OpenIdUriHelper::checkReturnTo($claimed_returnTo) + && !is_null($claimed_realm) && !empty($claimed_realm) && $valid_realm + && !is_null($claimed_assoc) && !empty($claimed_assoc) + && !is_null($claimed_sig) && !empty($claimed_sig) + && !is_null($signed) && !empty($signed) + && !is_null($claimed_nonce) && !empty($claimed_nonce) + && !is_null($claimed_op_endpoint) && !empty($claimed_op_endpoint) && $claimed_op_endpoint == $this->op_endpoint_url + && !is_null($claimed_identity) && !empty($claimed_identity) && OpenIdUriHelper::isValidUrl($claimed_identity); if (!$res) { - $msg = sprintf("return_to is empty? %b.",empty($claimed_returnTo)).PHP_EOL; - $msg = $msg.sprintf("realm is empty? %b.",empty($claimed_realm)).PHP_EOL; - $msg = $msg.sprintf("claimed_id is empty? %b.",empty($claimed_id)).PHP_EOL; - $msg = $msg.sprintf("identity is empty? %b.",empty($claimed_identity)).PHP_EOL; - $msg = $msg.sprintf("mode is empty? %b.",empty($mode)).PHP_EOL; - $msg = $msg.sprintf("is valid realm? %b.",$valid_realm).PHP_EOL; - throw new InvalidOpenIdMessageException($msg); + $msg = sprintf("return_to is empty? %b.", empty($claimed_returnTo)) . PHP_EOL; + $msg = $msg . sprintf("realm is empty? %b.", empty($claimed_realm)) . PHP_EOL; + $msg = $msg . sprintf("claimed_id is empty? %b.", empty($claimed_id)) . PHP_EOL; + $msg = $msg . sprintf("identity is empty? %b.", empty($claimed_identity)) . PHP_EOL; + $msg = $msg . sprintf("mode is empty? %b.", empty($mode)) . PHP_EOL; + $msg = $msg . sprintf("is valid realm? %b.", $valid_realm) . PHP_EOL; + throw new InvalidOpenIdMessageException($msg); } + return $res; } diff --git a/app/libs/openid/requests/OpenIdMessageMemento.php b/app/libs/openid/requests/OpenIdMessageMemento.php new file mode 100644 index 00000000..7d43b004 --- /dev/null +++ b/app/libs/openid/requests/OpenIdMessageMemento.php @@ -0,0 +1,63 @@ +state = $state; + } + + /** + * @return array + */ + public function getState(){ + return $this->state; + } + + /** + * @param OpenIdMessage $request + * @return OpenIdMessageMemento + */ + static public function buildFromRequest(OpenIdMessage $request){ + $r = new ReflectionObject($request); + $p = $r->getProperty('container'); + $p->setAccessible(true); + return new self($p->getValue($request)); + } + + /** + * @param array $state + * @return OpenIdMessageMemento + */ + static public function buildFromState(array $state){ + return new self($state); + } +} \ No newline at end of file diff --git a/app/libs/openid/requests/OpenIdRequest.php b/app/libs/openid/requests/OpenIdRequest.php index c25f65af..12920add 100644 --- a/app/libs/openid/requests/OpenIdRequest.php +++ b/app/libs/openid/requests/OpenIdRequest.php @@ -10,11 +10,14 @@ use openid\OpenIdMessage; */ abstract class OpenIdRequest { - + /** + * @var OpenIdMessage + */ protected $message; + public function __construct(OpenIdMessage $message) { - $this->message = $message; + $this->message = $message; } public function getMessage() diff --git a/app/libs/openid/responses/OpenIdDirectResponse.php b/app/libs/openid/responses/OpenIdDirectResponse.php index 73742641..5023e672 100644 --- a/app/libs/openid/responses/OpenIdDirectResponse.php +++ b/app/libs/openid/responses/OpenIdDirectResponse.php @@ -5,6 +5,7 @@ namespace openid\responses; use openid\exceptions\InvalidKVFormat; use openid\helpers\OpenIdErrorMessages; use openid\OpenIdProtocol; +use utils\http\HttpContentType; /** * Class OpenIdDirectResponse @@ -15,13 +16,12 @@ class OpenIdDirectResponse extends OpenIdResponse { const OpenIdDirectResponse = "OpenIdDirectResponse"; - const DirectResponseContentType = "text/plain"; public function __construct() { // Successful Responses: A server receiving a valid request MUST send a // response with an HTTP status code of 200. - parent::__construct(self::HttpOkResponse, self::DirectResponseContentType); + parent::__construct(self::HttpOkResponse, HttpContentType::Text); /* * This particular value MUST be present for the response to be a valid OpenID 2.0 * response. Future versions of the specification may define different values in order diff --git a/app/libs/openid/services/IMementoOpenIdRequestService.php b/app/libs/openid/services/IMementoOpenIdRequestService.php deleted file mode 100644 index 605acfa9..00000000 --- a/app/libs/openid/services/IMementoOpenIdRequestService.php +++ /dev/null @@ -1,25 +0,0 @@ -container = $values; } diff --git a/app/libs/utils/http/HttpUtils.php b/app/libs/utils/http/HttpUtils.php new file mode 100644 index 00000000..cfb77c40 --- /dev/null +++ b/app/libs/utils/http/HttpUtils.php @@ -0,0 +1,44 @@ +useSti() && get_class(new $this->stiBaseClass) !== get_class($this)) { + if ($this->useSti() && $this->stiBaseClass !== get_class($this)) { $builder->where($this->stiClassField, "=", $this->class->getShortName()); } return $builder; @@ -48,7 +48,7 @@ abstract class BaseModelEloquent extends Eloquent { public function newFromBuilder($attributes = array()) { - if ($this->useSti() && $attributes->{$this->stiClassField}) { + if ($this->useSti() && property_exists($attributes, $this->stiClassField) ) { $class = $this->class->getName(); $instance = new $class; $instance->exists = true; diff --git a/app/models/IEntity.php b/app/libs/utils/model/IEntity.php similarity index 81% rename from app/models/IEntity.php rename to app/libs/utils/model/IEntity.php index 13c222b4..bc98fedf 100644 --- a/app/models/IEntity.php +++ b/app/libs/utils/model/IEntity.php @@ -1,6 +1,6 @@ lifetime = $lifetime; $this->len = $len; } @@ -49,28 +51,32 @@ abstract class Identifier { * @param IdentifierGenerator $generator * @return $this */ - public function generate(IdentifierGenerator $generator){ + public function generate(IdentifierGenerator $generator) + { return $generator->generate($this); } /** * @return int */ - public function getLenght(){ + public function getLenght() + { return $this->len; } /** * @return int */ - public function getLifetime(){ + public function getLifetime() + { return intval($this->lifetime); } /** * @return string */ - public function getValue(){ + public function getValue() + { return $this->value; } @@ -78,7 +84,8 @@ abstract class Identifier { * @param string $value * @return $this */ - public function setValue($value){ + public function setValue($value) + { $this->value = $value; return $this; } diff --git a/app/libs/utils/services/IAuthService.php b/app/libs/utils/services/IAuthService.php index c9f4b711..a676d27f 100644 --- a/app/libs/utils/services/IAuthService.php +++ b/app/libs/utils/services/IAuthService.php @@ -2,26 +2,38 @@ namespace utils\services; +use oauth2\models\IClient; +use openid\model\IOpenIdUser; + /** * Interface IAuthService * @package utils\services */ -interface IAuthService { +interface IAuthService +{ + // authorization responses + const AuthorizationResponse_None = "None"; const AuthorizationResponse_AllowOnce = "AllowOnce"; const AuthorizationResponse_AllowForever = "AllowForever"; const AuthorizationResponse_DenyForever = "DenyForever"; const AuthorizationResponse_DenyOnce = "DenyOnce"; - const AuthenticationResponse_None = "None"; - const AuthenticationResponse_Cancel = "Cancel"; + // authentication responses + + const AuthenticationResponse_None = "None"; + const AuthenticationResponse_Cancel = "Cancel"; /** * @return bool */ public function isUserLogged(); + /** + * @return IOpenIdUser + */ public function getCurrentUser(); + /** * @param $username * @param $password @@ -30,23 +42,80 @@ interface IAuthService { */ public function login($username, $password, $remember_me); + /** + * @param string $username + * @return IOpenIdUser + */ public function getUserByUsername($username); + /** + * @param int $id + * @return IOpenIdUser + */ public function getUserById($id); public function getUserAuthorizationResponse(); public function setUserAuthorizationResponse($auth_response); - public function clearUserAuthorizationResponse(); + public function clearUserAuthorizationResponse(); - public function getUserAuthenticationResponse(); + public function getUserAuthenticationResponse(); - public function setUserAuthenticationResponse($auth_response); + public function setUserAuthenticationResponse($auth_response); - public function clearUserAuthenticationResponse(); + public function clearUserAuthenticationResponse(); + /** + * @return void + */ public function logout(); + /** + * @param string $openid + * @return IOpenIdUser + */ public function getUserByOpenId($openid); + + /** + * @param int $user_id + * @return string + */ + public function unwrapUserId($user_id); + + /** + * @param int $user_id + * @param IClient $client + * @return string + */ + public function wrapUserId($user_id, IClient $client); + + /** + * @param int $external_id + * @return IOpenIdUser + */ + public function getUserByExternaldId($external_id); + + /** + * @return string + */ + public function getSessionId(); + + /** + * @param $client_id + * @return void + */ + public function registerRPLogin($client_id); + + /** + * @return string[] + */ + public function getLoggedRPs(); + + /** + * @param string $jti + * @return void + */ + public function reloadSession($jti); + } \ No newline at end of file diff --git a/app/libs/utils/services/IServerConfigurationService.php b/app/libs/utils/services/IServerConfigurationService.php index 9fd4cb97..90de6745 100644 --- a/app/libs/utils/services/IServerConfigurationService.php +++ b/app/libs/utils/services/IServerConfigurationService.php @@ -17,4 +17,9 @@ interface IServerConfigurationService { public function getAllConfigValues(); public function saveConfigValue($key,$value); + + /** + * @return string + */ + public function getSiteUrl(); } \ No newline at end of file diff --git a/app/libs/utils/services/ServiceLocator.php b/app/libs/utils/services/ServiceLocator.php index 913fd3e8..7009bab8 100644 --- a/app/libs/utils/services/ServiceLocator.php +++ b/app/libs/utils/services/ServiceLocator.php @@ -8,16 +8,19 @@ use App; * Class ServiceLocator * @package utils\services */ -final class ServiceLocator { +final class ServiceLocator +{ private static $instance = null; - private function __construct(){ + private function __construct() + { } public static function getInstance() { - if (self::$instance == null) { + if (self::$instance == null) + { self::$instance = new ServiceLocator(); } diff --git a/app/libs/utils/services/UniqueIdentifierGenerator.php b/app/libs/utils/services/UniqueIdentifierGenerator.php index 1f9afde2..aaca298e 100644 --- a/app/libs/utils/services/UniqueIdentifierGenerator.php +++ b/app/libs/utils/services/UniqueIdentifierGenerator.php @@ -20,8 +20,8 @@ use utils\model\Identifier; * Class UniqueIdentifierGenerator * @package utils\services */ -abstract class UniqueIdentifierGenerator implements IdentifierGenerator { - +abstract class UniqueIdentifierGenerator implements IdentifierGenerator +{ /** * @var ICacheService @@ -45,9 +45,11 @@ abstract class UniqueIdentifierGenerator implements IdentifierGenerator { $reflect = new \ReflectionClass($identifier); $class_name = strtolower($reflect->getShortName()); - do { + do + { $value = $this->_generate($identifier)->getValue(); - } while(!$this->cache_service->addSingleValue($class_name.'.value.'.$value, $class_name.'.value.'.$value)); + } + while(!$this->cache_service->addSingleValue($class_name.'.value.'.$value, $class_name.'.value.'.$value)); return $identifier; } diff --git a/app/models/Member.php b/app/models/Member.php index 2c31ceff..49c208b8 100644 --- a/app/models/Member.php +++ b/app/models/Member.php @@ -14,6 +14,9 @@ class Member extends BaseModelEloquent //external os members db (SS) protected $connection = 'os_members'; + //no timestamps + public $timestamps = false; + public function checkPassword($password) { $digest = AuthHelper::encrypt_password($password, $this->Salt, $this->PasswordEncryption); @@ -21,8 +24,22 @@ class Member extends BaseModelEloquent return $res; } - public function groups(){ + public function groups() + { return $this->belongsToMany('Group', 'Group_Members', 'MemberID', 'GroupID'); } + + /** + * @return bool + */ + public function canLogin() + { + $attr = $this->getAttributes(); + if(isset($attr['Active'])) + { + return (bool)$attr['Active']; + } + return true; + } } \ No newline at end of file diff --git a/app/models/oauth2/Api.php b/app/models/oauth2/Api.php index 731b33f9..1f1db51c 100644 --- a/app/models/oauth2/Api.php +++ b/app/models/oauth2/Api.php @@ -3,27 +3,36 @@ use oauth2\models\IApi; use utils\model\BaseModelEloquent; -class Api extends BaseModelEloquent implements IApi { +class Api extends BaseModelEloquent implements IApi +{ - protected $fillable = array('name','description','active','resource_server_id','logo'); + protected $fillable = array('name', 'description', 'active', 'resource_server_id', 'logo'); protected $table = 'oauth2_api'; - public function getActiveAttribute(){ - return (bool) $this->attributes['active']; - } + public function getActiveAttribute() + { + return (bool)$this->attributes['active']; + } - public function getIdAttribute(){ - return (int) $this->attributes['id']; - } + public function getIdAttribute() + { + return (int)$this->attributes['id']; + } - public function getResourceServerIdAttribute(){ - return (int) $this->attributes['resource_server_id']; - } + public function getLogoAttribute() + { + return $this->getLogo(); + } + + public function getResourceServerIdAttribute() + { + return (int)$this->attributes['resource_server_id']; + } public function scopes() { - return $this->hasMany('ApiScope','api_id'); + return $this->hasMany('ApiScope', 'api_id'); } public function resource_server() @@ -33,7 +42,7 @@ class Api extends BaseModelEloquent implements IApi { public function endpoints() { - return $this->hasMany('ApiEndpoint','api_id'); + return $this->hasMany('ApiEndpoint', 'api_id'); } /** @@ -51,8 +60,8 @@ class Api extends BaseModelEloquent implements IApi { public function getLogo() { - $url = asset('img/apis/server.png'); - return !empty($this->logo)?$this->logo:$url; + $url = asset('/assets/img/apis/server.png'); + return $url; } @@ -64,11 +73,14 @@ class Api extends BaseModelEloquent implements IApi { public function getScope() { $scope = ''; - foreach($this->scopes()->get() as $s){ - if(!$s->active) continue; - $scope = $scope .$s->name.' '; + foreach ($this->scopes()->get() as $s) { + if (!$s->active) { + continue; + } + $scope = $scope . $s->name . ' '; } $scope = trim($scope); + return $scope; } @@ -93,15 +105,15 @@ class Api extends BaseModelEloquent implements IApi { $this->active = $active; } - public function delete () + public function delete() { - $endpoints = ApiEndpoint::where('api_id','=', $this->id)->get(); - foreach($endpoints as $endpoint){ + $endpoints = ApiEndpoint::where('api_id', '=', $this->id)->get(); + foreach ($endpoints as $endpoint) { $endpoint->delete(); } - $scopes = ApiScope::where('api_id','=', $this->id)->get(); - foreach($scopes as $scope){ + $scopes = ApiScope::where('api_id', '=', $this->id)->get(); + foreach ($scopes as $scope) { $scope->delete(); } diff --git a/app/models/oauth2/ApiScope.php b/app/models/oauth2/ApiScope.php index 74a9d8e6..5a0290f9 100644 --- a/app/models/oauth2/ApiScope.php +++ b/app/models/oauth2/ApiScope.php @@ -3,39 +3,38 @@ use oauth2\models\IApiScope; use utils\model\BaseModelEloquent; -class ApiScope extends BaseModelEloquent implements IApiScope { - - protected $fillable = array('name' ,'short_description', 'description','active','default','system', 'api_id'); - - public function getActiveAttribute(){ - return (bool) $this->attributes['active']; - } - - public function getDefaultAttribute(){ - return (bool) $this->attributes['default']; - } - - public function getSystemAttribute(){ - return (bool) $this->attributes['system']; - } - - public function getIdAttribute(){ - return (int) $this->attributes['id']; - } - - public function getApiIdAttribute(){ - return (int) $this->attributes['api_id']; - } +/** + * Class ApiScope + */ +class ApiScope extends BaseModelEloquent implements IApiScope +{ protected $table = 'oauth2_api_scope'; - protected $hidden = array('pivot'); + protected $fillable = array('name' ,'short_description', 'description','active','default','system', 'api_id', 'assigned_by_groups'); - public function api() - { - return $this->belongsTo('Api'); + public function getActiveAttribute(){ + return (bool) $this->attributes['active']; } + public function getDefaultAttribute(){ + return (bool) $this->attributes['default']; + } + + public function getSystemAttribute(){ + return (bool) $this->attributes['system']; + } + + public function getIdAttribute(){ + return (int) $this->attributes['id']; + } + + public function getApiIdAttribute(){ + return (int) $this->attributes['api_id']; + } + + protected $hidden = array('pivot'); + public function getShortDescription() { return $this->short_description; @@ -56,6 +55,27 @@ class ApiScope extends BaseModelEloquent implements IApiScope { return $this->active; } + /** + * @return boolean + */ + public function isSystem() + { + return $this->system; + } + + /** + * @return boolean + */ + public function isDefault() + { + return $this->default; + } + + public function api() + { + return $this->belongsTo('Api'); + } + public function getApiName() { $api = $this->api()->first(); @@ -69,6 +89,22 @@ class ApiScope extends BaseModelEloquent implements IApiScope { public function getApiLogo(){ $api = $this->api()->first(); - return !is_null($api)? $api->getLogo():asset('img/apis/server.png'); + return !is_null($api) ? $api->getLogo():asset('/assets/apis/server.png'); + } + + /** + * @return \oauth2\models\IApi + */ + public function getApi() + { + return $this->api(); + } + + /** + * @return bool + */ + public function isAssignableByGroups() + { + return $this->assigned_by_groups; } } \ No newline at end of file diff --git a/app/models/oauth2/ApiScopeGroup.php b/app/models/oauth2/ApiScopeGroup.php new file mode 100644 index 00000000..e1bcb2f5 --- /dev/null +++ b/app/models/oauth2/ApiScopeGroup.php @@ -0,0 +1,75 @@ +belongsToMany('ApiScope','oauth2_api_scope_group_scope','group_id','scope_id'); + } + + public function users() + { + return $this->belongsToMany('auth\User','oauth2_api_scope_group_users','group_id','user_id'); + } + + /** + * @param IApiScope $scope + */ + public function addScope(IApiScope $scope) + { + $this->scopes()->attach($scope->id); + } + + /** + * @param IOAuth2User $user + */ + public function addUser(IOAuth2User $user) + { + $this->users()->attach($user->id); + } + + /** + * @param IOAuth2User $scope + */ + public function removeScope(IOAuth2User $scope) + { + $this->scopes()->detach($scope->id); + } + + /** + * @param IOAuth2User $user + */ + public function removeUser(IOAuth2User $user) + { + $this->users()->detach($user->id); + } + + /** + * @return int + */ + public function getId() + { + return (int)$this->id; + } +} \ No newline at end of file diff --git a/app/models/oauth2/AssymetricKey.php b/app/models/oauth2/AssymetricKey.php new file mode 100644 index 00000000..b2bc7364 --- /dev/null +++ b/app/models/oauth2/AssymetricKey.php @@ -0,0 +1,158 @@ +id; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @return string + */ + public function getUse() + { + return $this->usage; + } + + /** + * @return bool + */ + public function isActive() + { + return (bool)$this->active; + } + + /** + * @return \DateTime + */ + public function getLastUse() + { + return $this->last_use; + } + + /** + * @return $this + */ + public function markAsUsed() + { + $this->last_use = new DateTime(); + return $this; + } + + /** + * @return string + */ + public function getKeyId() + { + return $this->kid; + } + + private function calculateThumbprint($alg){ + $pem = str_replace( array("\n","\r"), '', trim($this->getPublicKeyPEM())); + return strtoupper(hash($alg, base64_decode($pem))); + } + + /** + * @return string + */ + public function getSHA_1_Thumbprint() + { + return $this->calculateThumbprint('sha1'); + } + + /** + * @return string + */ + public function getSHA_256_Thumbprint() + { + return $this->calculateThumbprint('sha256'); + } + + abstract public function getPublicKeyPEM(); + + /** + * @return string + */ + public function getPEM() + { + return $this->pem_content; + } + + /** + * checks validatiry range with now + * @return bool + */ + public function isExpired() + { + $now = new \DateTime(); + return ( $this->valid_from <= $now && $this->valid_to >= $now); + } + + /** + * algorithm intended for use with the key + * @return ICryptoAlgorithm + */ + public function getAlg() + { + $algorithm = DigitalSignatures_MACs_Registry::getInstance()->get($this->alg); + + if(is_null($algorithm)) + { + $algorithm = KeyManagementAlgorithms_Registry::getInstance()->get($this->alg); + } + return $algorithm; + } + +} \ No newline at end of file diff --git a/app/models/oauth2/Client.php b/app/models/oauth2/Client.php index 864af077..9968c3fc 100644 --- a/app/models/oauth2/Client.php +++ b/app/models/oauth2/Client.php @@ -1,24 +1,135 @@ attributes['active']; } - public function getIdAttribute(){ + public function getIdAttribute() + { return (int) $this->attributes['id']; } - public function getLockedAttribute(){ + public function getLockedAttribute() + { return (int) $this->attributes['locked']; } + /** + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function public_keys() + { + return $this->hasMany('ClientPublicKey','oauth2_client_id','id'); + } + + /** + * @param string $value + */ + public function setApplicationTypeAttribute($value) + { + $this->attributes['application_type'] = strtolower($value); + $this->attributes['client_type'] = $this->infereClientTypeFromAppType($value); + } + + /** + * @param string $app_type + * @return string + */ + private function infereClientTypeFromAppType($app_type) + { + switch($app_type) + { + case IClient::ApplicationType_JS_Client: + return IClient::ClientType_Public; + break; + default: + return IClient::ClientType_Confidential; + break; + } + } + + public function access_tokens() { return $this->hasMany('AccessToken'); @@ -44,17 +155,6 @@ class Client extends BaseModelEloquent implements IClient { return $this->belongsToMany('ApiScope','oauth2_client_api_scope','client_id','scope_id'); } - public function authorized_uris() - { - return $this->hasMany('ClientAuthorizedUri','client_id'); - } - - public function allowed_origins() - { - return $this->hasMany('ClientAllowedOrigin','client_id'); - } - - public function getClientId() { return $this->client_id; @@ -80,7 +180,8 @@ class Client extends BaseModelEloquent implements IClient { $res = array(); - foreach($scopes as $db_scope){ + foreach($scopes as $db_scope) + { $api = !is_null($db_scope)?$db_scope->api()->first():null; $resource_server = !is_null($api) ? $api->resource_server()->first():null; if(!is_null($resource_server) && $resource_server->active && !is_null($api) && $api->active) @@ -89,9 +190,9 @@ class Client extends BaseModelEloquent implements IClient { return $res; } - public function getClientRegisteredUris() + public function getRedirectUris() { - return $this->authorized_uris()->get(); + return explode(',',$this->redirect_uris); } public function isScopeAllowed($scope) @@ -111,25 +212,28 @@ class Client extends BaseModelEloquent implements IClient { return $res; } - public function isUriAllowed($uri) { if(!filter_var($uri, FILTER_VALIDATE_URL)) return false; $parts = @parse_url($uri); - if ($parts == false) { + if ($parts == false) + { return false; } - if(($parts['scheme']!=='https') && (ServerConfigurationService::getConfigValue("SSL.Enable"))) + if(($this->application_type !== IClient::ApplicationType_Native && $parts['scheme']!=='https') && (ServerConfigurationService::getConfigValue("SSL.Enable"))) return false; //normalize uri $normalized_uri = $parts['scheme'].'://'.strtolower($parts['host']); + if(isset($parts['port'])) { + $normalized_uri .= ':'.strtolower($parts['port']); + } if(isset($parts['path'])) { $normalized_uri .= strtolower($parts['path']); } // normalize url and remove trailing / $normalized_uri = rtrim($normalized_uri, '/'); - $client_authorized_uri = ClientAuthorizedUri::where('client_id', '=', $this->id)->where('uri','=',$normalized_uri)->first(); - return !is_null($client_authorized_uri); + + return str_contains($this->redirect_uris, $normalized_uri); } public function getApplicationName() @@ -141,7 +245,10 @@ class Client extends BaseModelEloquent implements IClient { { $app_logo = $this->app_logo; if(is_null($app_logo) || empty($app_logo)) - $app_logo = asset('img/oauth2.default.logo.png'); + $app_logo = asset('assets/img/oauth2.default.logo.png'); + $app_logo_url = $this->logo_uri; + if(!empty($app_logo_url)) + $app_logo = $app_logo_url; return $app_logo; } @@ -197,7 +304,8 @@ class Client extends BaseModelEloquent implements IClient { * @return string * @throws Exception */ - public function getFriendlyApplicationType(){ + public function getFriendlyApplicationType() + { switch($this->application_type){ case IClient::ApplicationType_JS_Client: return 'Client Side (JS)'; @@ -208,13 +316,16 @@ class Client extends BaseModelEloquent implements IClient { case IClient::ApplicationType_Web_App: return 'Web Server Application'; break; + case IClient::ApplicationType_Native: + return 'Native Application'; + break; } throw new Exception('Invalid Application Type'); } public function getClientAllowedOrigins() { - return $this->allowed_origins()->get(); + return explode(',', $this->allowed_origins); } /** @@ -232,18 +343,247 @@ class Client extends BaseModelEloquent implements IClient { if($parts['scheme']!=='https') return false; $origin_without_port = $parts['scheme'].'://'.$parts['host']; - $client_allowed_origin = $this->allowed_origins()->where('allowed_origin','=',$origin_without_port)->first(); - if(!is_null($client_allowed_origin)) return true; + + if(str_contains($this->allowed_origins,$origin_without_port )) return true; if(isset($parts['port'])){ $origin_with_port = $parts['scheme'].'://'.$parts['host'].':'.$parts['port']; - $client_authorized_uri = $this->allowed_origins()->where('allowed_origin','=',$origin_with_port)->first();; - return !is_null($client_authorized_uri); + return str_contains($this->allowed_origins, $origin_with_port ); } return false; } public function getWebsite() { - return $this->website; + $res = $this->website; + if(empty($res)) $res = '#'; + return $res; } -} + + /** + * @return \DateTime + */ + public function getClientSecretExpiration() + { + $exp_date = $this->client_secret_expires_at; + if(is_null($exp_date)) return null; + + if($exp_date instanceof \DateTime) + return $exp_date; + return new \DateTime($exp_date); + } + + /** + * @return bool + */ + public function isClientSecretExpired() + { + $now = new \DateTime(); + $exp_date = $this->getClientSecretExpiration(); + + if(is_null($exp_date)) return false; + return $exp_date < $now; + } + + /** + * @return string[] + */ + public function getContacts() + { + return explode(',',$this->contacts); + } + + /** + * @return int + */ + public function getDefaultMaxAge() + { + return (int)$this->default_max_age; + } + + /** + * @return bool + */ + public function requireAuthTimeClaim() + { + return $this->require_auth_time; + } + + /** + * @return string + */ + public function getLogoUri() + { + return $this->logo_uri; + } + + /** + * @return string + */ + public function getPolicyUri() + { + $res = $this->policy_uri; + if(empty($res)) $res = '#'; + return $res; + } + + /** + * @return string + */ + public function getTermOfServiceUri() + { + $res = $this->tos_uri; + if(empty($res)) $res = '#'; + return $res; + } + + /** + * @return string[] + */ + public function getPostLogoutUris() + { + return explode(',', $this->post_logout_redirect_uris); + } + + /** + * @return string + */ + public function getLogoutUri() + { + return $this->logout_uri; + } + + /** + * @return JWTResponseInfo + */ + public function getIdTokenResponseInfo() + { + return new JWTResponseInfo + ( + DigitalSignatures_MACs_Registry::getInstance()->get($this->id_token_signed_response_alg), + KeyManagementAlgorithms_Registry::getInstance()->get($this->id_token_encrypted_response_alg), + ContentEncryptionAlgorithms_Registry::getInstance()->get($this->id_token_encrypted_response_enc) + ); + } + + /** + * @return JWTResponseInfo + */ + public function getUserInfoResponseInfo() + { + return new JWTResponseInfo + ( + DigitalSignatures_MACs_Registry::getInstance()->get($this->userinfo_signed_response_alg), + KeyManagementAlgorithms_Registry::getInstance()->get($this->userinfo_encrypted_response_alg), + ContentEncryptionAlgorithms_Registry::getInstance()->get($this->userinfo_encrypted_response_enc) + ); + } + + /** + * @return TokenEndpointAuthInfo + */ + public function getTokenEndpointAuthInfo() + { + return new TokenEndpointAuthInfo( + $this->token_endpoint_auth_method, + DigitalSignatures_MACs_Registry::getInstance()->isSupported($this->token_endpoint_auth_signing_alg) ? + DigitalSignatures_MACs_Registry::getInstance()->get($this->token_endpoint_auth_signing_alg) : + null + ); + } + + /** + * @return string + */ + public function getSubjectType() + { + return $this->subject_type; + } + + /** + * @return IClientPublicKey[] + */ + public function getPublicKeys() + { + return $this->public_keys()->get(); + } + + /** + * @return IClientPublicKey[] + */ + public function getPublicKeysByUse($use) + { + return $this->public_keys()->where('usage','=',$use)->all(); + } + + /** + * @param string $kid + * @return IClientPublicKey + */ + public function getPublicKeyByIdentifier($kid) + { + return $this->public_keys()->where('kid','=',$kid)->first(); + } + + /** + * @param IClientPublicKey $public_key + * @return $this + */ + public function addPublicKey(IClientPublicKey $public_key) + { + $this->public_keys()->save($public_key); + } + + /** + * @return string + */ + public function getJWKSUri() + { + return $this->jwks_uri; + } + + /** + * @param string $use + * @param string $alg + * @return IClientPublicKey + */ + public function getCurrentPublicKeyByUse($use, $alg) + { + $now = new \DateTime(); + + return $this->public_keys() + ->where('usage','=',$use) + ->where('alg','=',$alg) + ->where('active','=', true) + ->where('valid_from','<=',$now) + ->where('valid_to','>=',$now) + ->first(); + } + + /** + * @param string $post_logout_uri + * @return bool + */ + public function isPostLogoutUriAllowed($post_logout_uri) + { + if(!filter_var($post_logout_uri, FILTER_VALIDATE_URL)) return false; + + $parts = @parse_url($post_logout_uri); + + if ($parts == false) { + return false; + } + if($parts['scheme']!=='https') + return false; + + $logout_without_port = $parts['scheme'].'://'.$parts['host']; + + if(str_contains($this->post_logout_redirect_uris, $logout_without_port )) return true; + + if(isset($parts['port'])) + { + $logout_with_port = $parts['scheme'].'://'.$parts['host'].':'.$parts['port']; + return str_contains($this->post_logout_redirect_uris, $logout_with_port ); + } + return false; + } +} \ No newline at end of file diff --git a/app/models/oauth2/ClientAllowedOrigin.php b/app/models/oauth2/ClientAllowedOrigin.php deleted file mode 100644 index 0252278f..00000000 --- a/app/models/oauth2/ClientAllowedOrigin.php +++ /dev/null @@ -1,12 +0,0 @@ -belongsTo('Client'); - } -} \ No newline at end of file diff --git a/app/models/oauth2/ClientAuthorizedUri.php b/app/models/oauth2/ClientAuthorizedUri.php deleted file mode 100644 index 942d873d..00000000 --- a/app/models/oauth2/ClientAuthorizedUri.php +++ /dev/null @@ -1,13 +0,0 @@ -belongsTo('Client'); - } -} \ No newline at end of file diff --git a/app/models/oauth2/ClientPublicKey.php b/app/models/oauth2/ClientPublicKey.php new file mode 100644 index 00000000..219b90db --- /dev/null +++ b/app/models/oauth2/ClientPublicKey.php @@ -0,0 +1,84 @@ +belongsTo('Client'); + } + + /** + * @param string $kid + * @param string $type + * @param string $use + * @param string $pem + * @param string $alg + * @param bool $active + * @param \DateTime $valid_from + * @param \DateTime $valid_to + * @return IClientPublicKey + */ + static public function buildFromPEM($kid, $type, $use, $pem, $alg, $active, \DateTime $valid_from, \DateTime $valid_to) + { + $pk = new self; + $pk->kid = $kid; + $pk->pem_content = $pem; + $pk->type = $type; + $pk->usage = $use; + $pk->alg = $alg; + $pk->active = $active; + $pk->valid_from = $valid_from; + $pk->valid_to = $valid_to; + return $pk; + } + + public function getPublicKeyPEM() + { + return $this->pem_content; + } + + /** + * @return IJWK + */ + public function toJWK() + { + $jwk = RSAJWKFactory::build + ( + new RSAJWKPEMPublicKeySpecification + ( + $this->getPublicKeyPEM(), + $this->alg + ) + ); + + $jwk->setId($this->kid); + $jwk->setType($this->type); + $jwk->setKeyUse($this->usage); + return $jwk; + } +} \ No newline at end of file diff --git a/app/models/oauth2/ResourceServer.php b/app/models/oauth2/ResourceServer.php index c62a0882..343238c4 100644 --- a/app/models/oauth2/ResourceServer.php +++ b/app/models/oauth2/ResourceServer.php @@ -3,27 +3,31 @@ use oauth2\models\IResourceServer; use utils\model\BaseModelEloquent; -class ResourceServer extends BaseModelEloquent implements IResourceServer { +class ResourceServer extends BaseModelEloquent implements IResourceServer +{ - protected $fillable = array('host','ip','active','friendly_name'); + protected $fillable = array('host', 'ip', 'active', 'friendly_name'); protected $table = 'oauth2_resource_server'; - public function getActiveAttribute(){ - return (bool) $this->attributes['active']; - } + public function getActiveAttribute() + { + return (bool)$this->attributes['active']; + } - public function getIdAttribute(){ - return (int) $this->attributes['id']; - } + public function getIdAttribute() + { + return (int)$this->attributes['id']; + } public function apis() { - return $this->hasMany('Api','resource_server_id'); + return $this->hasMany('Api', 'resource_server_id'); } - public function client(){ + public function client() + { return $this->hasOne('Client'); } diff --git a/app/models/oauth2/ServerPrivateKey.php b/app/models/oauth2/ServerPrivateKey.php new file mode 100644 index 00000000..65cde246 --- /dev/null +++ b/app/models/oauth2/ServerPrivateKey.php @@ -0,0 +1,145 @@ +decrypt($this->attributes['pem_content']); + } + + public function setPemContentAttribute($value) + { + + $this->attributes['pem_content'] = $this->encrypt($value); + } + + public function getPasswordAttribute() + { + return $this->decrypt($this->attributes['password']); + } + + public function setPasswordAttribute($value) + { + $this->attributes['password'] = $this->encrypt($value); + } + + /** + * @return string + */ + public function getPassword() + { + return $this->password; + } + + /** + * @param string $kid + * @param \DateTime $valid_from + * @param \DateTime $valid_to + * @param string $type + * @param string $use + * @param bool $active + * @param string $pem_content + * @param null|string $password + * @return IServerPrivateKey + */ + static function build($kid, \DateTime $valid_from, \DateTime $valid_to, $type, $use, $alg, $active, $pem_content, $password = null) + { + $key = new self; + $key->kid = $kid; + $key->valid_from = $valid_from; + $key->valid_to = $valid_to; + $key->type = $type; + $key->usage = $use; + $key->active = $active; + $key->alg = $alg; + $key->pem_content = $pem_content; + $key->password = $password; + + return $key; + } + + public function getPublicKeyPEM() + { + $private_key_pem = $this->pem_content; + $rsa = new Crypt_RSA(); + + if(!empty($this->password)){ + $rsa->setPassword($this->password); + } + + $rsa->loadKey($private_key_pem); + return $rsa->getPublicKey(); + } + + /** + * @return IJWK + */ + public function toJWK() + { + //load server private key. + $jwk = RSAJWKFactory::build + ( + new RSAJWKPEMPrivateKeySpecification + ( + $this->pem_content, + $this->password, + $this->alg + ) + ); + + $jwk->setId($this->kid); + $jwk->setType($this->type); + $jwk->setKeyUse($this->usage); + + return $jwk; + } +} \ No newline at end of file diff --git a/app/providers/BehatSessionServiceProvider.php b/app/providers/BehatSessionServiceProvider.php new file mode 100644 index 00000000..9b00886c --- /dev/null +++ b/app/providers/BehatSessionServiceProvider.php @@ -0,0 +1,26 @@ +setCurrentPage($page_nbr); + + return $this->entity->Filter($filters)->paginate($page_size, $fields); + } + + /** + * @param IEntity $entity + * @return bool + */ + public function update(IEntity $entity) + { + return $entity->Save(); + } + + /** + * @param IEntity $entity + * @return bool + */ + public function add(IEntity $entity) + { + return $entity->save(); + } + + /** + * @param IEntity $entity + * @return bool + */ + public function delete(IEntity $entity) + { + return $entity->delete(); + } + + /** + * @param int $id + * @return IEntity + */ + public function get($id) + { + return $this->entity->find($id); + } +} \ No newline at end of file diff --git a/app/repositories/EloquentApiScopeGroupRepository.php b/app/repositories/EloquentApiScopeGroupRepository.php new file mode 100644 index 00000000..4ed8e5e8 --- /dev/null +++ b/app/repositories/EloquentApiScopeGroupRepository.php @@ -0,0 +1,43 @@ +entity = $group; + $this->log_service = $log_service; + } + +} \ No newline at end of file diff --git a/app/repositories/EloquentAssymetricKeyRepository.php b/app/repositories/EloquentAssymetricKeyRepository.php new file mode 100644 index 00000000..40068deb --- /dev/null +++ b/app/repositories/EloquentAssymetricKeyRepository.php @@ -0,0 +1,160 @@ +key->where('kid','=',$kid)->first(); + } + + /** + * @param IAssymetricKey $key + * @return void + */ + public function add(IAssymetricKey $key) + { + $key->save(); + } + + /** + * @param IAssymetricKey $key + * @return void + */ + public function delete(IAssymetricKey $key) + { + $key->delete(); + } + + /** + * @param int $id + * @return IAssymetricKey + */ + public function getById($id) + { + return $this->key->find($id); + } + + /** + * @param string $pem + * @return IAssymetricKey + */ + public function getByPEM($pem) + { + return $this->key->where('pem_content','=',$pem)->first(); + } + + /** + * @param string $type + * @param string $usage + * @params string $alg + * @param \DateTime $valid_from + * @param \DateTime $valid_to + * @param int|null $owner_id + * @return IAssymetricKey + */ + public function getByValidityRange($type, $usage, $alg, \DateTime $valid_from, \DateTime $valid_to, $owner_id = null) + { + // (StartA <= EndB) and (EndA >= StartB) + $query = $this->key + ->where('type','=',$type) + ->where('usage','=',$usage) + ->where('alg','=',$alg) + ->where('valid_from','<=',$valid_to) + ->where('valid_to','>=',$valid_from) + ->where('active','=', true); + + if($owner_id) + { + $query = $query->where('oauth2_client_id','=', $owner_id); + } + return $query->get(); + } + + + /** + * @param int $page_nbr + * @param int $page_size + * @param array $filters + * @param array $fields + * @return IAssymetricKey[] + */ + public function getAll($page_nbr = 1, $page_size = 10, array $filters = array(), array $fields = array('*')) + { + DB::getPaginator()->setCurrentPage($page_nbr); + return $this->key->Filter($filters)->paginate($page_size, $fields); + } + + /** + * @return IAssymetricKey[] + */ + public function getActives() + { + $now = new \DateTime(); + return $this->key + ->where('active','=', true) + ->where('valid_from','<=',$now) + ->where('valid_to','>=',$now) + ->get(); + } + + /** + * @param string $type + * @param string $usage + * @param string $alg + * @param int|null $owner_id + * @return IAssymetricKey + */ + public function getActiveByCriteria($type, $usage, $alg, $owner_id = null) + { + $now = new \DateTime(); + $query = $this->key + ->where('active','=', true) + ->where('valid_from','<=',$now) + ->where('valid_to','>=',$now) + ->where('type','=',$type) + ->where('usage','=',$usage) + ->where('alg','=',$alg); + if($owner_id) + { + $query = $query->where('oauth2_client_id','=', $owner_id); + } + return $query->first(); + } +} \ No newline at end of file diff --git a/app/repositories/EloquentClientPublicKeyRepository.php b/app/repositories/EloquentClientPublicKeyRepository.php new file mode 100644 index 00000000..ee629968 --- /dev/null +++ b/app/repositories/EloquentClientPublicKeyRepository.php @@ -0,0 +1,35 @@ +key = $public_key; + $this->log_service = $log_service; + } +} \ No newline at end of file diff --git a/app/repositories/EloquentClientRepository.php b/app/repositories/EloquentClientRepository.php new file mode 100644 index 00000000..1d8006ca --- /dev/null +++ b/app/repositories/EloquentClientRepository.php @@ -0,0 +1,73 @@ +client = $client; + $this->log_service = $log_service; + } + + /** + * @param int id + * @return IClient + */ + public function get($id) + { + return $this->client->find($id); + } + + /** + * @param IClient $client + * @return void + */ + public function add(IClient $client) + { + $client->save(); + } + + /** + * @param IClient $client + * @return void + */ + public function delete(IClient $client) + { + $client->delete(); + } +} \ No newline at end of file diff --git a/app/repositories/EloquentMemberRepository.php b/app/repositories/EloquentMemberRepository.php index 82a40c04..3df79e9d 100644 --- a/app/repositories/EloquentMemberRepository.php +++ b/app/repositories/EloquentMemberRepository.php @@ -10,19 +10,21 @@ use utils\services\ILogService; * Class EloquentMemberRepository * @package repositories */ -class EloquentMemberRepository implements IMemberRepository{ +class EloquentMemberRepository implements IMemberRepository +{ private $member; private $log_service; - /** - * @param Member $member - * @param ILogService $log_service - */ - public function __construct(Member $member, ILogService $log_service){ - $this->member = $member; - $this->log_service = $log_service; - } + /** + * @param Member $member + * @param ILogService $log_service + */ + public function __construct(Member $member, ILogService $log_service) + { + $this->member = $member; + $this->log_service = $log_service; + } /** * @param $id * @return Member diff --git a/app/repositories/EloquentOpenIdAssociationRepository.php b/app/repositories/EloquentOpenIdAssociationRepository.php index 4c00ab9c..1ebd331a 100644 --- a/app/repositories/EloquentOpenIdAssociationRepository.php +++ b/app/repositories/EloquentOpenIdAssociationRepository.php @@ -9,39 +9,42 @@ use OpenIdAssociation; * Class EloquentOpenIdAssociationRepository * @package repositories */ +class EloquentOpenIdAssociationRepository implements IOpenIdAssociationRepository +{ -class EloquentOpenIdAssociationRepository implements IOpenIdAssociationRepository { + private $association; - private $association; + public function __construct(OpenIdAssociation $association) + { + $this->association = $association; + } - public function __construct(OpenIdAssociation $association){ - $this->association = $association; - } + public function add(OpenIdAssociation $a) + { + return $a->Save(); + } - public function add(OpenIdAssociation $a) - { - return $a->Save(); - } + public function deleteById($id) + { + return $this->delete($this->get($id)); + } - public function deleteById($id) - { - return $this->delete($this->get($id)); - } + public function getByHandle($handle) + { + return $this->association->where('identifier', '=', $handle)->first(); + } - public function getByHandle($handle) - { - return $this->association->where('identifier', '=', $handle)->first(); - } + public function delete(OpenIdAssociation $a) + { + if (!is_null($a)) { + return $a->delete(); + } - public function delete(OpenIdAssociation $a) - { - if(!is_null($a)) - return $a->delete(); - return false; - } + return false; + } - public function get($id) - { - return $this->association->find($id); - } + public function get($id) + { + return $this->association->find($id); + } } \ No newline at end of file diff --git a/app/repositories/EloquentServerPrivateKeyRepository.php b/app/repositories/EloquentServerPrivateKeyRepository.php new file mode 100644 index 00000000..e6eaa437 --- /dev/null +++ b/app/repositories/EloquentServerPrivateKeyRepository.php @@ -0,0 +1,33 @@ +key = $private_key; + $this->log_service = $log_service; + } +} \ No newline at end of file diff --git a/app/repositories/EloquentUserRepository.php b/app/repositories/EloquentUserRepository.php index 8e87e01f..cbbbd583 100644 --- a/app/repositories/EloquentUserRepository.php +++ b/app/repositories/EloquentUserRepository.php @@ -4,91 +4,97 @@ namespace repositories; use auth\IUserRepository; use auth\User; -use utils\services\ILogService; use DB; +use utils\services\ILogService; +use \Member; -class EloquentUserRepository implements IUserRepository { +/** + * Class EloquentUserRepository + * @package repositories + */ +final class EloquentUserRepository extends AbstractEloquentEntityRepository implements IUserRepository +{ - private $user; - private $log_service; - public function __construct(User $user,ILogService $log_service){ - $this->user = $user; - $this->log_service = $log_service; - } - /** - * @param $id - * @return User - */ - public function get($id) - { - return $this->user->find($id); - } + /** + * @var ILogService + */ + private $log_service; - public function getByCriteria($filters){ - return $this->user->Filter($filters)->get(); - } + /** + * EloquentUserRepository constructor. + * @param User $user + * @param ILogService $log_service + */ + public function __construct(User $user, ILogService $log_service) + { + $this->entity = $user; + $this->log_service = $log_service; + } - public function getOneByCriteria($filters){ - return $this->user->Filter($filters)->first(); - } + /** + * @param $id + * @return User + */ + public function get($id) + { + return $this->entity->find($id); + } - /** - * @param User $u - * @return bool - */ - public function update(User $u) - { - return $u->Save(); - } + public function getByCriteria($filters) + { + return $this->entity->Filter($filters)->get(); + } - /** - * @param User $u - * @return bool - */ - public function add(User $u) - { - return $u->Save(); - } + public function getOneByCriteria($filters) + { + return $this->entity->Filter($filters)->first(); + } - /** - * @param int $page_nbr - * @param int $page_size - * @param array $filters - * @param array $fields - * @return array - */ - public function getByPage($page_nbr = 1, $page_size = 10, array $filters = array(), array $fields = array('*')) - { - DB::getPaginator()->setCurrentPage($page_nbr); - return $this->user->Filter($filters)->paginate($page_size, $fields); - } + /** + * @param array $filters + * @return int + */ + public function getCount(array $filters = array()) + { + return $this->entity->Filter($filters)->count(); + } - /** - * @param array $filters - * @return int - */ - public function getCount(array $filters = array()) - { - return $this->user->Filter($filters)->count(); - } + /** + * @param $external_id + * @return User + */ + public function getByExternalId($external_id) + { + return $this->entity->where('external_identifier', '=', $external_id)->first(); + } - /** - * @param $external_id - * @return User - */ - public function getByExternalId($external_id) - { - return $this->user->where('external_identifier', '=', $external_id)->first(); - } + /** + * @param mixed $identifier + * @param string $token + * @return User + */ + public function getByToken($identifier, $token) + { + return $this->entity + ->where('external_identifier', '=', $identifier) + ->where('remember_token', '=',$token)->first(); + } - /** - * @param mixed $identifier - * @param string $token - * @return User - */ - public function getByToken($identifier, $token) - { - return $this->user->where('external_identifier', '=', $identifier)->where('remember_token', '=', $token)->first(); - } + /** + * @param string $term + * @return array + */ + public function getByEmailOrName($term) + { + $list = array(); + $members = Member::where('Email', 'like', '%'.$term.'%')->paginate(10); + foreach($members->getItems() as $m) + { + $user = $this->getByExternalId(intval($m->ID)); + if(!is_null($user)) + array_push($list, $user); + } + return $list; + } } \ No newline at end of file diff --git a/app/repositories/RepositoriesProvider.php b/app/repositories/RepositoriesProvider.php index 5a33c7bb..f4047349 100644 --- a/app/repositories/RepositoriesProvider.php +++ b/app/repositories/RepositoriesProvider.php @@ -2,8 +2,8 @@ namespace repositories; -use Illuminate\Support\ServiceProvider; use App; +use Illuminate\Support\ServiceProvider; /** * Class RepositoriesProvider @@ -11,15 +11,21 @@ use App; */ class RepositoriesProvider extends ServiceProvider { - protected $defer = false; + protected $defer = false; - public function boot(){ - } + public function boot() + { + } - public function register(){ - App::singleton('openid\repositories\IOpenIdAssociationRepository', 'repositories\EloquentOpenIdAssociationRepository'); - App::singleton('openid\repositories\IOpenIdTrustedSiteRepository', 'repositories\EloquentOpenIdTrustedSiteRepository'); - App::singleton('auth\IUserRepository', 'repositories\EloquentUserRepository'); - App::singleton('auth\IMemberRepository', 'repositories\EloquentMemberRepository'); - } + public function register() + { + App::singleton('openid\repositories\IOpenIdAssociationRepository', 'repositories\EloquentOpenIdAssociationRepository'); + App::singleton('openid\repositories\IOpenIdTrustedSiteRepository', 'repositories\EloquentOpenIdTrustedSiteRepository'); + App::singleton('auth\IUserRepository', 'repositories\EloquentUserRepository'); + App::singleton('auth\IMemberRepository', 'repositories\EloquentMemberRepository'); + App::singleton('oauth2\repositories\IClientPublicKeyRepository', 'repositories\EloquentClientPublicKeyRepository'); + App::singleton('oauth2\repositories\IServerPrivateKeyRepository', 'repositories\EloquentServerPrivateKeyRepository'); + App::singleton('oauth2\repositories\IClientRepository', 'repositories\EloquentClientRepository'); + App::singleton('oauth2\repositories\IApiScopeGroupRepository', 'repositories\EloquentApiScopeGroupRepository'); + } } \ No newline at end of file diff --git a/app/routes.php b/app/routes.php index 9c7f6b19..e8bb587e 100644 --- a/app/routes.php +++ b/app/routes.php @@ -29,7 +29,8 @@ Route::group(array("before" => "ssl"), function () { * the response to make sure that the OP is authorized to make assertions about the Claimed Identifier. */ Route::get("/{identifier}", "UserController@getIdentity"); - Route::get("/accounts/user/ud/{identifier}", "DiscoveryController@user")->where(array('identifier' => '[\d\w\.\#]+')); + Route::get("/accounts/user/ud/{identifier}", + "DiscoveryController@user")->where(array('identifier' => '[\d\w\.\#]+')); //op endpoint url Route::post('/accounts/openid2', 'OpenIdProviderController@endpoint'); @@ -42,16 +43,29 @@ Route::group(array("before" => "ssl"), function () { }); //oauth2 endpoints -Route::group(array('prefix' => 'oauth2', 'before' => 'ssl|oauth2.enabled'), function() -{ - //authorization endpoint - Route::any('/auth',"OAuth2ProviderController@authorize"); +Route::group( array( 'before' => 'ssl|oauth2.enabled' ), function () { + Route::get('/.well-known/openid-configuration',"OAuth2ProviderController@discovery"); +}); + +Route::group(array('prefix' => 'oauth2', 'before' => 'ssl|oauth2.enabled'), function () { + Route::get('/check-session',"OAuth2ProviderController@checkSessionIFrame"); + Route::get('/end-session',"OAuth2ProviderController@endSession"); + Route::get('/end-session/cancel',"OAuth2ProviderController@cancelLogout"); + Route::post('/end-session',"OAuth2ProviderController@endSession"); + + //authorization endpoint + Route::any('/auth', "OAuth2ProviderController@authorize"); + // OIDC + // certificates + Route::get('/certs',"OAuth2ProviderController@certs"); + // discovery document + Route::get('/.well-known/openid-configuration',"OAuth2ProviderController@discovery"); //token endpoint - Route::group(array('prefix' => 'token'), function(){ - Route::post('/',"OAuth2ProviderController@token"); - Route::post('/revoke',"OAuth2ProviderController@revoke"); - Route::post('/introspection',"OAuth2ProviderController@introspection"); + Route::group(array('prefix' => 'token'), function () { + Route::post('/', "OAuth2ProviderController@token"); + Route::post('/revoke', "OAuth2ProviderController@revoke"); + Route::post('/introspection', "OAuth2ProviderController@introspection"); }); }); @@ -62,129 +76,169 @@ Route::group(array("before" => array("ssl", "auth")), function () { Route::any("/accounts/user/profile", "UserController@getProfile"); Route::any("/accounts/user/profile/trusted_site/delete/{id}", "UserController@deleteTrustedSite"); Route::post('/accounts/user/profile/update', 'UserController@postUserProfileOptions'); - }); +}); -Route::group(array('prefix' => 'admin','before' => 'ssl|auth'), function(){ +Route::group(array('prefix' => 'admin', 'before' => 'ssl|auth'), function () { //client admin UI - Route::get('clients/edit/{id}',array('before' => 'oauth2.enabled|user.owns.client.policy', 'uses' => 'AdminController@editRegisteredClient')); - Route::get('clients',array('before' => 'oauth2.enabled', 'uses' => 'AdminController@listOAuth2Clients')); + Route::get('clients/edit/{id}', + array('before' => 'oauth2.enabled|user.owns.client.policy', 'uses' => 'AdminController@editRegisteredClient')); + Route::get('clients', array('before' => 'oauth2.enabled', 'uses' => 'AdminController@listOAuth2Clients')); - Route::get('/grants',array('before' => 'oauth2.enabled', 'uses' => 'AdminController@editIssuedGrants')); - //oauth2 server admin UI - Route::group(array('before' => 'oauth2.enabled|oauth2.server.admin'), function(){ - Route::get('/resource-servers','AdminController@listResourceServers'); - Route::get('/resource-server/{id}','AdminController@editResourceServer'); - Route::get('/api/{id}','AdminController@editApi'); - Route::get('/scope/{id}','AdminController@editScope'); - Route::get('/endpoint/{id}','AdminController@editEndpoint'); - Route::get('/locked-clients','AdminController@listLockedClients'); + Route::get('/grants', array('before' => 'oauth2.enabled', 'uses' => 'AdminController@editIssuedGrants')); + //oauth2 server admin UI + Route::group(array('before' => 'oauth2.enabled|oauth2.server.admin'), function () { + + Route::get('/api-scope-groups', 'AdminController@listApiScopeGroups'); + Route::get('/api-scope-groups/{id}', 'AdminController@editApiScopeGroup'); + Route::get('/resource-servers', 'AdminController@listResourceServers'); + Route::get('/resource-server/{id}', 'AdminController@editResourceServer'); + Route::get('/api/{id}', 'AdminController@editApi'); + Route::get('/scope/{id}', 'AdminController@editScope'); + Route::get('/endpoint/{id}', 'AdminController@editEndpoint'); + Route::get('/locked-clients', 'AdminController@listLockedClients'); + // server private keys + Route::get('/private-keys', 'AdminController@listServerPrivateKeys'); }); - Route::group(array('before' => 'openstackid.server.admin'), function(){ - Route::get('/locked-users','AdminController@listLockedUsers'); - Route::get('/server-config','AdminController@listServerConfig'); - Route::post('/server-config','AdminController@saveServerConfig'); - Route::get('/banned-ips','AdminController@listBannedIPs'); + Route::group(array('before' => 'openstackid.server.admin'), function () { + Route::get('/locked-users', 'AdminController@listLockedUsers'); + Route::get('/server-config', 'AdminController@listServerConfig'); + Route::post('/server-config', 'AdminController@saveServerConfig'); + Route::get('/banned-ips', 'AdminController@listBannedIPs'); }); }); //Admin Backend API -Route::group(array('prefix' => 'admin/api/v1', 'before' => 'ssl|auth'), function() -{ - - Route::group(array('prefix' => 'users'), function(){ - Route::delete('/{id}/locked',array('before' => 'openstackid.server.admin.json', 'uses' => 'UserApiController@unlock')); - Route::delete('/{id}/token/{value}',array('before' => 'is.current.user', 'uses' => 'UserApiController@revokeToken')); +Route::group(array('prefix' => 'admin/api/v1', 'before' => 'ssl|auth'), function () { + Route::group(array('prefix' => 'users'), function () { + Route::delete('/{id}/locked', + array('before' => 'openstackid.server.admin.json', 'uses' => 'UserApiController@unlock')); + Route::delete('/{id}/token/{value}', + array('before' => 'is.current.user', 'uses' => 'UserApiController@revokeToken')); }); - Route::group(array('prefix' => 'banned-ips', 'before' => 'openstackid.server.admin.json'), function(){ - Route::get('/{id}',"ApiBannedIPController@get"); - Route::get('/',"ApiBannedIPController@getByPage"); - Route::delete('/{id?}',"ApiBannedIPController@delete"); + Route::group(array('prefix' => 'banned-ips', 'before' => 'openstackid.server.admin.json'), function () { + Route::get('/{id}', "ApiBannedIPController@get"); + Route::get('/', "ApiBannedIPController@getByPage"); + Route::delete('/{id?}', "ApiBannedIPController@delete"); }); //client api - Route::group(array('prefix' => 'clients'), function(){ + Route::group(array('prefix' => 'clients'), function () { + + // public keys + Route::post('/{id}/public_keys', array('before' => 'user.owns.client.policy', 'uses' => 'ClientPublicKeyApiController@create')); + Route::get('/{id}/public_keys', array('before' => 'user.owns.client.policy', 'uses' => 'ClientPublicKeyApiController@getByPage')); + Route::delete('/{id}/public_keys/{public_key_id}', array('before' => 'user.owns.client.policy', 'uses' => 'ClientPublicKeyApiController@delete')); + Route::put('/{id}/public_keys/{public_key_id}', array('before' => 'user.owns.client.policy', 'uses' => 'ClientPublicKeyApiController@update')); Route::post('/', array('before' => 'is.current.user', 'uses' => 'ClientApiController@create')); - Route::get('/',array('before' => 'is.current.user', 'uses' => 'ClientApiController@getByPage')); - Route::delete('/{id}',array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@delete')); - + Route::put('/', array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@update')); + Route::get('/{id}', "ClientApiController@get"); + Route::get('/', array('before' => 'is.current.user', 'uses' => 'ClientApiController@getByPage')); + Route::delete('/{id}', array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@delete')); //allowed redirect uris endpoints - Route::get('/{id}/uris',array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@getRegisteredUris')); - Route::post('/{id}/uris',array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@addAllowedRedirectUri')); - Route::delete('/{id}/uris/{uri_id}',array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@deleteClientAllowedUri')); + Route::get('/{id}/uris', array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@getRegisteredUris')); + Route::post('/{id}/uris', array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@addAllowedRedirectUri')); + Route::delete('/{id}/uris/{uri_id}', array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@deleteClientAllowedUri')); //allowed origin endpoints endpoints - Route::get('/{id}/origins',array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@geAllowedOrigins')); - Route::post('/{id}/origins',array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@addAllowedOrigin')); - Route::delete('/{id}/origins/{origin_id}',array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@deleteClientAllowedOrigin')); - - Route::delete('/{id}/lock',array('before' => 'openstackid.server.admin.json', 'uses' => 'ClientApiController@unlock')); - Route::put('/{id}/secret',array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@regenerateClientSecret')); - Route::put('/{id}/use-refresh-token',array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@setRefreshTokenClient')); - Route::put('/{id}/rotate-refresh-token',array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@setRotateRefreshTokenPolicy')); - Route::get('/{id}/access-token',array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@getAccessTokens')); - Route::get('/{id}/refresh-token',array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@getRefreshTokens')); - Route::delete('/{id}/token/{value}/{hint}',array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@revokeToken')); - Route::put('/{id}/scopes/{scope_id}',array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@addAllowedScope')); - Route::delete('/{id}/scopes/{scope_id}',array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@removeAllowedScope')); - - Route::put('/{id}/active',array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@activate')); - Route::delete('/{id}/active',array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@deactivate')); + Route::get('/{id}/origins', array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@geAllowedOrigins')); + Route::post('/{id}/origins', array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@addAllowedOrigin')); + Route::delete('/{id}/origins/{origin_id}', array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@deleteClientAllowedOrigin')); + Route::delete('/{id}/lock', array('before' => 'openstackid.server.admin.json', 'uses' => 'ClientApiController@unlock')); + Route::put('/{id}/secret', array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@regenerateClientSecret')); + Route::put('/{id}/use-refresh-token', array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@setRefreshTokenClient')); + Route::put('/{id}/rotate-refresh-token', array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@setRotateRefreshTokenPolicy')); + Route::get('/{id}/access-token', array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@getAccessTokens')); + Route::get('/{id}/refresh-token', array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@getRefreshTokens')); + Route::delete('/{id}/token/{value}/{hint}', array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@revokeToken')); + Route::put('/{id}/scopes/{scope_id}', array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@addAllowedScope')); + Route::delete('/{id}/scopes/{scope_id}', array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@removeAllowedScope')); + Route::put('/{id}/active', array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@activate')); + Route::delete('/{id}/active', array('before' => 'user.owns.client.policy', 'uses' => 'ClientApiController@deactivate')); }); - Route::group(array('prefix' => 'resource-servers', 'before' => 'oauth2.server.admin.json'), function(){ - Route::get('/{id}',"ApiResourceServerController@get"); - Route::get('/',"ApiResourceServerController@getByPage"); - Route::post('/',"ApiResourceServerController@create"); - Route::delete('/{id}',"ApiResourceServerController@delete"); - Route::put('/',"ApiResourceServerController@update"); - Route::put('/{id}/client-secret',"ApiResourceServerController@regenerateClientSecret"); - Route::put('/{id}/active',"ApiResourceServerController@activate"); - Route::delete('/{id}/active',"ApiResourceServerController@deactivate"); + // resouce servers + Route::group(array('prefix' => 'resource-servers', 'before' => 'oauth2.server.admin.json'), function () { + Route::get('/{id}', "ApiResourceServerController@get"); + Route::get('/', "ApiResourceServerController@getByPage"); + Route::post('/', "ApiResourceServerController@create"); + Route::delete('/{id}', "ApiResourceServerController@delete"); + Route::put('/', "ApiResourceServerController@update"); + Route::put('/{id}/client-secret', "ApiResourceServerController@regenerateClientSecret"); + Route::put('/{id}/active', "ApiResourceServerController@activate"); + Route::delete('/{id}/active', "ApiResourceServerController@deactivate"); }); - Route::group(array('prefix' => 'apis', 'before' => 'oauth2.server.admin.json'), function(){ - Route::get('/{id}',"ApiController@get"); - Route::get('/',"ApiController@getByPage"); - Route::post('/',"ApiController@create"); - Route::delete('/{id}',"ApiController@delete"); - Route::put('/',"ApiController@update"); - Route::put('/{id}/active',"ApiController@activate"); - Route::delete('/{id}/active',"ApiController@deactivate"); + // api scope groups + Route::group(array('prefix' => 'api-scope-groups', 'before' => 'oauth2.server.admin.json'), function () { + Route::get('/{id}', "ApiScopeGroupController@get"); + Route::get('/', "ApiScopeGroupController@getByPage"); + Route::put('/', "ApiScopeGroupController@update"); + Route::post('/', "ApiScopeGroupController@create"); + Route::delete('/{id}', "ApiScopeGroupController@delete"); + Route::put('/{id}/active', "ApiScopeGroupController@activate"); + Route::delete('/{id}/active', "ApiScopeGroupController@deactivate"); + Route::get('/users', "ApiScopeGroupController@fetchUsers"); }); - Route::group(array('prefix' => 'scopes', 'before' => 'oauth2.server.admin.json'), function(){ - Route::get('/{id}',"ApiScopeController@get"); - Route::get('/',"ApiScopeController@getByPage"); - Route::post('/',"ApiScopeController@create"); - Route::delete('/{id}',"ApiScopeController@delete"); - Route::put('/',"ApiScopeController@update"); - Route::put('/{id}/active',"ApiScopeController@activate"); - Route::delete('/{id}/active',"ApiScopeController@deactivate"); + // apis + Route::group(array('prefix' => 'apis', 'before' => 'oauth2.server.admin.json'), function () { + Route::get('/{id}', "ApiController@get"); + Route::get('/', "ApiController@getByPage"); + Route::post('/', "ApiController@create"); + Route::delete('/{id}', "ApiController@delete"); + Route::put('/', "ApiController@update"); + Route::put('/{id}/active', "ApiController@activate"); + Route::delete('/{id}/active', "ApiController@deactivate"); }); - Route::group(array('prefix' => 'endpoints', 'before' => 'oauth2.server.admin.json'), function(){ - Route::get('/{id}',"ApiEndpointController@get"); - Route::get('/',"ApiEndpointController@getByPage"); - Route::post('/',"ApiEndpointController@create"); - Route::delete('/{id}',"ApiEndpointController@delete"); - Route::put('/',"ApiEndpointController@update"); - Route::put('/{id}/scope/{scope_id}',"ApiEndpointController@addRequiredScope"); - Route::delete('/{id}/scope/{scope_id}',"ApiEndpointController@removeRequiredScope"); - Route::put('/{id}/active',"ApiEndpointController@activate"); - Route::delete('/{id}/active',"ApiEndpointController@deactivate"); + // scopes + Route::group(array('prefix' => 'scopes', 'before' => 'oauth2.server.admin.json'), function () { + Route::get('/{id}', "ApiScopeController@get"); + Route::get('/', "ApiScopeController@getByPage"); + Route::post('/', "ApiScopeController@create"); + Route::delete('/{id}', "ApiScopeController@delete"); + Route::put('/', "ApiScopeController@update"); + Route::put('/{id}/active', "ApiScopeController@activate"); + Route::delete('/{id}/active', "ApiScopeController@deactivate"); }); + + // endpoints + Route::group(array('prefix' => 'endpoints', 'before' => 'oauth2.server.admin.json'), function () { + Route::get('/{id}', "ApiEndpointController@get"); + Route::get('/', "ApiEndpointController@getByPage"); + Route::post('/', "ApiEndpointController@create"); + Route::delete('/{id}', "ApiEndpointController@delete"); + Route::put('/', "ApiEndpointController@update"); + Route::put('/{id}/scope/{scope_id}', "ApiEndpointController@addRequiredScope"); + Route::delete('/{id}/scope/{scope_id}', "ApiEndpointController@removeRequiredScope"); + Route::put('/{id}/active', "ApiEndpointController@activate"); + Route::delete('/{id}/active', "ApiEndpointController@deactivate"); + }); + + // private keys + Route::group(array('prefix' => 'private-keys', 'before' => 'oauth2.server.admin.json'), function () { + Route::get('/', "ServerPrivateKeyApiController@getByPage"); + Route::post('/', "ServerPrivateKeyApiController@create"); + Route::delete('/{id}', "ServerPrivateKeyApiController@delete"); + Route::put('/{id}', "ServerPrivateKeyApiController@update"); + }); + }); //OAuth2 Protected API -Route::group(array('prefix' => 'api/v1', +Route::group(array( + 'prefix' => 'api/v1', 'before' => 'ssl|oauth2.enabled|oauth2.protected.endpoint', - 'after' => ''), function() -{ - Route::group(array('prefix' => 'users'), function(){ - Route::get('/me','OAuth2UserApiController@me'); + 'after' => '' +), function () { + + Route::group(array('prefix' => 'users'), function () { + Route::get('/me', 'OAuth2UserApiController@me'); + Route::get('/info', 'OAuth2UserApiController@userInfo'); + Route::post('/info', 'OAuth2UserApiController@userInfo'); }); }); \ No newline at end of file diff --git a/app/services/exceptions/ValidationException.php b/app/services/exceptions/ValidationException.php new file mode 100644 index 00000000..6841e3a6 --- /dev/null +++ b/app/services/exceptions/ValidationException.php @@ -0,0 +1,17 @@ +first(); - } - - /** - * @param $uri - * @param $client_id - * @return bool|int - */ - public function create($uri, $client_id) - { - $origin = new ClientAllowedOrigin(); - $origin->allowed_origin = $uri; - $client = Client::find($client_id); - if(!is_null($client)){ - $client->allowed_origins()->save($origin); - $origin->Save(); - return $origin->id; - } - return false; - } - - /** - * @param $id - * @return bool - */ - public function delete($id) - { - $origin = $this->get($id); - if(!is_null($origin)){ - return $origin->delete(); - } - return false; - } - - /** - * @param $uri - * @return bool - */ - public function deleteByUri($uri) - { - $origin = $this->getByUri($uri); - if(!is_null($origin)){ - return $origin->delete(); - } - return false; - } -} \ No newline at end of file diff --git a/app/services/oauth2/ApiScopeGroupService.php b/app/services/oauth2/ApiScopeGroupService.php new file mode 100644 index 00000000..68d873f9 --- /dev/null +++ b/app/services/oauth2/ApiScopeGroupService.php @@ -0,0 +1,203 @@ +log_service = $log_service; + $this->repository = $repository; + $this->user_repository = $user_repository; + $this->scope_service = $scope_service; + $this->tx_service = $tx_service; + } + + public function update($id, array $params) + { + $repository = $this->repository; + $scope_service = $this->scope_service; + $user_repository = $this->user_repository; + + return $this->tx_service->transaction(function () use ($id, $params, $repository, $scope_service, $user_repository) { + + $group = ApiScopeGroup::find($id); + + if (is_null($group)) + { + throw new InvalidApiScopeGroup(sprintf('api scope group id %s does not exists!', $id)); + } + + $allowed_update_params = array('name', 'active', 'description', 'users', 'scopes'); + + foreach ($allowed_update_params as $param) + { + if (array_key_exists($param, $params)) + { + + if ($param == 'name') + { + if (ApiScopeGroup::where('name', '=', $params[$param])->where('id', '<>', $id)->count() > 0) + { + throw new InvalidApiScopeGroup(sprintf('there is already another api scope group name (%s).', $params[$param])); + } + } + if($param === 'scopes') + { + $ids = $group->scopes()->getRelatedIds(); + $group->scopes()->detach($ids); + $scopes = explode(',', $params['scopes']); + foreach($scopes as $scope_id) + { + $scope = $scope_service->get(intval($scope_id)); + if(is_null($scope)) throw new EntityNotFoundException(sprintf('scope %s not found.',$scope_id)); + $group->addScope($scope); + } + } + else if($param === 'users'){ + $ids = $group->users()->getRelatedIds(); + $group->users()->detach($ids); + $users = explode(',', $params['users']); + foreach($users as $user_id) + { + $user = $user_repository->get(intval($user_id)); + if(is_null($user)) throw new EntityNotFoundException(sprintf('user %s not found.',$user_id)); + $group->addUser($user); + } + } + else + $group->{$param} = $params[$param]; + } + } + $repository->add($group); + return true; + }); + } + + /** + * @param int $id + * @param bool $status status (active/non active) + * @return void + */ + public function setStatus($id, $status) + { + return ApiScopeGroup::find($id)->update(array('active' => $status)); + } + + /** + * @param string $name + * @param bool $active + * @param string $scopes + * @param string $users + * @return IApiScopeGroup + */ + public function register($name, $active, $scopes, $users) + { + $repository = $this->repository; + $scope_service = $this->scope_service; + $user_repository = $this->user_repository; + + return $this->tx_service->transaction(function () use ( + $name, + $active, + $scopes, + $users, + $repository, + $scope_service, + $user_repository + ) { + + if (ApiScopeGroup::where('name', '=', trim($name))->count() > 0) + { + throw new InvalidApiScopeGroup(sprintf('there is already another group with that name (%s).', $name)); + } + + $repository->add($instance = new ApiScopeGroup + ( + array + ( + 'name' => trim($name), + 'active' => $active, + 'description' => '' + ) + )); + + $scopes = explode(',', $scopes); + $users = explode(',', $users); + foreach($scopes as $scope_id) + { + $scope = $scope_service->get(intval($scope_id)); + if(is_null($scope)) throw new EntityNotFoundException(sprintf('scope %s not found.',$scope_id)); + $instance->addScope($scope); + } + foreach($users as $user_id) + { + $user = $user_repository->get(intval($user_id)); + if(is_null($user)) throw new EntityNotFoundException(sprintf('user %s not found.',$user_id)); + $instance->addUser($user); + } + return $instance; + }); + } +} \ No newline at end of file diff --git a/app/services/oauth2/ApiScopeService.php b/app/services/oauth2/ApiScopeService.php index dd979417..29a9c59a 100644 --- a/app/services/oauth2/ApiScopeService.php +++ b/app/services/oauth2/ApiScopeService.php @@ -2,28 +2,31 @@ namespace services\oauth2; +use Api; +use ApiScope; +use DB; use oauth2\exceptions\InvalidApi; use oauth2\exceptions\InvalidApiScope; use oauth2\models\IApiScope; use oauth2\services\IApiScopeService; -use ApiScope; -use Api; -use DB; use utils\db\ITransactionService; + /** * Class ApiScopeService * @package services\oauth2 */ -class ApiScopeService implements IApiScopeService { +final class ApiScopeService implements IApiScopeService +{ - private $tx_service; + private $tx_service; - /** - * @param ITransactionService $tx_service - */ - public function __construct(ITransactionService $tx_service){ - $this->tx_service = $tx_service; - } + /** + * @param ITransactionService $tx_service + */ + public function __construct(ITransactionService $tx_service) + { + $this->tx_service = $tx_service; + } /** * @param array $scopes_names @@ -31,35 +34,45 @@ class ApiScopeService implements IApiScopeService { */ public function getScopesByName(array $scopes_names) { - return ApiScope::where('active','=',true)->whereIn('name',$scopes_names)->get(); + return ApiScope::where('active', '=', true)->whereIn('name', $scopes_names)->get(); } /** * @param array $scopes_names * @return mixed */ - public function getFriendlyScopesByName(array $scopes_names){ - return DB::table('oauth2_api_scope')->where('active','=',true)->whereIn('name',$scopes_names)->lists('short_description'); + public function getFriendlyScopesByName(array $scopes_names) + { + return DB::table('oauth2_api_scope')->where('active', '=', true)->whereIn('name', + $scopes_names)->lists('short_description'); } /** * @param bool $system + * @param bool $assigned_by_groups * @return array|mixed */ - public function getAvailableScopes($system=false){ - $scopes = ApiScope - ::with('api') + public function getAvailableScopes($system = false, $assigned_by_groups = false) + { + $scopes = ApiScope + ::with('api') ->with('api.resource_server') - ->where('active','=',true) + ->where('active', '=', true) ->orderBy('api_id')->get(); $res = array(); - foreach($scopes as $scope){ + foreach ($scopes as $scope) + { $api = $scope->api()->first(); - if(!is_null($api) && $api->resource_server()->first()->active && $api->active){ - if($scope->system && !$system) continue; - array_push($res,$scope); + if (!is_null($api) && $api->resource_server()->first()->active && $api->active) { + if ($scope->system && !$system) { + continue; + } + if ($scope->assigned_by_groups && !$assigned_by_groups) { + continue; + } + array_push($res, $scope); } } @@ -70,16 +83,18 @@ class ApiScopeService implements IApiScopeService { * @param array $scopes_names * @return array|mixed */ - public function getAudienceByScopeNames(array $scopes_names){ + public function getAudienceByScopeNames(array $scopes_names) + { $scopes = $this->getScopesByName($scopes_names); $audience = array(); - foreach($scopes as $scope){ + foreach ($scopes as $scope) { $api = $scope->api()->first(); - $resource_server = !is_null($api)? $api->resource_server()->first():null; - if(!is_null($resource_server) && !array_key_exists($resource_server->host, $audience)){ + $resource_server = !is_null($api) ? $api->resource_server()->first() : null; + if (!is_null($resource_server) && !array_key_exists($resource_server->host, $audience)) { $audience[$resource_server->host] = $resource_server->ip; } } + return $audience; } @@ -87,13 +102,15 @@ class ApiScopeService implements IApiScopeService { * @param array $scopes_names * @return string */ - public function getStrAudienceByScopeNames(array $scopes_names){ + public function getStrAudienceByScopeNames(array $scopes_names) + { $audiences = $this->getAudienceByScopeNames($scopes_names); - $audience = ''; - foreach($audiences as $resource_server_host => $ip){ - $audience = $audience . $resource_server_host .' '; + $audience = ''; + foreach ($audiences as $resource_server_host => $ip) { + $audience = $audience . $resource_server_host . ' '; } - $audience = trim($audience); + $audience = trim($audience); + return $audience; } @@ -114,10 +131,11 @@ class ApiScopeService implements IApiScopeService { * @param array $fields * @return mixed */ - public function getAll($page_nbr=1,$page_size=10, array $filters=array(), array $fields=array('*')) + public function getAll($page_nbr = 1, $page_size = 10, array $filters = array(), array $fields = array('*')) { DB::getPaginator()->setCurrentPage($page_nbr); - return ApiScope::Filter($filters)->paginate($page_size,$fields); + + return ApiScope::Filter($filters)->paginate($page_size, $fields); } /** @@ -126,9 +144,10 @@ class ApiScopeService implements IApiScopeService { */ public function save(IApiScope $scope) { - if(!$scope->exists() || count($scope->getDirty())>0){ + if (!$scope->exists() || count($scope->getDirty()) > 0) { return $scope->Save(); } + return true; } @@ -140,25 +159,27 @@ class ApiScopeService implements IApiScopeService { */ public function update($id, array $params) { - $res = false; - $this_var = $this; + $res = false; + $this_var = $this; - $this->tx_service->transaction(function () use ($id,$params,&$res,&$this_var) { + $this->tx_service->transaction(function () use ($id, $params, &$res, &$this_var) { //check that scope exists... $scope = ApiScope::find($id); - if(is_null($scope)) - throw new InvalidApiScope(sprintf('scope id %s does not exists!',$id)); + if (is_null($scope)) { + throw new InvalidApiScope(sprintf('scope id %s does not exists!', $id)); + } - $allowed_update_params = array('name','description','short_description','active','system','default'); + $allowed_update_params = array('name', 'description', 'short_description', 'active', 'system', 'default', 'assigned_by_groups'); - foreach($allowed_update_params as $param){ - if(array_key_exists($param,$params)){ + foreach ($allowed_update_params as $param) { + if (array_key_exists($param, $params)) { - if($param=='name'){ + if ($param == 'name') { //check if we have a former scope with selected name - if(ApiScope::where('name','=',$params[$param])->where('id','<>',$id)->count()>0) - throw new InvalidApiScope(sprintf('scope name %s already exists!',$params[$param])); + if (ApiScope::where('name', '=', $params[$param])->where('id', '<>', $id)->count() > 0) { + throw new InvalidApiScope(sprintf('scope name %s already exists!', $params[$param])); + } } $scope->{$param} = $params[$param]; @@ -166,6 +187,7 @@ class ApiScopeService implements IApiScopeService { } $res = $this_var->save($scope); }); + return $res; } @@ -178,11 +200,12 @@ class ApiScopeService implements IApiScopeService { */ public function setStatus($id, $active) { - $scope = ApiScope::find($id); - if(is_null($scope)) - throw new InvalidApiScope(sprintf('scope id %s does not exists!',$id)); + $scope = ApiScope::find($id); + if (is_null($scope)) { + throw new InvalidApiScope(sprintf('scope id %s does not exists!', $id)); + } - return $scope->update(array('active'=>$active)); + return $scope->update(array('active' => $active)); } /** @@ -193,14 +216,16 @@ class ApiScopeService implements IApiScopeService { public function delete($id) { $res = false; - $this->tx_service->transaction(function () use ($id,&$res) { + $this->tx_service->transaction(function () use ($id, &$res) { $scope = ApiScope::find($id); - if(is_null($scope)) - throw new InvalidApiScope(sprintf('scope id %s does not exists!',$id)); + if (is_null($scope)) { + throw new InvalidApiScope(sprintf('scope id %s does not exists!', $id)); + } $res = $scope->delete(); }); + return $res; } @@ -213,43 +238,85 @@ class ApiScopeService implements IApiScopeService { * @param $default * @param $system * @param $api_id + * @param $assigned_by_groups * @throws \oauth2\exceptions\InvalidApi * @return IApiScope */ - public function add($name, $short_description, $description, $active, $default, $system, $api_id) + public function add($name, $short_description, $description, $active, $default, $system, $api_id, $assigned_by_groups) { $instance = null; - $this->tx_service->transaction(function () use ($name, $short_description, $description, $active, $default, $system, $api_id, &$instance) { + $this->tx_service->transaction(function () use ( + $name, + $short_description, + $description, + $active, + $default, + $system, + $api_id, + &$instance + ) { // check if api exists... - if(is_null(Api::find($api_id))) - throw new InvalidApi(sprintf('api id %s does not exists!.',$api_id)); + if (is_null(Api::find($api_id))) { + throw new InvalidApi(sprintf('api id %s does not exists!.', $api_id)); + } //check if we have a former scope with selected name - if(ApiScope::where('name','=',$name)->count()>0) - throw new InvalidApiScope(sprintf('scope name %s not allowed.',$name)); + if (ApiScope::where('name', '=', $name)->count() > 0) { + throw new InvalidApiScope(sprintf('scope name %s not allowed.', $name)); + } - $instance = new ApiScope( - array( - 'name' => $name, - 'description' => $description, - 'short_description' => $short_description, - 'active' => $active, - 'default' => $default, - 'system' => $system, - 'api_id' => $api_id + $instance = new ApiScope + ( + array + ( + 'name' => $name, + 'description' => $description, + 'short_description' => $short_description, + 'active' => $active, + 'default' => $default, + 'system' => $system, + 'api_id' => $api_id, + 'assigned_by_groups' => $assigned_by_groups ) ); $instance->Save(); }); + return $instance; } /** * @return mixed */ - public function getDefaultScopes(){ - return $scopes = ApiScope::where('default','=',true)->where('active','=',true)->get(array('id')); + public function getDefaultScopes() + { + return $scopes = ApiScope::where('default', '=', true)->where('active', '=', true)->get(array('id')); + } + + /** + * @return mixed + */ + public function getAssignedByGroups() + { + $scopes = ApiScope + ::with('api') + ->with('api.resource_server') + ->where('active', '=', true) + ->where('assigned_by_groups', '=', true) + ->orderBy('api_id')->get(); + + $res = array(); + + foreach ($scopes as $scope) + { + $api = $scope->api()->first(); + if (!is_null($api) && $api->resource_server()->first()->active && $api->active) { + array_push($res, $scope); + } + } + + return $res; } } \ No newline at end of file diff --git a/app/services/oauth2/AssymetricKeyService.php b/app/services/oauth2/AssymetricKeyService.php new file mode 100644 index 00000000..28b9c28b --- /dev/null +++ b/app/services/oauth2/AssymetricKeyService.php @@ -0,0 +1,112 @@ +tx_service = $tx_service; + $this->repository = $repository; + } + + /** + * @param array $params + * @return IAssymetricKey + */ + abstract public function register(array $params); + + + /** + * @param int $key_id + * @return bool + */ + public function delete($key_id) + { + $repository = $this->repository; + + return $this->tx_service->transaction(function() use($key_id, $repository) + { + + $key = $repository->getById($key_id); + if(!$key) return false; + $repository->delete($key); + return true; + }); + } + + /** + * @param int $key_id + * @param array $params + * @return bool + */ + public function update($key_id, array $params) + { + $repository = $this->repository; + + return $this->tx_service->transaction(function () use ($key_id, $params, $repository) { + + $key = $repository->getById($key_id); + + if (is_null($key)) + { + return false; + } + + $owner_id = $key->oauth2_client_id; + + $key_active = $repository->getActiveByCriteria($key->getType(), $key->getUse(), $key->getAlg()->getName()); + + if($key_active && $params['active'] === true) + { + $key_active->active = false; + $repository->add($key_active); + } + + $allowed_update_params = array + ( + 'active', + ); + + foreach ($allowed_update_params as $param) { + if (array_key_exists($param, $params)) { + $key->{$param} = $params[$param]; + } + } + + $repository->add($key); + return true; + }); + } + + +} \ No newline at end of file diff --git a/app/services/oauth2/AuthorizationCodeRedeemPolicy.php b/app/services/oauth2/AuthorizationCodeRedeemPolicy.php index 0162ef59..e02497f1 100644 --- a/app/services/oauth2/AuthorizationCodeRedeemPolicy.php +++ b/app/services/oauth2/AuthorizationCodeRedeemPolicy.php @@ -1,26 +1,32 @@ getAuthCode(); - $this->counter_measure->trigger(array('auth_code'=>$auth_code)); + + if ($ex instanceof ReplayAttackException) { + $token = $ex->getToken(); + $this->counter_measure->trigger + ( + array + ( + 'auth_code' => $token + ) + ); } } catch (Exception $ex) { Log::error($ex); diff --git a/app/services/oauth2/CORS/CORSMiddleware.php b/app/services/oauth2/CORS/CORSMiddleware.php index d7b04376..5b6aac5a 100644 --- a/app/services/oauth2/CORS/CORSMiddleware.php +++ b/app/services/oauth2/CORS/CORSMiddleware.php @@ -2,30 +2,40 @@ namespace services\oauth2\CORS; +use App; +use Config; +use Exception; +use Log; use oauth2\models\IApiEndpoint; -use oauth2\services\IAllowedOriginService; +use oauth2\services\IClientService; use oauth2\services\IApiEndpointService; +use Route; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use utils\services\ICacheService; -use Route; -use App; -use Log; -use Exception; -use Config; -use Illuminate\Support\Facades\Cache; + /** * Class CORSMiddleware * @package services\oauth2 * Implementation of http://www.w3.org/TR/cors/ */ -class CORSMiddleware { +final class CORSMiddleware +{ + /** + * @var IApiEndpointService + */ private $endpoint_service; + /** + * @var ICacheService + */ private $cache_service; - private $origin_service; + /** + * @var IClientService + */ + private $client_service; private $actual_request = false; - private $headers = array(); + private $headers = array(); private $allowed_headers; private $allowed_methods; /** @@ -41,16 +51,24 @@ class CORSMiddleware { const DefaultAllowedHeaders = 'origin, content-type, accept, authorization, x-requested-with'; const DefaultAllowedMethods = 'GET, POST, OPTIONS, PUT, DELETE'; - public function __construct(IApiEndpointService $endpoint_service, - ICacheService $cache_service, - IAllowedOriginService $origin_service) + /** + * @param IApiEndpointService $endpoint_service + * @param ICacheService $cache_service + * @param IClientService $client_service + */ + public function __construct + ( + IApiEndpointService $endpoint_service, + ICacheService $cache_service, + IClientService $client_service + ) { - $this->endpoint_service = $endpoint_service; - $this->cache_service = $cache_service; - $this->origin_service = $origin_service; + $this->endpoint_service = $endpoint_service; + $this->cache_service = $cache_service; + $this->client_service = $client_service; - $this->allowed_headers = Config::get('cors.AllowedHeaders',self::DefaultAllowedHeaders); - $this->allowed_methods = Config::get('cors.AllowedMethods',self::DefaultAllowedMethods); + $this->allowed_headers = Config::get('cors.AllowedHeaders', self::DefaultAllowedHeaders); + $this->allowed_methods = Config::get('cors.AllowedMethods', self::DefaultAllowedMethods); } /** @@ -60,20 +78,21 @@ class CORSMiddleware { * @param IApiEndpoint $endpoint * @return Response */ - private function makePreflightResponse(Request $request, IApiEndpoint $endpoint){ + private function makePreflightResponse(Request $request, IApiEndpoint $endpoint) + { $response = new Response(); $allow_credentials = Config::get('cors.AllowCredentials', ''); - if(!empty($allow_credentials)){ + if (!empty($allow_credentials)) { // The Access-Control-Allow-Credentials header indicates whether the response to request // can be exposed when the omit credentials flag is unset. When part of the response to a preflight request // it indicates that the actual request can include user credentials. - $response->headers->set('Access-Control-Allow-Credentials',$allow_credentials ); + $response->headers->set('Access-Control-Allow-Credentials', $allow_credentials); } - if(Config::get('cors.UsePreflightCaching', false)){ + if (Config::get('cors.UsePreflightCaching', false)) { // The Access-Control-Max-Age header indicates how long the response can be cached, so that for // subsequent requests, within the specified time, no preflight request has to be made. $response->headers->set('Access-Control-Max-Age', Config::get('cors.MaxAge', 32000)); @@ -85,6 +104,7 @@ class CORSMiddleware { if (!$this->checkOrigin($request)) { $response->headers->set('Access-Control-Allow-Origin', 'null'); $response->setStatusCode(403); + return $response; } @@ -96,6 +116,7 @@ class CORSMiddleware { if ($request->headers->get('Access-Control-Request-Method') != $endpoint->getHttpMethod()) { $response->setStatusCode(405); + return $response; } //The Access-Control-Allow-Methods header indicates, as part of the response to a preflight request, @@ -108,8 +129,8 @@ class CORSMiddleware { $headers = $request->headers->get('Access-Control-Request-Headers'); if ($headers) { - $headers = trim(strtolower($headers)); - $allow_headers = explode(', ',$this->allowed_headers); + $headers = trim(strtolower($headers)); + $allow_headers = explode(', ', $this->allowed_headers); foreach (preg_split('{, *}', $headers) as $header) { //if they are simple headers then skip them if (in_array($header, self::$simple_headers, true)) { @@ -118,13 +139,14 @@ class CORSMiddleware { //check is the requested header is on the list of allowed headers if (!in_array($header, $allow_headers, true)) { $response->setStatusCode(400); - $response->setContent('Unauthorized header '.$header); + $response->setContent('Unauthorized header ' . $header); break; } } } //OK - No Content $response->setStatusCode(204); + return $response; } @@ -132,17 +154,22 @@ class CORSMiddleware { { // check origin $origin = $request->headers->get('Origin'); - if($this->cache_service->getSingleValue($origin)) return true; - if($origin = $this->origin_service->getByUri($origin)){ - $this->cache_service->addSingleValue($origin,$origin); + if ($this->cache_service->getSingleValue($origin)) { return true; } - Log::warning(sprintf('CORS: origin %s not allowed!',$origin)); + if ($client = $this->client_service->getByOrigin($origin)) + { + $this->cache_service->addSingleValue($origin, $origin); + return true; + } + Log::warning(sprintf('CORS: origin %s not allowed!', $origin)); + return false; } - public function verifyRequest($request){ - try{ + public function verifyRequest($request) + { + try { /** * The presence of the Origin header does not necessarily mean that the request is a cross-origin request. * While all cross-origin requests will contain an Origin header, @@ -155,40 +182,42 @@ class CORSMiddleware { return; } //https://www.owasp.org/index.php/CORS_OriginHeaderScrutiny - $origin = $request->headers->get('Origin',null,false); - $host = $request->headers->get('Host',null,false); - if(is_array($origin) && count($origin)>1){ + $origin = $request->headers->get('Origin', null, false); + $host = $request->headers->get('Host', null, false); + if (is_array($origin) && count($origin) > 1) { // If we reach this point it means that we have multiple instance of the "Origin" header $response = new Response(); $response->setStatusCode(403); + return $response; } //now get the first one - $origin = $request->headers->get('Origin'); - $server_name = isset($_SERVER['SERVER_NAME'])?$_SERVER['SERVER_NAME']:null; - $origin_host = @parse_url($origin,PHP_URL_HOST); + $origin = $request->headers->get('Origin'); + $server_name = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null; + $origin_host = @parse_url($origin, PHP_URL_HOST); //Have only one and non empty instance of the host header, - if(is_array($host) && count($host)>1){ + if (is_array($host) && count($host) > 1) { // If we reach this point it means that we have multiple instance of the "Host" header $response = new Response(); $response->setStatusCode(403); + return; } //now get the first one $host = $request->headers->get('Host'); - if(is_null($host) || $server_name != $host || is_null($origin_host) || $origin_host == $server_name){ + if (is_null($host) || $server_name != $host || is_null($origin_host) || $origin_host == $server_name) { return; } - $method = $request->getMethod(); + $method = $request->getMethod(); $preflight = false; //preflight checks if ($method == 'OPTIONS') { - $request_method = $request->headers->get('Access-Control-Request-Method'); - if(!is_null($request_method)){ + $request_method = $request->headers->get('Access-Control-Request-Method'); + if (!is_null($request_method)) { // sets the original method on request in order to be able to find the // correct route $request->setMethod($request_method); @@ -197,34 +226,37 @@ class CORSMiddleware { } //gets routes from container and try to find the route - $router = App::make('router'); - $routes = $router->getRoutes(); - $route = $routes->match($request); - $url = $route->getPath(); + $router = App::make('router'); + $routes = $router->getRoutes(); + $route = $routes->match($request); + $url = $route->getPath(); - if(strpos($url, '/') != 0){ - $url = '/'.$url; + if (strpos($url, '/') != 0) { + $url = '/' . $url; } $endpoint = $this->endpoint_service->getApiEndpointByUrl($url); //check if api endpoint exists or not, if active and if supports cors - if(is_null($endpoint) || !$endpoint->isActive() || !$endpoint->supportCORS()){ + if (is_null($endpoint) || !$endpoint->isActive() || !$endpoint->supportCORS()) { - if(is_null($endpoint)){ - Log::warning(sprintf("does not exists an endpoint for url %s.",$url)); - } - else if(!$endpoint->isActive()){ - Log::warning(sprintf("endpoint %s is not active.",$url)); - } - else if(!$endpoint->supportCORS()){ - Log::warning(sprintf("endpoint %s does not support CORS.",$url)); + if (is_null($endpoint)) { + Log::warning(sprintf("does not exists an endpoint for url %s.", $url)); + } else { + if (!$endpoint->isActive()) { + Log::warning(sprintf("endpoint %s is not active.", $url)); + } else { + if (!$endpoint->supportCORS()) { + Log::warning(sprintf("endpoint %s does not support CORS.", $url)); + } + } } + return; } //perform preflight checks if ($preflight) { - return $this->makePreflightResponse($request,$endpoint); + return $this->makePreflightResponse($request, $endpoint); } //Actual Request if (!$this->checkOrigin($request)) { @@ -234,13 +266,13 @@ class CORSMiddleware { $this->actual_request = true; // Save response headers - $this->headers['Access-Control-Allow-Origin'] = $request->headers->get('Origin'); + $this->headers['Access-Control-Allow-Origin'] = $request->headers->get('Origin'); $allow_credentials = Config::get('cors.AllowCredentials', ''); - if(!empty($allow_credentials)){ + if (!empty($allow_credentials)) { // The Access-Control-Allow-Credentials header indicates whether the response to request // can be exposed when the omit credentials flag is unset. When part of the response to a preflight request // it indicates that the actual request can include user credentials. - $this->headers['Access-Control-Allow-Credentials'] = $allow_credentials ; + $this->headers['Access-Control-Allow-Credentials'] = $allow_credentials; } /** @@ -258,24 +290,24 @@ class CORSMiddleware { * to the client. */ $exposed_headers = Config::get('cors.ExposedHeaders', ''); - if(!empty($exposed_headers)){ - $this->headers['Access-Control-Expose-Headers'] = $exposed_headers ; + if (!empty($exposed_headers)) { + $this->headers['Access-Control-Expose-Headers'] = $exposed_headers; } - } - catch(Exception $ex){ + } catch (Exception $ex) { Log::error($ex); } } public function modifyResponse($request, $response) { - if(!$this->actual_request){ + if (!$this->actual_request) { return $response; } // add CORS response headers Log::info('CORS: Adding CORS HEADERS.'); $response->headers->add($this->headers); + return $response; } diff --git a/app/services/oauth2/CORS/CORSProvider.php b/app/services/oauth2/CORS/CORSProvider.php index b2a4abbf..848c2575 100644 --- a/app/services/oauth2/CORS/CORSProvider.php +++ b/app/services/oauth2/CORS/CORSProvider.php @@ -2,23 +2,25 @@ namespace services\oauth2\CORS; -use Illuminate\Support\ServiceProvider; use App; +use Illuminate\Support\ServiceProvider; -class CORSProvider extends ServiceProvider { +class CORSProvider extends ServiceProvider +{ protected $defer = false; + /** * Register the service provider. - * * @return void */ public function register() { - App::singleton('CORSMiddleware', 'services\oauth2\CORS\CORSMiddleware'); + App::singleton('CORSMiddleware', 'services\oauth2\CORS\CORSMiddleware'); } - public function boot(){ + public function boot() + { } diff --git a/app/services/oauth2/ClienPublicKeyService.php b/app/services/oauth2/ClienPublicKeyService.php new file mode 100644 index 00000000..deccc0b7 --- /dev/null +++ b/app/services/oauth2/ClienPublicKeyService.php @@ -0,0 +1,102 @@ +client_repository = $client_repository; + parent::__construct($repository, $tx_service); + } + + /** + * @param array $params + * @return IAssymetricKey + */ + public function register(array $params) + { + $client_repository = $this->client_repository; + $repository = $this->repository; + + return $this->tx_service->transaction(function() use($params, $repository, $client_repository) + { + + if ($repository->getByPEM($params['pem_content'])) + { + throw new ValidationException('public key already exists on another client, choose another one!.'); + } + + $client = $client_repository->get(intval($params['client_id'])); + + if(is_null($client)) + throw new ValidationException('client does not exits!'); + + $existent_kid = $client->public_keys()->where('kid','=', $params['kid'])->first(); + + if ($existent_kid) + { + throw new ValidationException('public key identifier (kid) already exists!.'); + } + + $old_key_active = $client->public_keys() + ->where('type','=', $params['type']) + ->where('usage','=', $params['usage']) + ->where('alg','=', $params['alg']) + ->where('valid_from','<=',new \DateTime($params['valid_to'])) + ->where('valid_to','>=', new \DateTime($params['valid_from'])) + ->first(); + + $public_key = ClientPublicKey::buildFromPEM + ( + $params['kid'], + $params['type'], + $params['usage'], + $params['pem_content'], + $params['alg'], + $old_key_active ? false : $params['active'], + new \DateTime($params['valid_from']), + new \DateTime($params['valid_to']) + ); + + $client->addPublicKey($public_key); + return $public_key; + }); + } + +} \ No newline at end of file diff --git a/app/services/oauth2/ClientCrendentialGenerator.php b/app/services/oauth2/ClientCrendentialGenerator.php new file mode 100644 index 00000000..9d2801c6 --- /dev/null +++ b/app/services/oauth2/ClientCrendentialGenerator.php @@ -0,0 +1,49 @@ +client_id = Rand::getString(32, OAuth2Protocol::VsChar, true) . '.openstack.client'; + + if ($client->client_type === IClient::ClientType_Confidential) + { + $now = new \DateTime(); + $client->client_secret = Rand::getString(64, OAuth2Protocol::VsChar, true); + // default 6 months + $client->client_secret_expires_at = $now->add( new \DateInterval('P6M')); + } + return $client; + } +} \ No newline at end of file diff --git a/app/services/oauth2/ClientService.php b/app/services/oauth2/ClientService.php index e0b1f39b..21a7ce8e 100644 --- a/app/services/oauth2/ClientService.php +++ b/app/services/oauth2/ClientService.php @@ -3,28 +3,30 @@ namespace services\oauth2; use Client; -use ClientAuthorizedUri; -use ClientAllowedOrigin; - use DB; +use Event; use Input; -use oauth2\exceptions\AllowedClientUriAlreadyExistsException; +use oauth2\exceptions\AbsentClientException; +use oauth2\exceptions\InvalidApiScope; +use oauth2\exceptions\InvalidClientAuthMethodException; use oauth2\exceptions\InvalidClientType; use oauth2\exceptions\MissingClientAuthorizationInfo; -use oauth2\exceptions\AbsentClientException; -use oauth2\exceptions\InvalidAllowedClientUriException; - +use oauth2\models\ClientAssertionAuthenticationContext; +use oauth2\models\ClientAuthenticationContext; +use oauth2\models\ClientCredentialsAuthenticationContext; use oauth2\models\IClient; use oauth2\OAuth2Protocol; -use oauth2\services\IApiScopeService; use oauth2\services\IApiScope; +use oauth2\services\IApiScopeService; +use oauth2\services\IClientCrendentialGenerator; use oauth2\services\IClientService; use oauth2\services\id; use Request; -use utils\services\IAuthService; -use Zend\Math\Rand; -use Event; +use URL\Normalizer; use utils\db\ITransactionService; +use utils\exceptions\EntityNotFoundException; +use utils\http\HttpUtils; +use utils\services\IAuthService; /** * Class ClientService @@ -32,21 +34,41 @@ use utils\db\ITransactionService; */ class ClientService implements IClientService { + /** + * @var IAuthService + */ private $auth_service; + /** + * @var IApiScopeService + */ private $scope_service; - /** - * @param IAuthService $auth_service - * @param IApiScopeService $scope_service - * @param ITransactionService $tx_service - */ - public function __construct(IAuthService $auth_service, IApiScopeService $scope_service,ITransactionService $tx_service) + /** + * @var IClientCrendentialGenerator + */ + private $client_credential_generator; + + /** + * @param IAuthService $auth_service + * @param IApiScopeService $scope_service + * @param IClientCrendentialGenerator $client_credential_generator + * @param ITransactionService $tx_service + */ + public function __construct + ( + IAuthService $auth_service, + IApiScopeService $scope_service, + IClientCrendentialGenerator $client_credential_generator, + ITransactionService $tx_service + ) { - $this->auth_service = $auth_service; - $this->scope_service = $scope_service; - $this->tx_service = $tx_service; + $this->auth_service = $auth_service; + $this->scope_service = $scope_service; + $this->client_credential_generator = $client_credential_generator; + $this->tx_service = $tx_service; } + /** * Clients in possession of a client password MAY use the HTTP Basic * authentication scheme as defined in [RFC2617] to authenticate with @@ -55,150 +77,185 @@ class ClientService implements IClientService * client credentials in the request-body using the following * parameters: * implementation of http://tools.ietf.org/html/rfc6749#section-2.3.1 - * @throws MissingClientAuthorizationInfo; - * @return list + * implementation of http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication + * @throws InvalidClientAuthMethodException + * @throws MissingClientAuthorizationInfo + * @return ClientAuthenticationContext */ public function getCurrentClientAuthInfo() { - //check first http basic auth header + $auth_header = Request::header('Authorization'); - if (!is_null($auth_header) && !empty($auth_header)) { + if + ( + Input::has( OAuth2Protocol::OAuth2Protocol_ClientAssertionType) && + Input::has( OAuth2Protocol::OAuth2Protocol_ClientAssertion) + ) + { + return new ClientAssertionAuthenticationContext + ( + Input::get(OAuth2Protocol::OAuth2Protocol_ClientAssertionType, ''), + Input::get(OAuth2Protocol::OAuth2Protocol_ClientAssertion, '') + ); + } + if + ( + Input::has( OAuth2Protocol::OAuth2Protocol_ClientId) && + Input::has( OAuth2Protocol::OAuth2Protocol_ClientSecret) + ) + { + return new ClientCredentialsAuthenticationContext + ( + Input::get(OAuth2Protocol::OAuth2Protocol_ClientId, ''), + Input::get(OAuth2Protocol::OAuth2Protocol_ClientSecret, ''), + OAuth2Protocol::TokenEndpoint_AuthMethod_ClientSecretPost + ); + } + if(!empty($auth_header)) + { $auth_header = trim($auth_header); $auth_header = explode(' ', $auth_header); if (!is_array($auth_header) || count($auth_header) < 2) - throw new MissingClientAuthorizationInfo; + { + throw new MissingClientAuthorizationInfo('bad auth header.'); + } $auth_header_content = $auth_header[1]; $auth_header_content = base64_decode($auth_header_content); $auth_header_content = explode(':', $auth_header_content); if (!is_array($auth_header_content) || count($auth_header_content) !== 2) - throw new MissingClientAuthorizationInfo; + { + throw new MissingClientAuthorizationInfo('bad auth header.'); + } - //client_id:client_secret - return array($auth_header_content[0], $auth_header_content[1]); + return new ClientCredentialsAuthenticationContext( + $auth_header_content[0], + $auth_header_content[1], + OAuth2Protocol::TokenEndpoint_AuthMethod_ClientSecretBasic + ); } - //if not get from http input - $client_id = Input::get(OAuth2Protocol::OAuth2Protocol_ClientId, ''); - $client_secret = Input::get(OAuth2Protocol::OAuth2Protocol_ClientSecret, ''); - return array($client_id, $client_secret); + + throw new InvalidClientAuthMethodException; } - public function addClient($application_type, $user_id, $app_name, $app_description,$app_url=null, $app_logo = '') + public function addClient($application_type, $user_id, $app_name, $app_description, $app_url = null, $app_logo = '') { - $instance = null; - $this_var = $this; - $scope_service = $this_var->scope_service; + $scope_service = $this->scope_service; + $client_credential_generator = $this->client_credential_generator; - $this->tx_service->transaction(function () use ($application_type, $user_id, $app_name,$app_url, $app_description, $app_logo, &$instance, &$this_var,&$scope_service) { + return $this->tx_service->transaction(function () use ( + $application_type, + $user_id, + $app_name, + $app_url, + $app_description, + $app_logo, + $scope_service, + $client_credential_generator + ) { - //check $application_type vs client_type - $client_type = $application_type == IClient::ApplicationType_JS_Client? IClient::ClientType_Public : IClient::ClientType_Confidential; - $instance = new Client; - $instance->app_name = $app_name; - $instance->app_logo = $app_logo; - $instance->client_id = Rand::getString(32, OAuth2Protocol::VsChar, true) . '.openstack.client'; - //only generates secret for confidential clients - if ($client_type == IClient::ClientType_Confidential) - $instance->client_secret = Rand::getString(24, OAuth2Protocol::VsChar, true); + $client = new Client + ( + array + ( + 'max_auth_codes_issuance_basis' => 0, + 'max_refresh_token_issuance_basis' => 0, + 'max_access_token_issuance_qty' => 0, + 'max_access_token_issuance_basis' => 0, + 'max_refresh_token_issuance_qty' => 0, + 'use_refresh_token' => false, + 'rotate_refresh_token' => false, + ) + ); - $instance->client_type = $client_type; - $instance->application_type = $application_type; + $client->app_name = $app_name; + $client->app_logo = $app_logo; + $client->app_description = $app_description; + $client->application_type = $application_type; + $client = $client_credential_generator->generate($client); - $instance->user_id = $user_id; - $instance->active = true; - $instance->use_refresh_token = false; - $instance->rotate_refresh_token = false; - $instance->website = $app_url; - $instance->Save(); - //default allowed url - $this_var->addClientAllowedUri($instance->getId(), 'https://localhost'); + if ($client->client_type === IClient::ClientType_Confidential) { + $client->token_endpoint_auth_method = OAuth2Protocol::TokenEndpoint_AuthMethod_ClientSecretBasic; + } else { + $client->token_endpoint_auth_method = OAuth2Protocol::TokenEndpoint_AuthMethod_None; + } + + $client->user_id = $user_id; + $client->active = true; + $client->use_refresh_token = false; + $client->rotate_refresh_token = false; + $client->website = $app_url; + $client->Save(); //add default scopes $default_scopes = $scope_service->getDefaultScopes(); - foreach($default_scopes as $default_scope){ - $instance->scopes()->attach($default_scope->id); + foreach ($default_scopes as $default_scope) { + if + ( + $default_scope->name === OAuth2Protocol::OfflineAccess_Scope && + !( + $client->application_type == IClient::ApplicationType_Native || + $client->application_type == IClient::ApplicationType_Web_App + ) + ) { + continue; + } + $client->scopes()->attach($default_scope->id); } + return $client; }); - return $instance; - } - - public function addClientAllowedUri($id, $uri) - { - $res = false; - $this->tx_service->transaction(function () use ($id,$uri,&$res){ - $client = Client::find($id); - - if (is_null($client)) - throw new AbsentClientException(sprintf("client id %s does not exists!",$id)); - - if(!filter_var($uri, FILTER_VALIDATE_URL)) return false; - $parts = @parse_url($uri); - if (!$parts) { - throw new InvalidAllowedClientUriException(sprintf('uri : %s', $uri)); - } - if(($parts['scheme']!=='https') && (ServerConfigurationService::getConfigValue("SSL.Enable"))) - throw new InvalidAllowedClientUriException(sprintf('uri : %s', $uri)); - //normalize uri - $normalized_uri = $parts['scheme'].'://'.strtolower($parts['host']); - if(isset($parts['path'])) { - $normalized_uri .= strtolower($parts['path']); - } - // normalize url and remove trailing / - $normalized_uri = rtrim($normalized_uri, '/'); - - $client_uri = ClientAuthorizedUri::where('uri', '=', $normalized_uri)->where('client_id', '=', $id)->first(); - if (!is_null($client_uri)) { - throw new AllowedClientUriAlreadyExistsException(sprintf('uri : %s', $normalized_uri)); - } - - $client_authorized_uri = new ClientAuthorizedUri; - $client_authorized_uri->client_id = $id; - $client_authorized_uri->uri = $uri; - $res = $client_authorized_uri->Save(); - }); - return $res; } public function addClientScope($id, $scope_id) { $client = Client::find($id); - if (is_null($client)) - throw new AbsentClientException(sprintf("client id %s does not exists!",$id)); + if (is_null($client)) { + throw new EntityNotFoundException(sprintf("client id %s not found!.", $id)); + } + $scope = $this->scope_service->get(intval($scope_id)); + if(is_null($scope)) throw new EntityNotFoundException(sprintf("scope %s not found!.", $scope_id)); + $user = $client->user()->first(); + + if($scope->isAssignableByGroups()) { + + $allowed = false; + foreach($user->getGroupScopes() as $group_scope) + { + if(intval($group_scope->id) === intval($scope_id)) + { + $allowed = true; break; + } + } + if(!$allowed) throw new InvalidApiScope(sprintf('you cant assign to this client api scope %s', $scope_id)); + } + if($scope->isSystem() && !$user->canUseSystemScopes()) + throw new InvalidApiScope(sprintf('you cant assign to this client api scope %s', $scope_id)); return $client->scopes()->attach($scope_id); } public function deleteClientScope($id, $scope_id) { $client = Client::find($id); - if (is_null($client)) - throw new AbsentClientException(sprintf("client id %s does not exists!",$id)); - return $client->scopes()->detach($scope_id); - } + if (is_null($client)) { + throw new AbsentClientException(sprintf("client id %s does not exists!", $id)); + } - /** - * Deletes a former client allowed redirection Uri - * @param $id client identifier - * @param $uri_id uri identifier - */ - public function deleteClientAllowedUri($id, $uri_id) - { - return ClientAuthorizedUri::where('id', '=', $uri_id)->where('client_id', '=', $id)->delete(); + return $client->scopes()->detach($scope_id); } public function deleteClientByIdentifier($id) { $res = false; - $this->tx_service->transaction(function () use ($id,&$res){ + $this->tx_service->transaction(function () use ($id, &$res) { $client = Client::find($id); if (!is_null($client)) { - $client->authorized_uris()->delete(); $client->scopes()->detach(); - Event::fire('oauth2.client.delete', array($client->client_id)); + Event::fire('oauth2.client.delete', array($client->client_id)); $res = $client->delete(); } }); @@ -208,29 +265,39 @@ class ClientService implements IClientService /** * Regenerates Client Secret * @param $id client id - * @return mixed + * @return IClient */ public function regenerateClientSecret($id) { - $new_secret = ''; - $this->tx_service->transaction(function () use ($id, &$new_secret) { + $client_credential_generator = $this->client_credential_generator; + + return $this->tx_service->transaction(function () use ($id, $client_credential_generator) + { $client = Client::find($id); - if(is_null($client)) - throw new AbsentClientException(sprintf("client id %d does not exists!.",$id)); + if (is_null($client)) + { + throw new AbsentClientException(sprintf("client id %d does not exists!.", $id)); + } - if($client->client_type != IClient::ClientType_Confidential) - throw new InvalidClientType($id,sprintf("client id %d is not confidential!.",$id)); + if ($client->client_type != IClient::ClientType_Confidential) + { + throw new InvalidClientType + ( + sprintf + ( + "client id %d is not confidential type!.", + $id + ) + ); + } - $client_secret = Rand::getString(24, OAuth2Protocol::VsChar, true); - $client->client_secret = $client_secret; - $client->Save(); - Event::fire('oauth2.client.regenerate.secret', array($client->client_id)); - $new_secret = $client->client_secret; + $client_credential_generator->generate($client, true)->save(); + Event::fire('oauth2.client.regenerate.secret', array($client->client_id)); + return $client; }); - return $new_secret; } /** @@ -240,17 +307,19 @@ class ClientService implements IClientService */ public function lockClient($client_id) { - $res = false; - $this_var = $this; + $res = false; + $this_var = $this; - $this->tx_service->transaction(function () use ($client_id, &$res, &$this_var) { + $this->tx_service->transaction(function () use ($client_id, &$res, &$this_var) { $client = $this_var->getClientByIdentifier($client_id); - if (is_null($client)) - throw new AbsentClientException($client_id,sprintf("client id %s does not exists!",$client_id)); + if (is_null($client)) { + throw new AbsentClientException($client_id, sprintf("client id %s does not exists!", $client_id)); + } $client->locked = true; - $res = $client->Save(); + $res = $client->Save(); }); + return $res; } @@ -261,21 +330,22 @@ class ClientService implements IClientService */ public function unlockClient($client_id) { - $res = false; - $this_var = $this; + $res = false; + $this_var = $this; - $this->tx_service->transaction(function () use ($client_id, &$res, &$this_var) { + $this->tx_service->transaction(function () use ($client_id, &$res, &$this_var) { $client = $this_var->getClientByIdentifier($client_id); - if (is_null($client)) - throw new AbsentClientException($client_id,sprintf("client id %s does not exists!",$client_id)); + if (is_null($client)) { + throw new AbsentClientException($client_id, sprintf("client id %s does not exists!", $client_id)); + } $client->locked = false; - $res = $client->Save(); + $res = $client->Save(); }); + return $res; } - /** * @param $client_id * @return IClient @@ -283,40 +353,48 @@ class ClientService implements IClientService public function getClientById($client_id) { $client = Client::where('client_id', '=', $client_id)->first(); + return $client; } public function activateClient($id, $active) { $client = $this->getClientByIdentifier($id); - if (is_null($client)) - throw new AbsentClientException(sprintf("client id %s does not exists!",$id)); + if (is_null($client)) { + throw new AbsentClientException(sprintf("client id %s does not exists!", $id)); + } $client->active = $active; + return $client->Save(); } public function getClientByIdentifier($id) { $client = Client::where('id', '=', $id)->first(); + return $client; } public function setRefreshTokenUsage($id, $use_refresh_token) { $client = $this->getClientByIdentifier($id); - if (is_null($client)) - throw new AbsentClientException(sprintf("client id %s does not exists!",$id)); + if (is_null($client)) { + throw new AbsentClientException(sprintf("client id %s does not exists!", $id)); + } $client->use_refresh_token = $use_refresh_token; + return $client->Save(); } public function setRotateRefreshTokenPolicy($id, $rotate_refresh_token) { $client = $this->getClientByIdentifier($id); - if (is_null($client)) - throw new AbsentClientException(sprintf("client id %s does not exists!",$id)); + if (is_null($client)) { + throw new AbsentClientException(sprintf("client id %s does not exists!", $id)); + } $client->rotate_refresh_token = $rotate_refresh_token; + return $client->Save(); } @@ -342,10 +420,11 @@ class ClientService implements IClientService * @param array $fields * @return mixed */ - public function getAll($page_nbr=1,$page_size=10,array $filters=array(), array $fields=array('*')) + public function getAll($page_nbr = 1, $page_size = 10, array $filters = array(), array $fields = array('*')) { DB::getPaginator()->setCurrentPage($page_nbr); - return Client::Filter($filters)->paginate($page_size,$fields); + + return Client::Filter($filters)->paginate($page_size, $fields); } /** @@ -354,12 +433,14 @@ class ClientService implements IClientService */ public function save(IClient $client) { - if(!$client->exists() || count($client->getDirty())>0){ + if (!$client->exists() || count($client->getDirty()) > 0) { return $client->Save(); } + return true; } + /** * @param $id * @param array $params @@ -368,70 +449,164 @@ class ClientService implements IClientService */ public function update($id, array $params) { - $res = false; - $this_var = $this; + $this_var = $this; - $this->tx_service->transaction(function () use ($id,$params, &$res, &$this_var) { + return $this->tx_service->transaction(function () use ($id, $params, &$this_var) { $client = Client::find($id); - if(is_null($client)) - throw new AbsentClientException(sprintf('client id %s does not exists!',$id)); + if (is_null($client)) { + throw new AbsentClientException(sprintf('client id %s does not exists.', $id)); + } + $current_app_type = $client->getApplicationType(); + if($current_app_type !== $params['application_type']) + { + throw new \ValidationException('application type does not match.'); + } + + // validate uris + switch($current_app_type) + { + case IClient::ApplicationType_Native: + { + $redirect_uris = explode(',', $params['redirect_uris']); + if(!isset($params['redirect_uris'])) throw new \ValidationException('redirect_uris param is required.'); + //check that custom schema does not already exists for another registerd app + if(!empty($params['redirect_uris'])) { + foreach ($redirect_uris as $uri) { + $uri = @parse_url($uri); + if (!isset($uri['scheme'])) { + throw new \ValidationException('invalid scheme on redirect uri.'); + } + if (HttpUtils::isCustomSchema($uri['scheme'])) { + $already_has_schema_registered = Client::where('redirect_uris', 'like', + '%' . $uri['scheme'] . '://%')->where('id', '<>', $id)->count(); + if ($already_has_schema_registered > 0) { + throw new \ValidationException(sprintf('schema %s:// already registered for another client.', + $uri['scheme'])); + } + } else { + if (!HttpUtils::isHttpSchema($uri['scheme'])) { + throw new \ValidationException(sprintf('scheme %s:// is invalid.', $uri['scheme'])); + } + } + } + } + } + break; + case IClient::ApplicationType_Web_App: + case IClient::ApplicationType_JS_Client: + { + if(!isset($params['redirect_uris'])) throw new \ValidationException('redirect_uris param is required.'); + if(!empty($params['redirect_uris'])) { + $redirect_uris = explode(',', $params['redirect_uris']); + foreach ($redirect_uris as $uri) { + $uri = @parse_url($uri); + if (!isset($uri['scheme'])) { + throw new \ValidationException('invalid scheme on redirect uri.'); + } + if (!HttpUtils::isHttpsSchema($uri['scheme'])) { + throw new \ValidationException(sprintf('scheme %s:// is invalid.', $uri['scheme'])); + } + } + } + if($current_app_type === IClient::ApplicationType_JS_Client && isset($params['allowed_origins']) &&!empty($params['allowed_origins'])){ + $allowed_origins = explode(',', $params['allowed_origins']); + foreach ($allowed_origins as $uri) { + $uri = @parse_url($uri); + if (!isset($uri['scheme'])) { + throw new \ValidationException('invalid scheme on allowed origin uri.'); + } + if (!HttpUtils::isHttpsSchema($uri['scheme'])) { + throw new \ValidationException(sprintf('scheme %s:// is invalid.', $uri['scheme'])); + } + } + } + } + break; + } $allowed_update_params = array( - 'app_name','website','app_description','app_logo','active','locked','use_refresh_token','rotate_refresh_token'); + 'app_name', + 'website', + 'app_description', + 'app_logo', + 'active', + 'locked', + 'use_refresh_token', + 'rotate_refresh_token', + 'contacts', + 'logo_uri', + 'tos_uri', + 'post_logout_redirect_uris', + 'logout_uri', + 'logout_session_required', + 'logout_use_iframe', + 'policy_uri', + 'jwks_uri', + 'default_max_age', + 'logout_use_iframe', + 'require_auth_time', + 'token_endpoint_auth_method', + 'token_endpoint_auth_signing_alg', + 'subject_type', + 'userinfo_signed_response_alg', + 'userinfo_encrypted_response_alg', + 'userinfo_encrypted_response_enc', + 'id_token_signed_response_alg', + 'id_token_encrypted_response_alg', + 'id_token_encrypted_response_enc', + 'redirect_uris', + 'allowed_origins', + ); - foreach($allowed_update_params as $param){ - if(array_key_exists($param,$params)){ + $fields_to_uri_normalize = array + ( + 'post_logout_redirect_uris', + 'logout_uri', + 'policy_uri', + 'jwks_uri', + 'tos_uri', + 'logo_uri', + 'redirect_uris', + 'allowed_origins' + ); + + foreach ($allowed_update_params as $param) + { + if (array_key_exists($param, $params)) + { + if(in_array($param, $fields_to_uri_normalize)) + { + $urls = $params[$param]; + if(!empty($urls)) + { + $urls = explode(',', $urls); + $normalized_uris = ''; + foreach ($urls as $url) { + $un = new Normalizer($url); + $url = $un->normalize(); + if (!empty($normalized_uris)) { + $normalized_uris .= ','; + } + $normalized_uris .= $url; + } + $params[$param] = $normalized_uris; + } + } $client->{$param} = $params[$param]; } } - $res = $this_var->save($client); + return $this_var->save($client); }); - return $res; + } /** - * @param $id - * @param $origin - * @return mixed - * @throws \oauth2\exceptions\AllowedClientUriAlreadyExistsException - * @throws \oauth2\exceptions\AbsentClientException + * @param string $origin + * @return IClient */ - public function addClientAllowedOrigin($id, $origin) + public function getByOrigin($origin) { - $res = false; - - $this->tx_service->transaction(function () use ($id, $origin, &$res) { - - $client = Client::find($id); - - if (is_null($client)) - throw new AbsentClientException(sprintf("client id %s does not exists!",$id)); - - if($client->getApplicationType()!=IClient::ApplicationType_JS_Client) - throw new InvalidClientType($id,sprintf("client id %s application type must be JS_CLIENT",$id)); - - $client_origin = ClientAllowedOrigin::where('allowed_origin', '=', $origin)->where('client_id', '=', $id)->first(); - if (!is_null($client_origin)) { - throw new AllowedClientUriAlreadyExistsException(sprintf('origin : %s', $origin)); - } - - $client_origin = new ClientAllowedOrigin; - $client_origin->client_id = $id; - $client_origin->allowed_origin = $origin; - - $res = $client_origin->Save(); - }); - return $res; - } - - /** - * @param $id - * @param $origin_id - * @return mixed - */ - public function deleteClientAllowedOrigin($id, $origin_id) - { - return ClientAllowedOrigin::where('id', '=', $origin_id)->where('client_id', '=', $id)->delete(); + return Client::where('allowed_origins', 'like', '%'.$origin.'%')->first(); } } \ No newline at end of file diff --git a/app/services/oauth2/HttpIClientJWKSetReader.php b/app/services/oauth2/HttpIClientJWKSetReader.php new file mode 100644 index 00000000..98e50cea --- /dev/null +++ b/app/services/oauth2/HttpIClientJWKSetReader.php @@ -0,0 +1,51 @@ +getJWKSUri(); + if(empty($jwk_set_uri)) return null; + + $client = new HttpClient; + $client->setSslVerification(false); + $request = $client->get($jwk_set_uri); + $response = $request->send(); + if($response->getStatusCode() !== 200) return null; + $content_type = $response->getHeader('content-type'); + if(is_null($content_type)) return null; + if(!$content_type->hasValue(HttpContentType::Json)) return null; + return JWKSet::fromJson($response->getBody()); + } +} \ No newline at end of file diff --git a/app/services/oauth2/IdTokenBuilderImpl.php b/app/services/oauth2/IdTokenBuilderImpl.php new file mode 100644 index 00000000..f52ef39e --- /dev/null +++ b/app/services/oauth2/IdTokenBuilderImpl.php @@ -0,0 +1,182 @@ +server_private_key_repository = $server_private_key_repository; + $this->jwk_set_reader_service = $jwk_set_reader_service; + } + + /** + * @param JWTClaimSet $claim_set + * @param JWTResponseInfo $info + * @param IClient $client + * @return \jwe\IJWE|\jws\IJWS|\jwt\IJWT + * @throws RecipientKeyNotFoundException + * @throws \oauth2\exceptions\InvalidClientType + * @throws \oauth2\exceptions\ServerKeyNotFoundException + */ + public function buildJWT(JWTClaimSet $claim_set, JWTResponseInfo $info, IClient $client) + { + $sig_alg = $info->getSigningAlgorithm(); + $enc_alg = $info->getEncryptionKeyAlgorithm(); + $enc = $info->getEncryptionContentAlgorithm(); + + $jwt = UnsecuredJWT::fromClaimSet($claim_set); + + if(!is_null($sig_alg)) + { + // must sign + // get server private key to sign + + $heuristic = new ServerSigningKeyFinder($this->server_private_key_repository); + + $jwt = self::buildJWS + ( + $heuristic->find + ( + $client, + $sig_alg + ), + $sig_alg->getName(), + $claim_set + ); + + } + + if(!is_null($enc_alg) && !is_null($enc)) + { + //encrypt , get client public key + + $alg = new StringOrURI($enc_alg->getName()); + $enc = new StringOrURI($enc->getName()); + + //encrypt jwt as payload + + $heuristic = new ClientEncryptionKeyFinder($this->jwk_set_reader_service); + + $jwt = self::buildJWE + ( + $heuristic->find + ( + $client, + $enc_alg + ), + $alg, + $enc, + $jwt + ); + } + + return $jwt; + } + + /** + * @param IJWK $recipient_key + * @param StringOrURI $alg + * @param StringOrURI $enc + * @param IBasicJWT $jwt + * @return \jwe\IJWE + * @throws RecipientKeyNotFoundException + * @throws \jwk\exceptions\InvalidJWKAlgorithm + * @throws \jwk\exceptions\InvalidJWKType + */ + static private function buildJWE(IJWK $recipient_key, StringOrURI $alg, StringOrURI $enc, IBasicJWT $jwt) + { + + if(is_null($recipient_key)) + throw new RecipientKeyNotFoundException; + + $jwe = JWEFactory::build + ( + new JWE_ParamsSpecification + ( + $recipient_key, + $alg, + $enc, + $payload = $jwt->toCompactSerialization() + ) + ); + return $jwe; + } + + /** + * @param IJWK $jwk + * @param $alg + * @param JWTClaimSet $claim_set + * @return \jws\IJWS + * @throws \jwk\exceptions\InvalidJWKAlgorithm + * @throws \jwk\exceptions\InvalidJWKType + */ + static private function buildJWS(IJWK $jwk, $alg, JWTClaimSet $claim_set) + { + return JWSFactory::build + ( + new JWS_ParamsSpecification + ( + $jwk, + new StringOrURI + ( + $alg + ), + $claim_set + ) + ); + } +} \ No newline at end of file diff --git a/app/services/oauth2/MementoOAuth2AuthenticationRequestService.php b/app/services/oauth2/MementoOAuth2AuthenticationRequestService.php deleted file mode 100644 index 1c1e58db..00000000 --- a/app/services/oauth2/MementoOAuth2AuthenticationRequestService.php +++ /dev/null @@ -1,93 +0,0 @@ - $value) { - if (array_key_exists($key, OAuth2AuthorizationRequest::$params) == true) { - array_push($oauth2_params, $key); - } - } - - if (count($oauth2_params) > 0) { - Input::flashOnly($oauth2_params); - return true; - } else { - $old_data = Input::old(); - $oauth2_params = array(); - foreach ($old_data as $key => $value) { - if (array_key_exists($key, OAuth2AuthorizationRequest::$params) == true) { - array_push($oauth2_params, $key); - } - } - if (count($oauth2_params) > 0) { - Session::reflash(); - return true; - } - } - - return false; - - } - - /** Retrieve last OAuth2AuthorizationRequest - * @return OAuth2AuthorizationRequest; - */ - public function getCurrentAuthorizationRequest() - { - $msg = new OAuth2AuthorizationRequest(new OAuth2Message(Input::all())); - if (!$msg->isValid()) { - //if not valid , then check on old input - $msg = null; - $old_data = Input::old(); - $oauth2_params = array(); - foreach ($old_data as $key => $value) { - if (array_key_exists($key, OAuth2AuthorizationRequest::$params) == true) { - $oauth2_params[$key] = $value; - } - } - if (count($oauth2_params) > 0) { - $msg = new OAuth2AuthorizationRequest(new OAuth2Message($oauth2_params)); - } - } - return $msg; - } - - public function clearCurrentRequest() - { - $old_data = Input::old(); - $oauth2_params = array(); - - foreach ($old_data as $key => $value) { - if (array_key_exists($key, OAuth2AuthorizationRequest::$params) == true){ - array_push($oauth2_params, $key); - } - } - - if (count($oauth2_params) > 0) { - foreach ($oauth2_params as $oauth2_param) { - Session::forget($oauth2_param); - Session::remove($oauth2_param); - } - } - } - - -} \ No newline at end of file diff --git a/app/services/oauth2/OAuth2MementoSessionSerializerService.php b/app/services/oauth2/OAuth2MementoSessionSerializerService.php new file mode 100644 index 00000000..ffa9e280 --- /dev/null +++ b/app/services/oauth2/OAuth2MementoSessionSerializerService.php @@ -0,0 +1,68 @@ +getState())); + Session::put('oauth2.request.state', $state); + Session::save(); + } + + /** + * @return OAuth2RequestMemento + */ + public function load() + { + $state = Session::get('oauth2.request.state', null); + if(is_null($state)) return null; + + $state = json_decode( base64_decode($state), true); + + return OAuth2RequestMemento::buildFromState($state); + } + + /** + * @return void + */ + public function forget() + { + Session::remove('oauth2.request.state'); + Session::save(); + } + + /** + * @return bool + */ + public function exists() + { + return Session::has('oauth2.request.state'); + } +} \ No newline at end of file diff --git a/app/services/oauth2/OAuth2ServiceProvider.php b/app/services/oauth2/OAuth2ServiceProvider.php index 7733b238..98d3406d 100644 --- a/app/services/oauth2/OAuth2ServiceProvider.php +++ b/app/services/oauth2/OAuth2ServiceProvider.php @@ -7,9 +7,11 @@ use oauth2\services\AccessTokenGenerator; use oauth2\services\AuthorizationCodeGenerator; use oauth2\services\OAuth2ServiceCatalog; use oauth2\services\RefreshTokenGenerator; +use oauth2\strategies\ClientAuthContextValidatorFactory; use services\oauth2\ResourceServer; use App; use utils\services\UtilsServiceCatalog; +use URL; /** * Class OAuth2ServiceProvider @@ -19,23 +21,41 @@ class OAuth2ServiceProvider extends ServiceProvider { protected $defer = false; - public function boot(){ + public function boot() + { } - public function register(){ - + public function register() + { App::singleton('oauth2\\IResourceServerContext', 'services\\oauth2\\ResourceServerContext'); - App::singleton(OAuth2ServiceCatalog::MementoService, 'services\\oauth2\\MementoOAuth2AuthenticationRequestService'); + App::singleton(OAuth2ServiceCatalog::ClientCredentialGenerator, 'services\\oauth2\\ClientCrendentialGenerator'); App::singleton(OAuth2ServiceCatalog::ClientService, 'services\\oauth2\\ClientService'); + App::singleton(OAuth2ServiceCatalog::ClienPublicKeyService, 'services\\oauth2\\ClienPublicKeyService'); + App::singleton(OAuth2ServiceCatalog::ServerPrivateKeyService, 'services\\oauth2\\ServerPrivateKeyService'); App::singleton(OAuth2ServiceCatalog::ScopeService, 'services\\oauth2\\ApiScopeService'); App::singleton(OAuth2ServiceCatalog::ResourceServerService, 'services\\oauth2\\ResourceServerService'); App::singleton(OAuth2ServiceCatalog::ApiService, 'services\\oauth2\\ApiService'); App::singleton(OAuth2ServiceCatalog::ApiEndpointService, 'services\\oauth2\\ApiEndpointService'); App::singleton(OAuth2ServiceCatalog::UserConsentService, 'services\\oauth2\\UserConsentService'); - App::singleton(OAuth2ServiceCatalog::AllowedOriginService, 'services\\oauth2\\AllowedOriginService'); - App::singleton(OAuth2ServiceCatalog::TokenService, function(){ + App::singleton(OAuth2ServiceCatalog::OpenIDProviderConfigurationService,'services\\oauth2\\OpenIDProviderConfigurationService'); + App::singleton(OAuth2ServiceCatalog::MementoSerializerService,'services\\oauth2\\OAuth2MementoSessionSerializerService'); + App::singleton(OAuth2ServiceCatalog::SecurityContextService,'services\\oauth2\\SecurityContextService'); + App::singleton(OAuth2ServiceCatalog::PrincipalService,'services\\oauth2\\PrincipalService'); + App::singleton('oauth2\services\IClientJWKSetReader','services\oauth2\HttpIClientJWKSetReader'); + App::singleton('oauth2\services\IApiScopeGroupService','services\oauth2\ApiScopeGroupService'); - return new TokenService( + App::singleton('oauth2\\builders\\IdTokenBuilder',function() { + return new IdTokenBuilderImpl + ( + App::make('oauth2\repositories\IServerPrivateKeyRepository'), + new HttpIClientJWKSetReader + ); + }); + + App::singleton(OAuth2ServiceCatalog::TokenService, function() + { + return new TokenService + ( App::make(OAuth2ServiceCatalog::ClientService), App::make(UtilsServiceCatalog::LockManagerService), App::make(UtilsServiceCatalog::ServerConfigurationService), @@ -45,6 +65,11 @@ class OAuth2ServiceProvider extends ServiceProvider new AuthorizationCodeGenerator(App::make(UtilsServiceCatalog::CacheService)), new AccessTokenGenerator(App::make(UtilsServiceCatalog::CacheService)), new RefreshTokenGenerator(App::make(UtilsServiceCatalog::CacheService)), + App::make('oauth2\repositories\IServerPrivateKeyRepository'), + new HttpIClientJWKSetReader, + App::make(OAuth2ServiceCatalog::SecurityContextService), + App::make(OAuth2ServiceCatalog::PrincipalService), + App::make('oauth2\\builders\\IdTokenBuilder'), App::make(UtilsServiceCatalog::TransactionService) ); }); diff --git a/app/services/oauth2/OpenIDProviderConfigurationService.php b/app/services/oauth2/OpenIDProviderConfigurationService.php new file mode 100644 index 00000000..d9e0d059 --- /dev/null +++ b/app/services/oauth2/OpenIDProviderConfigurationService.php @@ -0,0 +1,99 @@ +setState + ( + array + ( + $user_id, + $auth_time, + $ops + ) + ); + + return $principal; + } + + /** + * @param IPrincipal $principal + * @return void + */ + public function save(IPrincipal $principal) + { + $this->register + ( + $principal->getUserId(), + $principal->getAuthTime() + ); + } + + /** + * @param int $user_id + * @param int $auth_time + * @return mixed + */ + public function register($user_id, $auth_time) + { + Session::put(self::UserIdParam, $user_id); + Session::put(self::AuthTimeParam, $auth_time); + $opbs = bin2hex(mcrypt_create_iv(16, MCRYPT_DEV_URANDOM)); + Cookie::queue('opbs', $opbs, $minutes = 2628000, $path = '/', $domain = null, $secure = false, $httpOnly = false); + Session::put(self::OPBrowserState, $opbs); + Session::save(); + } + + /** + * @return $this + */ + public function clear() + { + Session::remove(self::UserIdParam); + Session::remove(self::AuthTimeParam); + Session::remove(self::OPBrowserState); + Session::save(); + Cookie::queue('opbs', null, $minutes = -2628000, $path = '/', $domain = null, $secure = false, $httpOnly = false); + } + +} \ No newline at end of file diff --git a/app/services/oauth2/ResourceServerService.php b/app/services/oauth2/ResourceServerService.php index ed7425ff..079fe552 100644 --- a/app/services/oauth2/ResourceServerService.php +++ b/app/services/oauth2/ResourceServerService.php @@ -3,31 +3,33 @@ namespace services\oauth2; -use oauth2\models\IResourceServer; +use DB; +use oauth2\exceptions\InvalidResourceServer; use oauth2\models\IClient; +use oauth2\models\IResourceServer; +use oauth2\services\IClientService; use oauth2\services\id; use oauth2\services\IResourceServerService; -use oauth2\services\IClientService; use ResourceServer; -use DB; -use \oauth2\exceptions\InvalidResourceServer; use utils\db\ITransactionService; /** * Class ResourceServerService * @package services\oauth2 */ -class ResourceServerService implements IResourceServerService { +class ResourceServerService implements IResourceServerService +{ private $client_service; - /** - * @param IClientService $client_service - * @param ITransactionService $tx_service - */ - public function __construct(IClientService $client_service,ITransactionService $tx_service){ + /** + * @param IClientService $client_service + * @param ITransactionService $tx_service + */ + public function __construct(IClientService $client_service, ITransactionService $tx_service) + { $this->client_service = $client_service; - $this->tx_service = $tx_service; + $this->tx_service = $tx_service; } /** @@ -37,10 +39,11 @@ class ResourceServerService implements IResourceServerService { * @param array $fields * @return mixed */ - public function getAll($page_nbr=1,$page_size=10,array $filters=array(), array $fields=array('*')) + public function getAll($page_nbr = 1, $page_size = 10, array $filters = array(), array $fields = array('*')) { DB::getPaginator()->setCurrentPage($page_nbr); - return ResourceServer::Filter($filters)->paginate($page_size,$fields); + + return ResourceServer::Filter($filters)->paginate($page_size, $fields); } /** @@ -49,30 +52,38 @@ class ResourceServerService implements IResourceServerService { * @return bool * @throws \oauth2\exceptions\InvalidResourceServer */ - public function update($id, array $params){ + public function update($id, array $params) + { - $res = false; - $this_var = $this; + $res = false; + $this_var = $this; - $this->tx_service->transaction(function () use ($id,$params,&$res, &$this_var) { + $this->tx_service->transaction(function () use ($id, $params, &$res, &$this_var) { $resource_server = ResourceServer::find($id); - if(is_null($resource_server)) - throw new InvalidResourceServer(sprintf('resource server id %s does not exists!',$id)); - $allowed_update_params = array('host','ip','active','friendly_name'); + if (is_null($resource_server)) { + throw new InvalidResourceServer(sprintf('resource server id %s does not exists!', $id)); + } + $allowed_update_params = array('host', 'ip', 'active', 'friendly_name'); - foreach($allowed_update_params as $param){ - if(array_key_exists($param,$params)){ + foreach ($allowed_update_params as $param) { + if (array_key_exists($param, $params)) { - if($param =='host'){ - if(ResourceServer::where('host','=',$params[$param])->where('id','<>',$id)->count()>0) - throw new InvalidResourceServer(sprintf('there is already another resource server with that hostname (%s).',$params[$param])); + if ($param == 'host') { + if (ResourceServer::where('host', '=', $params[$param])->where('id', '<>', $id)->count() > 0) { + throw new InvalidResourceServer(sprintf('there is already another resource server with that hostname (%s).', + $params[$param])); + } } - if($param =='friendly_name'){ - if(ResourceServer::where('friendly_name','=',$params[$param])->where('id','<>',$id)->count()>0) - throw new InvalidResourceServer(sprintf('there is already another resource server with that friendly name (%s).',$params[$param])); + if ($param == 'friendly_name') { + if (ResourceServer::where('friendly_name', '=', $params[$param])->where('id', '<>', + $id)->count() > 0 + ) { + throw new InvalidResourceServer(sprintf('there is already another resource server with that friendly name (%s).', + $params[$param])); + } } $resource_server->{$param} = $params[$param]; @@ -80,6 +91,7 @@ class ResourceServerService implements IResourceServerService { } $res = $this_var->save($resource_server); }); + return $res; } @@ -89,9 +101,10 @@ class ResourceServerService implements IResourceServerService { */ public function save(IResourceServer $resource_server) { - if(!$resource_server->exists() || count($resource_server->getDirty())>0){ + if (!$resource_server->exists() || count($resource_server->getDirty()) > 0) { return $resource_server->Save(); } + return true; } @@ -103,7 +116,7 @@ class ResourceServerService implements IResourceServerService { */ public function setStatus($id, $active) { - return ResourceServer::find($id)->update(array('active'=>$active)); + return ResourceServer::find($id)->update(array('active' => $active)); } /** @@ -113,22 +126,23 @@ class ResourceServerService implements IResourceServerService { */ public function delete($id) { - $res = false; - $client_service = $this->client_service; + $res = false; + $client_service = $this->client_service; - $this->tx_service->transaction(function () use ($id,&$res,&$client_service) { + $this->tx_service->transaction(function () use ($id, &$res, &$client_service) { $resource_server = ResourceServer::find($id); - if(!is_null($resource_server)){ + if (!is_null($resource_server)) { $client = $resource_server->client()->first(); - if(!is_null($client)){ - $client_service->deleteClientByIdentifier($client->id); + if (!is_null($client)) { + $client_service->deleteClientByIdentifier($client->id); } $resource_server->delete(); $res = true; } }); + return $res; } @@ -151,26 +165,39 @@ class ResourceServerService implements IResourceServerService { */ public function add($host, $ip, $friendly_name, $active) { - $instance = null; - $client_service = $this->client_service; - if(is_string($active)){ - $active = strtoupper($active) =='TRUE' ?true:false; + $client_service = $this->client_service; + + if (is_string($active)) + { + $active = strtoupper($active) == 'TRUE' ? true : false; } - $this->tx_service->transaction(function () use ($host, $ip, $friendly_name, $active, &$instance, &$client_service) { + return $this->tx_service->transaction(function () use ( + $host, + $ip, + $friendly_name, + $active, + $client_service + ) { - if(ResourceServer::where('host','=',$host)->count()>0) - throw new InvalidResourceServer(sprintf('there is already another resource server with that hostname (%s).',$host)); + if (ResourceServer::where('host', '=', $host)->count() > 0) { + throw new InvalidResourceServer(sprintf('there is already another resource server with that hostname (%s).', + $host)); + } - if(ResourceServer::where('friendly_name','=',$friendly_name)->count()>0) - throw new InvalidResourceServer(sprintf('there is already another resource server with that friendly name (%s).',$friendly_name)); + if (ResourceServer::where('friendly_name', '=', $friendly_name)->count() > 0) { + throw new InvalidResourceServer(sprintf('there is already another resource server with that friendly name (%s).', + $friendly_name)); + } - $instance = new ResourceServer( - array( - 'host' => $host, - 'ip' => $ip, - 'active' => $active, + $instance = new ResourceServer + ( + array + ( + 'host' => $host, + 'ip' => $ip, + 'active' => $active, 'friendly_name' => $friendly_name ) ); @@ -178,32 +205,44 @@ class ResourceServerService implements IResourceServerService { $instance->Save(); // creates a new client for this brand new resource server - $new_client = $client_service->addClient(IClient::ApplicationType_Service,null,$host.'.confidential.application',$friendly_name.' confidential oauth2 application',''); + $new_client = $client_service->addClient + ( + IClient::ApplicationType_Service, + null, + $host . '.confidential.application', + $friendly_name . ' confidential oauth2 application', '' + ); + $new_client->resource_server()->associate($instance); + // does not expires ... + $new_client->client_secret_expires_at = null; $new_client->Save(); + return $instance; }); - return $instance; + } /** * @param $id * @return bool */ - public function regenerateClientSecret($id){ - $res = null; - $client_service = $this->client_service; + public function regenerateClientSecret($id) + { + $res = null; + $client_service = $this->client_service; - $this->tx_service->transaction(function () use ($id,&$res,&$client_service) { + $this->tx_service->transaction(function () use ($id, &$res, &$client_service) { $resource_server = ResourceServer::find($id); - if(!is_null($resource_server)){ + if (!is_null($resource_server)) { $client = $resource_server->client()->first(); - if(!is_null($client)){ + if (!is_null($client)) { $res = $client_service->regenerateClientSecret($client->id); } } }); + return $res; } } diff --git a/app/services/oauth2/SecurityContextService.php b/app/services/oauth2/SecurityContextService.php new file mode 100644 index 00000000..362df5b1 --- /dev/null +++ b/app/services/oauth2/SecurityContextService.php @@ -0,0 +1,70 @@ +setState + ( + array + ( + Session::get(self::RequestedUserIdParam), + Session::get(self::RequestedAuthTime), + ) + ); + + return $context; + } + + /** + * @param SecurityContext $security_context + * @return void + */ + public function save(SecurityContext $security_context) + { + Session::put(self::RequestedUserIdParam, $security_context->getRequestedUserId()); + Session::put(self::RequestedAuthTime, $security_context->isAuthTimeRequired()); + Session::save(); + } + + /** + * @return $this + */ + public function clear() + { + Session::remove(self::RequestedUserIdParam); + Session::remove(self::RequestedAuthTime); + Session::save(); + } +} \ No newline at end of file diff --git a/app/services/oauth2/ServerPrivateKeyService.php b/app/services/oauth2/ServerPrivateKeyService.php new file mode 100644 index 00000000..94ca4df4 --- /dev/null +++ b/app/services/oauth2/ServerPrivateKeyService.php @@ -0,0 +1,109 @@ +rsa = new Crypt_RSA(); + } + + /** + * @param array $params + * @return IAssymetricKey + * @throws ValidationException + */ + public function register(array $params) + { + $rsa = $this->rsa; + $repository = $this->repository; + + return $this->tx_service->transaction(function() use($params, $rsa, $repository) + { + $pem = isset($params['pem_content']) ? $params['pem_content'] : ''; + $password = $params['password']; + + $old_active_key = $repository->getByValidityRange + ( + $params['type'], + $params['usage'], + $params['alg'], + new \DateTime($params['valid_from']), + new \DateTime($params['valid_to']) + )->first(); + + + if(empty($pem)) + { + if(!empty($password)) + $rsa->setPassword($password); + /** + * array( + * 'privatekey' => $privatekey, + * 'publickey' => $publickey, + * 'partialkey' => false + * ); + */ + $res = $rsa->createKey(2048); + $pem = $res['privatekey']; + } + + + $key = ServerPrivateKey::build + ( + $params['kid'], + new \DateTime($params['valid_from']), + new \DateTime($params['valid_to']), + $params['type'], + $params['usage'], + $params['alg'], + $old_active_key ? false : $params['active'], + $pem, + $password + ); + + $repository->add($key); + + return $key; + }); + } + +} \ No newline at end of file diff --git a/app/services/oauth2/TokenService.php b/app/services/oauth2/TokenService.php index 06775520..4f19ac8c 100644 --- a/app/services/oauth2/TokenService.php +++ b/app/services/oauth2/TokenService.php @@ -17,39 +17,65 @@ namespace services\oauth2; use AccessToken as DBAccessToken; use DB; use Event; +use oauth2\exceptions\RevokedAccessTokenException; +use oauth2\exceptions\RevokedAccessTokenExceptionxtends; +use oauth2\exceptions\RevokedRefreshTokenException; +use Session; +use Crypt; +use jwa\cryptographic_algorithms\HashFunctionAlgorithm; +use jwt\IBasicJWT; +use jwt\impl\JWTClaimSet; +use jwt\JWTClaim; +use oauth2\builders\IdTokenBuilder; use oauth2\exceptions\AbsentClientException; +use oauth2\exceptions\AbsentCurrentUserException; use oauth2\exceptions\ExpiredAccessTokenException; use oauth2\exceptions\InvalidAccessTokenException; use oauth2\exceptions\InvalidAuthorizationCodeException; +use oauth2\exceptions\InvalidClientCredentials; use oauth2\exceptions\InvalidGrantTypeException; +use oauth2\exceptions\RecipientKeyNotFoundException; use oauth2\exceptions\ReplayAttackException; +use oauth2\heuristics\ClientEncryptionKeyFinder; +use oauth2\heuristics\EncryptionClientPublicKeyFinder; +use oauth2\heuristics\SigningClientPublicKeyFinder; use oauth2\models\AccessToken; use oauth2\models\AuthorizationCode; use oauth2\models\IClient; use oauth2\models\RefreshToken; use oauth2\OAuth2Protocol; +use oauth2\repositories\IServerPrivateKeyRepository; use oauth2\services\Authorization; +use oauth2\services\IClientJWKSetReader; use oauth2\services\IClientService; +use oauth2\services\IPrincipalService; +use oauth2\services\ISecurityContextService; use oauth2\services\ITokenService; use oauth2\services\IUserConsentService; -use RefreshToken as DBRefreshToken; use RefreshToken as RefreshTokenDB; +use RefreshToken as DBRefreshToken; +use utils\Base64UrlRepresentation; +use utils\ByteUtil; use utils\db\ITransactionService; use utils\exceptions\UnacquiredLockException; use utils\IPHelper; +use utils\json_types\JsonValue; +use utils\json_types\NumericDate; +use utils\json_types\StringOrURI; use utils\services\IAuthService; use utils\services\ICacheService; use utils\services\IdentifierGenerator; use utils\services\ILockManagerService; use utils\services\IServerConfigurationService; use Zend\Crypt\Hash; +use utils\exceptions\ConfigurationException; /** * Class TokenService * Provides all Tokens related operations (create, get and revoke) * @package services\oauth2 */ -class TokenService implements ITokenService +final class TokenService implements ITokenService { const ClientAccessTokenPrefixList = '.atokens'; const ClientAuthCodePrefixList = '.acodes'; @@ -103,11 +129,36 @@ class TokenService implements ITokenService * @var IdentifierGenerator */ private $refresh_token_generator; + + /** + * @var IServerPrivateKeyRepository + */ + private $server_private_key_repository; + + /** + * @var IClientJWKSetReader + */ + private $jwk_set_reader_service; /** * @var ITransactionService */ private $tx_service; + /** + * @var ISecurityContextService + */ + private $security_context_service; + + /** + * @var IPrincipalService + */ + private $principal_service; + + /** + * @var IdTokenBuilder + */ + private $id_token_builder; + /** * @param IClientService $client_service * @param ILockManagerService $lock_manager_service @@ -118,9 +169,15 @@ class TokenService implements ITokenService * @param IdentifierGenerator $auth_code_generator * @param IdentifierGenerator $access_token_generator * @param IdentifierGenerator $refresh_token_generator + * @param IServerPrivateKeyRepository $server_private_key_repository + * @param IClientJWKSetReader $jwk_set_reader_service + * @param ISecurityContextService $security_context_service + * @param IPrincipalService $principal_service + * @param IdTokenBuilder $id_token_builder * @param ITransactionService $tx_service */ - public function __construct( + public function __construct + ( IClientService $client_service, ILockManagerService $lock_manager_service, IServerConfigurationService $configuration_service, @@ -130,18 +187,30 @@ class TokenService implements ITokenService IdentifierGenerator $auth_code_generator, IdentifierGenerator $access_token_generator, IdentifierGenerator $refresh_token_generator, + IServerPrivateKeyRepository $server_private_key_repository, + IClientJWKSetReader $jwk_set_reader_service, + ISecurityContextService $security_context_service, + IPrincipalService $principal_service, + IdTokenBuilder $id_token_builder, ITransactionService $tx_service - ) { - $this->client_service = $client_service; - $this->lock_manager_service = $lock_manager_service; - $this->configuration_service = $configuration_service; - $this->cache_service = $cache_service; - $this->auth_service = $auth_service; - $this->user_consent_service = $user_consent_service; - $this->auth_code_generator = $auth_code_generator; - $this->access_token_generator = $access_token_generator; - $this->refresh_token_generator = $refresh_token_generator; - $this->tx_service = $tx_service; + ) + { + $this->client_service = $client_service; + $this->lock_manager_service = $lock_manager_service; + $this->configuration_service = $configuration_service; + $this->cache_service = $cache_service; + $this->auth_service = $auth_service; + $this->user_consent_service = $user_consent_service; + $this->auth_code_generator = $auth_code_generator; + $this->access_token_generator = $access_token_generator; + $this->refresh_token_generator = $refresh_token_generator; + $this->server_private_key_repository = $server_private_key_repository; + $this->jwk_set_reader_service = $jwk_set_reader_service; + $this->security_context_service = $security_context_service; + $this->principal_service = $principal_service; + $this->id_token_builder = $id_token_builder; + $this->tx_service = $tx_service; + $this_var = $this; Event::listen('oauth2.client.delete', function ($client_id) use (&$this_var) { @@ -163,45 +232,69 @@ class TokenService implements ITokenService * @param string $access_type * @param string $approval_prompt * @param bool $has_previous_user_consent + * @param string|null $state + * @param string|null $nonce + * @param string|null $response_type * @return AuthorizationCode */ - public function createAuthorizationCode( + public function createAuthorizationCode + ( $user_id, $client_id, $scope, - $audience = '', - $redirect_uri = null, - $access_type = OAuth2Protocol::OAuth2Protocol_AccessType_Online, - $approval_prompt = OAuth2Protocol::OAuth2Protocol_Approval_Prompt_Auto, - $has_previous_user_consent = false - ) { + $audience = '' , + $redirect_uri = null, + $access_type = OAuth2Protocol::OAuth2Protocol_AccessType_Online, + $approval_prompt = OAuth2Protocol::OAuth2Protocol_Approval_Prompt_Auto, + $has_previous_user_consent = false, + $state = null, + $nonce = null, + $response_type = null + ) + { //create model - $code = $this->auth_code_generator->generate(AuthorizationCode::create( - $user_id, - $client_id, - $scope, - $audience, - $redirect_uri, - $access_type, - $approval_prompt, $has_previous_user_consent, - $this->configuration_service->getConfigValue('OAuth2.AuthorizationCode.Lifetime'))); + $code = $this->auth_code_generator->generate + ( + AuthorizationCode::create + ( + $user_id, + $client_id, + $scope, + $audience, + $redirect_uri, + $access_type, + $approval_prompt, $has_previous_user_consent, + $this->configuration_service->getConfigValue('OAuth2.AuthorizationCode.Lifetime'), + $state, + $nonce, + $response_type, + $this->security_context_service->get()->isAuthTimeRequired(), + $this->principal_service->get()->getAuthTime() + ) + ); $hashed_value = Hash::compute('sha256', $code->getValue()); //stores on cache $this->cache_service->storeHash($hashed_value, - array( - 'client_id' => $code->getClientId(), - 'scope' => $code->getScope(), - 'audience' => $code->getAudience(), - 'redirect_uri' => $code->getRedirectUri(), - 'issued' => $code->getIssued(), - 'lifetime' => $code->getLifetime(), - 'from_ip' => $code->getFromIp(), - 'user_id' => $code->getUserId(), - 'access_type' => $code->getAccessType(), - 'approval_prompt' => $code->getApprovalPrompt(), - 'has_previous_user_consent' => $code->getHasPreviousUserConsent() + array + ( + 'client_id' => $code->getClientId(), + 'scope' => $code->getScope(), + 'audience' => $code->getAudience(), + 'redirect_uri' => $code->getRedirectUri(), + 'issued' => $code->getIssued(), + 'lifetime' => $code->getLifetime(), + 'from_ip' => $code->getFromIp(), + 'user_id' => $code->getUserId(), + 'access_type' => $code->getAccessType(), + 'approval_prompt' => $code->getApprovalPrompt(), + 'has_previous_user_consent' => $code->getHasPreviousUserConsent(), + 'state' => $code->getState(), + 'nonce' => $code->getNonce(), + 'response_type' => $code->getResponseType(), + 'requested_auth_time' => $code->isAuthTimeRequested(), + 'auth_time' => $code->getAuthTime(), ), intval($code->getLifetime())); //stores brand new auth code hash value on a set by client id... @@ -223,14 +316,17 @@ class TokenService implements ITokenService $hashed_value = Hash::compute('sha256', $value); - if (!$this->cache_service->exists($hashed_value)) { + if (!$this->cache_service->exists($hashed_value)) + { throw new InvalidAuthorizationCodeException(sprintf("auth_code %s ", $value)); } - try { + try + { $this->lock_manager_service->acquireLock('lock.get.authcode.' . $hashed_value); - $cache_values = $this->cache_service->getHash($hashed_value, array( + $cache_values = $this->cache_service->getHash($hashed_value, array + ( 'user_id', 'client_id', 'scope', @@ -241,10 +337,17 @@ class TokenService implements ITokenService 'from_ip', 'access_type', 'approval_prompt', - 'has_previous_user_consent' + 'has_previous_user_consent', + 'state', + 'nonce', + 'response_type', + 'requested_auth_time', + 'auth_time' )); - $code = AuthorizationCode::load($value, + $code = AuthorizationCode::load + ( + $value, $cache_values['user_id'], $cache_values['client_id'], $cache_values['scope'], @@ -255,12 +358,27 @@ class TokenService implements ITokenService $cache_values['from_ip'], $cache_values['access_type'], $cache_values['approval_prompt'], - $cache_values['has_previous_user_consent'] + $cache_values['has_previous_user_consent'], + $cache_values['state'], + $cache_values['nonce'], + $cache_values['response_type'], + $cache_values['requested_auth_time'], + $cache_values['auth_time'] ); return $code; - } catch (UnacquiredLockException $ex1) { - throw new ReplayAttackException($value, sprintf("auth_code %s ", $value)); + } + catch (UnacquiredLockException $ex1) + { + throw new ReplayAttackException + ( + $value, + sprintf + ( + "Code was already redeemed %s.", + $value + ) + ); } } @@ -273,8 +391,10 @@ class TokenService implements ITokenService public function createAccessToken(AuthorizationCode $auth_code, $redirect_uri = null) { - $access_token = $this->access_token_generator->generate( - AccessToken::create( + $access_token = $this->access_token_generator->generate + ( + AccessToken::create + ( $auth_code, $this->configuration_service->getConfigValue('OAuth2.AccessToken.Lifetime') ) @@ -285,30 +405,32 @@ class TokenService implements ITokenService $auth_service = $this->auth_service; $this_var = $this; - $this->tx_service->transaction(function () use ( + return $this->tx_service->transaction(function () use ( $auth_code, $redirect_uri, - &$access_token, - &$cache_service, - &$client_service, - &$auth_service, - &$this_var + $access_token, + $cache_service, + $client_service, + $auth_service, + $this_var ) { - $value = $access_token->getValue(); + $value = $access_token->getValue(); $hashed_value = Hash::compute('sha256', $value); - $client_id = $access_token->getClientId(); - $user_id = $access_token->getUserId(); - $client = $client_service->getClientById($client_id); - $user = $auth_service->getUserById($user_id); + $client_id = $access_token->getClientId(); + $user_id = $access_token->getUserId(); + $client = $client_service->getClientById($client_id); + $user = $auth_service->getUserById($user_id); - $access_token_db = new DBAccessToken ( - array( - 'value' => $hashed_value, - 'from_ip' => IPHelper::getUserIp(), + $access_token_db = new DBAccessToken + ( + array + ( + 'value' => $hashed_value, + 'from_ip' => IPHelper::getUserIp(), 'associated_authorization_code' => Hash::compute('sha256', $auth_code->getValue()), 'lifetime' => $access_token->getLifetime(), - 'scope' => $access_token->getScope(), + 'scope' => $access_token->getScope(), 'audience' => $access_token->getAudience() ) ); @@ -319,9 +441,26 @@ class TokenService implements ITokenService $access_token_db->save(); //check if use refresh tokens... - if ($client->use_refresh_token && $client->getApplicationType() == IClient::ApplicationType_Web_App && $auth_code->getAccessType() == OAuth2Protocol::OAuth2Protocol_AccessType_Offline) { + if + ( + $client->use_refresh_token && + ( + $client->getApplicationType() == IClient::ApplicationType_Web_App || + $client->getApplicationType() == IClient::ApplicationType_Native + ) && + ( + $auth_code->getAccessType() == OAuth2Protocol::OAuth2Protocol_AccessType_Offline || + str_contains($auth_code->getScope(), OAuth2Protocol::OfflineAccess_Scope) + ) + ) + { //but only the first time (approval_prompt == force || not exists previous consent) - if (!$auth_code->getHasPreviousUserConsent() || $auth_code->getApprovalPrompt() == OAuth2Protocol::OAuth2Protocol_Approval_Prompt_Force) { + if + ( + !$auth_code->getHasPreviousUserConsent() || + $auth_code->getApprovalPrompt() == OAuth2Protocol::OAuth2Protocol_Approval_Prompt_Force + ) + { $this_var->createRefreshToken($access_token); } } @@ -329,11 +468,17 @@ class TokenService implements ITokenService $this_var->storesAccessTokenOnCache($access_token); //stores brand new access token hash value on a set by client id... $cache_service->addMemberSet($client_id . TokenService::ClientAccessTokenPrefixList, $hashed_value); - $cache_service->incCounter($client_id . TokenService::ClientAccessTokensQty, - TokenService::ClientAccessTokensQtyLifetime); + + $cache_service->incCounter + ( + $client_id . TokenService::ClientAccessTokensQty, + TokenService::ClientAccessTokensQtyLifetime + ); + + return $access_token; }); - return $access_token; + } /** @@ -422,13 +567,12 @@ class TokenService implements ITokenService public function createAccessTokenFromRefreshToken(RefreshToken $refresh_token, $scope = null) { - - $cache_service = $this->cache_service; - $client_service = $this->client_service; - $configuration_service = $this->configuration_service; - $auth_service = $this->auth_service; + $cache_service = $this->cache_service; + $client_service = $this->client_service; + $configuration_service = $this->configuration_service; + $auth_service = $this->auth_service; $access_token_generator = $this->access_token_generator; - $this_var = $this; + $this_var = $this; //preserve entire operation on db transaction... @@ -443,30 +587,43 @@ class TokenService implements ITokenService $access_token_generator ) { - $refresh_token_value = $refresh_token->getValue(); + $refresh_token_value = $refresh_token->getValue(); $refresh_token_hashed_value = Hash::compute('sha256', $refresh_token_value); //clear current access tokens as invalid $this_var->clearAccessTokensForRefreshToken($refresh_token->getValue()); //validate scope if present... - if (!is_null($scope) && empty($scope)) { - $original_scope = $refresh_token->getScope(); - $aux_original_scope = explode(' ', $original_scope); - $aux_scope = explode(' ', $scope); + if (!is_null($scope) && empty($scope)) + { + $original_scope = $refresh_token->getScope(); + $aux_original_scope = explode(OAuth2Protocol::OAuth2Protocol_Scope_Delimiter, $original_scope); + $aux_scope = explode(OAuth2Protocol::OAuth2Protocol_Scope_Delimiter, $scope); //compare original scope with given one, and validate if its included on original one //or not - if (count(array_diff($aux_scope, $aux_original_scope)) !== 0) { - throw new InvalidGrantTypeException(sprintf("requested scope %s is not contained on original one %s", - $scope, $original_scope)); + if (count(array_diff($aux_scope, $aux_original_scope)) !== 0) + { + throw new InvalidGrantTypeException + ( + sprintf + ( + "requested scope %s is not contained on original one %s", + $scope, + $original_scope + ) + ); } - } else { + } + else + { //get original scope $scope = $refresh_token->getScope(); } //create new access token - $access_token = $access_token_generator->generate( - AccessToken::createFromRefreshToken( + $access_token = $access_token_generator->generate + ( + AccessToken::createFromRefreshToken + ( $refresh_token, $scope, $configuration_service->getConfigValue('OAuth2.AccessToken.Lifetime') @@ -485,12 +642,14 @@ class TokenService implements ITokenService $client = $client_service->getClientById($client_id); //stores in DB - $access_token_db = new DBAccessToken( - array( - 'value' => $hashed_value, - 'from_ip' => IPHelper::getUserIp(), + $access_token_db = new DBAccessToken + ( + array + ( + 'value' => $hashed_value, + 'from_ip' => IPHelper::getUserIp(), 'lifetime' => $access_token->getLifetime(), - 'scope' => $access_token->getScope(), + 'scope' => $access_token->getScope(), 'audience' => $access_token->getAudience() ) ); @@ -501,7 +660,8 @@ class TokenService implements ITokenService $access_token_db->client()->associate($client); - if (!is_null($user_id)) { + if (!is_null($user_id)) + { $user = $auth_service->getUserById($user_id); $access_token_db->user()->associate($user); } @@ -510,8 +670,11 @@ class TokenService implements ITokenService //stores brand new access token hash value on a set by client id... $cache_service->addMemberSet($client_id . TokenService::ClientAccessTokenPrefixList, $hashed_value); - $cache_service->incCounter($client_id . TokenService::ClientAccessTokensQty, - TokenService::ClientAccessTokensQtyLifetime); + $cache_service->incCounter + ( + $client_id . TokenService::ClientAccessTokensQty, + TokenService::ClientAccessTokensQtyLifetime + ); return $access_token; }); } @@ -594,10 +757,10 @@ class TokenService implements ITokenService */ public function getAccessToken($value, $is_hashed = false) { - $cache_service = $this->cache_service; - $lock_manager_service = $this->lock_manager_service; + $cache_service = $this->cache_service; + $lock_manager_service = $this->lock_manager_service; $configuration_service = $this->configuration_service; - $this_var = $this; + $this_var = $this; return $this->tx_service->transaction(function () use ( $this_var, @@ -609,29 +772,39 @@ class TokenService implements ITokenService ) { //hash the given value, bc tokens values are stored hashed on DB $hashed_value = !$is_hashed ? Hash::compute('sha256', $value) : $value; - $lock_name = ''; + $lock_name = ''; $access_token = null; - try { + + try + { // check cache ... - if (!$cache_service->exists($hashed_value)) { + if (!$cache_service->exists($hashed_value)) + { // check on DB... $access_token_db = DBAccessToken::where('value', '=', $hashed_value)->first(); - if (is_null($access_token_db)) { - if ($cache_service->exists('access.token:void:' . $hashed_value)) // check if its marked on cache as expired ... + if (is_null($access_token_db)) + { + if($this_var->isAccessTokenRevoked($hashed_value)) + { + throw new RevokedAccessTokenException(sprintf('Access token %s is revoked!', $value)); + } + else if($this_var->isAccessTokenVoid($hashed_value)) // check if its marked on cache as expired ... { throw new ExpiredAccessTokenException(sprintf('Access token %s is expired!', $value)); - } else { + } + else + { throw new InvalidGrantTypeException(sprintf("Access token %s is invalid!", $value)); } } // lock ... $lock_name = 'lock.get.accesstoken.' . $hashed_value; $lock_manager_service->acquireLock($lock_name); - if ($access_token_db->isVoid()) { + if ($access_token_db->isVoid()) + { // invalid one ... // add to cache as expired ... - $cache_service->addSingleValue('access.token:void:' . $hashed_value, - 'access.token:void:' . $hashed_value); + $this_var->markAccessTokenAsVoid($hashed_value); // and deleted it from db $access_token_db->delete(); throw new ExpiredAccessTokenException(sprintf('Access token %s is expired!', $value)); @@ -642,7 +815,8 @@ class TokenService implements ITokenService $lock_manager_service->releaseLock($lock_name); } - $cache_values = $cache_service->getHash($hashed_value, array( + $cache_values = $cache_service->getHash($hashed_value, array + ( 'user_id', 'client_id', 'scope', @@ -655,7 +829,8 @@ class TokenService implements ITokenService )); // reload auth code ... - $auth_code = AuthorizationCode::load( + $auth_code = AuthorizationCode::load + ( $cache_values['auth_code'], intval($cache_values['user_id']) == 0 ? null : intval($cache_values['user_id']), $cache_values['client_id'], @@ -668,11 +843,18 @@ class TokenService implements ITokenService $access_type = OAuth2Protocol::OAuth2Protocol_AccessType_Online, $approval_prompt = OAuth2Protocol::OAuth2Protocol_Approval_Prompt_Auto, $has_previous_user_consent = false, + null, + null, $is_hashed = true ); // reload access token ... - $access_token = AccessToken::load($value, $auth_code, $cache_values['issued'], - $cache_values['lifetime']); + $access_token = AccessToken::load + ( + $value, + $auth_code, + $cache_values['issued'], + $cache_values['lifetime'] + ); $refresh_token_value = $cache_values['refresh_token']; if (!empty($refresh_token_value)) { @@ -792,35 +974,54 @@ class TokenService implements ITokenService public function getRefreshToken($value, $is_hashed = false) { //hash the given value, bc tokens values are stored hashed on DB - $hashed_value = !$is_hashed ? Hash::compute('sha256', $value) : $value; + $hashed_value = !$is_hashed ? Hash::compute('sha256', $value) : $value; $refresh_token_db = DBRefreshToken::where('value', '=', $hashed_value)->first(); - if (is_null($refresh_token_db)) { - throw new InvalidGrantTypeException(sprintf("Refresh token %s does not exists!", $value)); + if (is_null($refresh_token_db)) + { + if($this->isRefreshTokenRevoked($hashed_value)) + throw new RevokedRefreshTokenException(sprintf("revoked refresh token %s !", $value)); + + throw new InvalidGrantTypeException(sprintf("refresh token %s does not exists!", $value)); } - if ($refresh_token_db->void) { - throw new ReplayAttackException($value, sprintf("Refresh token %s is void", $value)); + if ($refresh_token_db->void) + { + throw new ReplayAttackException + ( + $value, + sprintf + ( + "refresh token %s is void", + $value + ) + ); } //check is refresh token is stills alive... (ZERO is infinite lifetime) - if ($refresh_token_db->isVoid()) { - throw new InvalidGrantTypeException(sprintf("Refresh token %s is expired!", $value)); + if ($refresh_token_db->isVoid()) + { + throw new InvalidGrantTypeException(sprintf("refresh token %s is expired!", $value)); } $client = $refresh_token_db->client()->first(); - $refresh_token = RefreshToken::load(array( - 'value' => $value, - 'scope' => $refresh_token_db->scope, - 'client_id' => $client->client_id, - 'user_id' => $refresh_token_db->user_id, - 'audience' => $refresh_token_db->audience, - 'from_ip' => $refresh_token_db->from_ip, - 'issued' => $refresh_token_db->created_at, - 'is_hashed' => $is_hashed - ), intval($refresh_token_db->lifetime)); + $refresh_token = RefreshToken::load + ( + array + ( + 'value' => $value, + 'scope' => $refresh_token_db->scope, + 'client_id' => $client->client_id, + 'user_id' => $refresh_token_db->user_id, + 'audience' => $refresh_token_db->audience, + 'from_ip' => $refresh_token_db->from_ip, + 'issued' => $refresh_token_db->created_at, + 'is_hashed' => $is_hashed + ), + intval($refresh_token_db->lifetime) + ); return $refresh_token; } @@ -842,7 +1043,7 @@ class TokenService implements ITokenService foreach ($db_access_tokens as $db_access_token) { - $client = $db_access_tokens->client()->first(); + $client = $db_access_token->client()->first(); $access_token_value = $db_access_token->value; $refresh_token_db = $db_access_token->refresh_token()->first(); @@ -870,24 +1071,32 @@ class TokenService implements ITokenService public function revokeAccessToken($value, $is_hashed = false) { - $res = 0; $cache_service = $this->cache_service; + $this_var = $this; - $this->tx_service->transaction(function () use ($value, $is_hashed, &$res, &$cache_service) { + return $this->tx_service->transaction(function () use ($value, $is_hashed, $cache_service, $this_var) { + $res = 0; //hash the given value, bc tokens values are stored hashed on DB $hashed_value = !$is_hashed ? Hash::compute('sha256', $value) : $value; $access_token_db = DBAccessToken::where('value', '=', $hashed_value)->first(); - $client = $access_token_db->client()->first(); + $client = $access_token_db->client()->first(); //delete from cache $res = $cache_service->delete($hashed_value); - $res = $cache_service->deleteMemberSet($client->client_id . TokenService::ClientAccessTokenPrefixList, - $access_token_db->value); + $res = $cache_service->deleteMemberSet + ( + $client->client_id . TokenService::ClientAccessTokenPrefixList, + $access_token_db->value + ); + //check on DB... and delete it $res = $access_token_db->delete(); + + $this_var->markAccessTokenAsRevoked($hashed_value); + + return $res > 0; }); - return $res > 0; } /** @@ -897,29 +1106,42 @@ class TokenService implements ITokenService public function revokeClientRelatedTokens($client_id) { //get client auth codes - $auth_codes = $this->cache_service->getSet($client_id . self::ClientAuthCodePrefixList); + $auth_codes = $this->cache_service->getSet($client_id . self::ClientAuthCodePrefixList); //get client access tokens - $access_tokens = $this->cache_service->getSet($client_id . self::ClientAccessTokenPrefixList); + $access_tokens = $this->cache_service->getSet($client_id . self::ClientAccessTokenPrefixList); $client_service = $this->client_service; - $cache_service = $this->cache_service; - + $cache_service = $this->cache_service; + $this_var = $this; $this->tx_service->transaction(function () use ( $client_id, $auth_codes, $access_tokens, - &$cache_service, - &$client_service + $cache_service, + $client_service, + $this_var ) { $client = $client_service->getClientById($client_id); - if (is_null($client)) { + + if (is_null($client)) + { return; } //revoke on cache $cache_service->deleteArray($auth_codes); $cache_service->deleteArray($access_tokens); //revoke on db + foreach($client->access_tokens()->get() as $at) + { + $this_var->markAccessTokenAsRevoked($at->value); + } + + foreach($client->refresh_tokens()->get() as $rt) + { + $this_var->markRefreshTokenAsRevoked($rt); + } + $client->access_tokens()->delete(); $client->refresh_tokens()->delete(); //delete client list (auth codes and access tokens) @@ -928,6 +1150,71 @@ class TokenService implements ITokenService }); } + /** + * @param string $at_hash + */ + public function markAccessTokenAsRevoked($at_hash) + { + $this->cache_service->addSingleValue + ( + 'access.token:revoked:'.$at_hash, + 'access.token:revoked:'.$at_hash, + $this->configuration_service->getConfigValue('OAuth2.AccessToken.Revoked.Lifetime') + ); + } + + /** + * @param string $at_hash + */ + public function markAccessTokenAsVoid($at_hash) + { + $this->cache_service->addSingleValue + ( + 'access.token:void:'.$at_hash, + 'access.token:void:'.$at_hash, + $this->configuration_service->getConfigValue('OAuth2.AccessToken.Void.Lifetime') + ); + } + + /** + * @param string $rt_hash + */ + public function markRefreshTokenAsRevoked($rt_hash) + { + $this->cache_service->addSingleValue + ( + 'refresh.token:revoked:'.$rt_hash, + 'refresh.token:revoked:'.$rt_hash, + $this->configuration_service->getConfigValue('OAuth2.RefreshToken.Revoked.Lifetime') + ); + } + + /** + * @param string $at_hash + * @return bool + */ + public function isAccessTokenRevoked($at_hash) + { + return $this->cache_service->exists('access.token:revoked:' . $at_hash); + } + + /** + * @param string $at_hash + * @return bool + */ + public function isAccessTokenVoid($at_hash) + { + return $this->cache_service->exists('access.token:void:' . $at_hash); + } + + /** + * @param string $rt_hash + * @return bool + */ + public function isRefreshTokenRevoked($rt_hash) + { + return $this->cache_service->exists('refresh.token:revoked:' . $rt_hash); + } /** * Mark a given refresh token as void @@ -938,12 +1225,10 @@ class TokenService implements ITokenService public function invalidateRefreshToken($value, $is_hashed = false) { $hashed_value = !$is_hashed ? Hash::compute('sha256', $value) : $value; - $res = RefreshTokenDB::where('value', '=', $hashed_value)->update(array('void' => true)); - + $res = RefreshTokenDB::where('value', '=', $hashed_value)->update(array('void' => true)); return $res > 0; } - /** * Revokes a give refresh token and all related access tokens * @param $value @@ -972,32 +1257,44 @@ class TokenService implements ITokenService public function clearAccessTokensForRefreshToken($value, $is_hashed = false) { - $hashed_value = !$is_hashed ? Hash::compute('sha256', $value) : $value; - $res = false; + $hashed_value = !$is_hashed ? Hash::compute('sha256', $value) : $value; $cache_service = $this->cache_service; + $this_var = $this; - $this->tx_service->transaction(function () use ($hashed_value, &$res, &$cache_service) { + return $this->tx_service->transaction(function () use ($hashed_value, $cache_service, $this_var) { + $res = false; $refresh_token_db = DBRefreshToken::where('value', '=', $hashed_value)->first(); - if (!is_null($refresh_token_db)) { + + if (!is_null($refresh_token_db)) + { $access_tokens_db = DBAccessToken::where('refresh_token_id', '=', $refresh_token_db->id)->get(); - if (!count($access_tokens_db)) { + + if (!count($access_tokens_db)) + { $res = true; } - foreach ($access_tokens_db as $access_token_db) { + foreach ($access_tokens_db as $access_token_db) + { - $res = $cache_service->delete($access_token_db->value); + $res = $cache_service->delete($access_token_db->value); $client = $access_token_db->client()->first(); - $res = $cache_service->deleteMemberSet($client->client_id . TokenService::ClientAccessTokenPrefixList, - $access_token_db->value); + $res = $cache_service->deleteMemberSet + ( + $client->client_id . TokenService::ClientAccessTokenPrefixList, + $access_token_db->value + ); + + $this_var->markAccessTokenAsRevoked($access_token_db->value); + $access_token_db->delete(); } } - }); - return $res; + return $res; + }); } public function getAccessTokenByClient($client_id) @@ -1020,7 +1317,8 @@ class TokenService implements ITokenService public function getRefreshTokenByClient($client_id) { $client = $this->client_service->getClientById($client_id); - if (is_null($client)) { + if (is_null($client)) + { throw new AbsentClientException(sprintf("client id %d does not exists!", $client_id)); } $res = array(); @@ -1067,4 +1365,226 @@ class TokenService implements ITokenService return $res; } + + /** + * @param string $nonce + * @param string $client_id + * @param AccessToken|null $access_token + * @param AuthorizationCode $auth_code + * @return IBasicJWT + * @throws AbsentClientException + * @throws InvalidClientCredentials + * @throws RecipientKeyNotFoundException + * @throws \jwt\exceptions\ClaimAlreadyExistsException + * @throws \oauth2\exceptions\InvalidClientType + * @throws \oauth2\exceptions\ServerKeyNotFoundException + */ + public function createIdToken + ( + $nonce, + $client_id, + AccessToken $access_token = null, + AuthorizationCode $auth_code = null + ) + { + $issuer = $this->configuration_service->getSiteUrl(); + if(empty($issuer)) throw new ConfigurationException('missing idp url'); + + $client = $this->client_service->getClientById($client_id); + $id_token_lifetime = $this->configuration_service->getConfigValue('OAuth2.IdToken.Lifetime'); + + if (is_null($client)) + { + throw new AbsentClientException + ( + sprintf + ( + "client id %d does not exists!", + $client_id + ) + ); + } + + $user = $this->auth_service->getUserById + ( + $this->principal_service->get()->getUserId() + ); + + if(!$user) + throw new AbsentCurrentUserException; + + // build claim set + $epoch_now = time(); + $session_id = Crypt::encrypt(Session::getId()); + $encoder = new Base64UrlRepresentation(); + $jti = $encoder->encode(hash('sha512', $session_id.$client_id, true)); + + $this->cache_service->addSingleValue($jti, $session_id, $id_token_lifetime); + + $claim_set = new JWTClaimSet + ( + $iss = new StringOrURI($issuer), + $sub = new StringOrURI + ( + $this->auth_service->wrapUserId + ( + $user->getExternalIdentifier(), + $client + ) + ), + $aud = new StringOrURI($client_id), + $iat = new NumericDate($epoch_now), + $exp = new NumericDate($epoch_now + $id_token_lifetime), + $jti = new JsonValue($jti) + ); + + if(!empty($nonce)) + $claim_set->addClaim(new JWTClaim(OAuth2Protocol::OAuth2Protocol_Nonce, new StringOrURI($nonce))); + + $id_token_response_info = $client->getIdTokenResponseInfo(); + $sig_alg = $id_token_response_info->getSigningAlgorithm(); + $enc_alg = $id_token_response_info->getEncryptionKeyAlgorithm(); + $enc = $id_token_response_info->getEncryptionContentAlgorithm(); + + if(!is_null($sig_alg) && !is_null($access_token)) + $this->buildAccessTokenHashClaim($access_token, $sig_alg , $claim_set); + + if(!is_null($sig_alg) && !is_null($auth_code)) + $this->buildAuthCodeHashClaim($auth_code, $sig_alg , $claim_set); + + $this->buildAuthTimeClaim($claim_set); + + return $this->id_token_builder->buildJWT($claim_set, $id_token_response_info, $client); + } + + /** + * @param AccessToken $access_token + * @param HashFunctionAlgorithm $hashing_alg + * @param JWTClaimSet $claim_set + * @return JWTClaimSet + * @throws InvalidClientCredentials + * @throws \jwt\exceptions\ClaimAlreadyExistsException + */ + private function buildAccessTokenHashClaim + ( + AccessToken $access_token, + HashFunctionAlgorithm $hashing_alg, + JWTClaimSet $claim_set + ) + { + $at = $access_token->getValue(); + $at_len = $hashing_alg->getHashKeyLen() / 2 ; + $encoder = new Base64UrlRepresentation(); + + if($at_len > ByteUtil::bitLength(strlen($at))) + throw new InvalidClientCredentials('invalid access token length!.'); + + $claim_set->addClaim + ( + new JWTClaim + ( + OAuth2Protocol::OAuth2Protocol_AccessToken_Hash, + new JsonValue + ( + $encoder->encode + ( + substr + ( + hash + ( + $hashing_alg->getHashingAlgorithm(), + $at, + true + ), + 0, + $at_len / 8 + ) + ) + ) + ) + ); + + return $claim_set; + } + + /** + * @param AuthorizationCode $auth_code + * @param HashFunctionAlgorithm $hashing_alg + * @param JWTClaimSet $claim_set + * @return JWTClaimSet + * @throws InvalidClientCredentials + * @throws \jwt\exceptions\ClaimAlreadyExistsException + */ + private function buildAuthCodeHashClaim + ( + AuthorizationCode $auth_code, + HashFunctionAlgorithm $hashing_alg, + JWTClaimSet $claim_set + ) + { + + $ac = $auth_code->getValue(); + $ac_len = $hashing_alg->getHashKeyLen() / 2 ; + $encoder = new Base64UrlRepresentation(); + + if($ac_len > ByteUtil::bitLength(strlen($ac))) + throw new InvalidClientCredentials('invalid auth code length!.'); + + $claim_set->addClaim + ( + new JWTClaim + ( + OAuth2Protocol::OAuth2Protocol_AuthCode_Hash, + new JsonValue + ( + $encoder->encode + ( + substr + ( + hash + ( + $hashing_alg->getHashingAlgorithm(), + $ac, + true + ), + 0, + $ac_len / 8 + ) + ) + ) + ) + ); + + return $claim_set; + } + + private function buildAuthTimeClaim(JWTClaimSet $claim_set) + { + if($this->security_context_service->get()->isAuthTimeRequired()) + { + $claim_set->addClaim + ( + new JWTClaim + ( + OAuth2Protocol::OAuth2Protocol_AuthTime, + new JsonValue + ( + $this->principal_service->get()->getAuthTime() + ) + ) + ); + } + } + + /** + * @param AuthorizationCode $auth_code + * @return AccessToken|null + */ + public function getAccessTokenByAuthCode(AuthorizationCode $auth_code) + { + $auth_code_value = Hash::compute('sha256', $auth_code->getValue()); + $db_access_token = DBAccessToken::where('associated_authorization_code', '=', $auth_code_value)->first(); + if(is_null($db_access_token)) return null; + return $this->getAccessToken($db_access_token->value, true); + } } \ No newline at end of file diff --git a/app/services/oauth2/UserConsentService.php b/app/services/oauth2/UserConsentService.php index 07d3229e..3da61ce8 100644 --- a/app/services/oauth2/UserConsentService.php +++ b/app/services/oauth2/UserConsentService.php @@ -2,13 +2,14 @@ namespace services\oauth2; +use Client; use oauth2\exceptions\AbsentClientException; use oauth2\models\IUserConsent; use oauth2\services\IUserConsentService; use UserConsent; -use Client; -class UserConsentService implements IUserConsentService{ +class UserConsentService implements IUserConsentService +{ /** * @param $user_id @@ -18,11 +19,102 @@ class UserConsentService implements IUserConsentService{ */ public function get($user_id, $client_id, $scopes) { - return UserConsent::where('user_id','=',$user_id) - ->where('client_id','=',$client_id) - ->where('scopes','=',$scopes)->first(); + + $set = explode(' ', $scopes); + $size = count($set) - 1; + $perm = range(0, $size); + $j = 0; + $perms = array(); + + do + { + foreach ($perm as $i) + { + $perms[$j][] = $set[$i]; + } + } while ($perm = $this->nextPermutation($perm, $size) and ++$j); + + $scope_conditions = array(); + + $query1 = UserConsent::where('user_id', '=', $user_id)->where('client_id', '=', $client_id); + + $query2 = UserConsent::where('user_id', '=', $user_id)->where('client_id', '=', $client_id); + + + $query1 = $query1->where(function ($query) use($perms) + { + foreach ($perms as $p) + { + $str = join(' ', $p); + $query = $query->orWhere('scopes', '=', $str); + } + + return $query; + }); + + + $query2 = $query2->where(function ($query) use($perms) + { + foreach ($perms as $p) + { + $str = join(' ', $p); + $query = $query->orWhere('scopes', 'like', '%'.$str.'%'); + } + + return $query; + }); + + + $consent = $query1->first(); + + if (is_null($consent)) { + $consent = $query2->first(); + } + + return $consent; } + + /** + * @param $p + * @param $size + * @return bool + * + * http://docstore.mik.ua/orelly/webprog/pcook/ch04_26.htm + * + */ + private function nextPermutation($p, $size) + { + // slide down the array looking for where we're smaller than the next guy + for ($i = $size - 1; $i >= 0 && $p[$i] >= $p[$i + 1]; --$i) {} + + // if this doesn't occur, we've finished our permutations + // the array is reversed: (1, 2, 3, 4) => (4, 3, 2, 1) + if ($i == -1) + { + return false; + } + + // slide down the array looking for a bigger number than what we found before + for ($j = $size; $p[$j] <= $p[$i]; --$j) {} + + // swap them + $tmp = $p[$i]; + $p[$i] = $p[$j]; + $p[$j] = $tmp; + + // now reverse the elements in between by swapping the ends + for (++$i, $j = $size; $i < $j; ++$i, --$j) + { + $tmp = $p[$i]; + $p[$i] = $p[$j]; + $p[$j] = $tmp; + } + + return $p; + } + + /** * @param $user_id * @param $client_id @@ -34,12 +126,13 @@ class UserConsentService implements IUserConsentService{ { $consent = new UserConsent(); - if(is_null(Client::find($client_id))) + if (is_null(Client::find($client_id))) { throw new AbsentClientException; + } $consent->client_id = $client_id; - $consent->user_id = $user_id; - $consent->scopes = $scopes; + $consent->user_id = $user_id; + $consent->scopes = $scopes; $consent->Save(); } } \ No newline at end of file diff --git a/app/services/oauth2/resource_server/UserService.php b/app/services/oauth2/resource_server/UserService.php index f4fd9268..9e10a9a4 100644 --- a/app/services/oauth2/resource_server/UserService.php +++ b/app/services/oauth2/resource_server/UserService.php @@ -2,30 +2,65 @@ namespace services\oauth2\resource_server; +use Exception; +use jwt\impl\JWTClaimSet; +use jwt\JWTClaim; +use oauth2\AddressClaim; +use oauth2\IResourceServerContext; use oauth2\resource_server\IUserService; use oauth2\resource_server\OAuth2ProtectedService; -use oauth2\IResourceServerContext; -use utils\services\ILogService; +use oauth2\services\IClientService; +use oauth2\StandardClaims; use openid\services\IUserService as IAPIUserService; -use Exception; +use utils\json_types\JsonArray; +use utils\json_types\JsonValue; +use utils\json_types\StringOrURI; +use utils\services\IAuthService; +use utils\services\ILogService; use utils\services\IServerConfigurationService; + /** * Class UserService * OAUTH2 Protected Endpoint * @package services\oauth2\resource_server */ -class UserService extends OAuth2ProtectedService implements IUserService { - +class UserService extends OAuth2ProtectedService implements IUserService +{ + /** + * @var IAPIUserService + */ private $user_service; - private $configuration_service; + /** + * @var IServerConfigurationService + */ + private $configuration_service; - public function __construct(IAPIUserService $user_service, - IResourceServerContext $resource_server_context, - IServerConfigurationService $configuration_service, - ILogService $log_service){ - parent::__construct($resource_server_context,$log_service); - $this->user_service = $user_service; - $this->configuration_service = $configuration_service; + /** + * @var IClientService + */ + private $client_service; + + /** + * @var IAuthService + */ + private $auth_service; + + public function __construct + ( + IAPIUserService $user_service, + IResourceServerContext $resource_server_context, + IServerConfigurationService $configuration_service, + ILogService $log_service, + IClientService $client_service, + IAuthService $auth_service + ) + { + parent::__construct($resource_server_context, $log_service); + + $this->user_service = $user_service; + $this->configuration_service = $configuration_service; + $this->client_service = $client_service; + $this->auth_service = $auth_service; } /** @@ -35,47 +70,129 @@ class UserService extends OAuth2ProtectedService implements IUserService { */ public function getCurrentUserInfo() { - $data = array(); - try{ + $data = array(); + try + { $me = $this->resource_server_context->getCurrentUserId(); - if(is_null($me)){ + if (is_null($me)) { + throw new Exception('me is no set!.'); + } + + $current_user = $this->user_service->get($me); + $scopes = $this->resource_server_context->getCurrentScope(); + + if (in_array(self::UserProfileScope_Address, $scopes)) { + // Address Claims + $data[AddressClaim::Country] = $current_user->getCountry(); + $data[AddressClaim::StreetAddress] = $current_user->getCountry(); + $data[AddressClaim::PostalCode] = $current_user->getPostalCode(); + $data[AddressClaim::Region] = $current_user->getRegion(); + $data[AddressClaim::Locality] = $current_user->getLocality(); + } + if (in_array(self::UserProfileScope_Profile, $scopes)) { + // Profile Claims + $assets_url = $this->configuration_service->getConfigValue('Assets.Url'); + $pic_url = $current_user->getPic(); + $pic_url = str_contains($pic_url, 'http') ? $pic_url : $assets_url . $pic_url; + + $data[StandardClaims::Name] = $current_user->getFullName(); + $data[StandardClaims::GivenName] = $current_user->getFirstName(); + $data[StandardClaims::FamilyName] = $current_user->getLastName(); + $data[StandardClaims::NickName] = $current_user->getNickName(); + $data[StandardClaims::Picture] = $pic_url; + $data[StandardClaims::Birthdate] = $current_user->getDateOfBirth(); + $data[StandardClaims::Gender] = $current_user->getGender(); + } + if (in_array(self::UserProfileScope_Email, $scopes)) { + // Email Claim + $data[StandardClaims::Email] = $current_user->getEmail(); + $data[StandardClaims::EmailVerified] = true; + } + } catch (Exception $ex) { + $this->log_service->error($ex); + throw $ex; + } + + return $data; + } + + /** + * @return JWTClaimSet + * @throws Exception + */ + public function getCurrentUserInfoClaims() + { + try + { + + $me = $this->resource_server_context->getCurrentUserId(); + $client_id = $this->resource_server_context->getCurrentClientId(); + $client = $this->client_service->getClientById($client_id); + + if (is_null($me)) + { throw new Exception('me is no set!.'); } $current_user = $this->user_service->get($me); $scopes = $this->resource_server_context->getCurrentScope(); - if(in_array(self::UserProfileScope_Address, $scopes)){ - // Address Claim - $data['country'] = $current_user->getCountry(); - $data['street_address'] = $current_user->getCountry(); - $data['postal_code'] = $current_user->getPostalCode(); - $data['region'] = $current_user->getRegion(); - $data['locality'] = $current_user->getLocality(); + $claim_set = new JWTClaimSet + ( + null, + $sub = new StringOrURI + ( + $this->auth_service->wrapUserId + ( + $current_user->getExternalIdentifier(), + $client + ) + ), + $aud = new StringOrURI($client_id) + + ); + + if (in_array(self::UserProfileScope_Address, $scopes)) { + // Address Claims + $address = array(); + $address[AddressClaim::Country] = $current_user->getCountry(); + $address[AddressClaim::StreetAddress] = $current_user->getStreetAddress(); + $address[AddressClaim::PostalCode] = $current_user->getPostalCode(); + $address[AddressClaim::Region] = $current_user->getRegion(); + $address[AddressClaim::Locality] = $current_user->getLocality(); + $address[AddressClaim::Formatted] = $current_user->getFormattedAddress(); + + $claim_set->addClaim(new JWTClaim(StandardClaims::Address, new JsonValue($address))); + } - if(in_array(self::UserProfileScope_Profile, $scopes)){ - // Address Claim - $assets_url = $this->configuration_service->getConfigValue('Assets.Url'); - $pic_url = $current_user->getPic(); - $pic_url = str_contains($pic_url,'http')?$pic_url:$assets_url.$pic_url; - $data['name'] = $current_user->getFirstName(); - $data['family_name'] = $current_user->getLastName(); - $data['nickname'] = $current_user->getNickName(); - $data['picture'] = $pic_url; - $data['birthdate'] = $current_user->getDateOfBirth(); - $data['gender'] = $current_user->getGender(); + if (in_array(self::UserProfileScope_Profile, $scopes)) + { + // Profile Claims + $assets_url = $this->configuration_service->getConfigValue('Assets.Url'); + $pic_url = $current_user->getPic(); + $pic_url = str_contains($pic_url, 'http') ? $pic_url : $assets_url . $pic_url; + + $claim_set->addClaim(new JWTClaim(StandardClaims::Name, new StringOrURI($current_user->getFullName()))); + $claim_set->addClaim(new JWTClaim(StandardClaims::GivenName, new StringOrURI($current_user->getFirstName()))); + $claim_set->addClaim(new JWTClaim(StandardClaims::FamilyName, new StringOrURI($current_user->getLastName()))); + $claim_set->addClaim(new JWTClaim(StandardClaims::NickName, new StringOrURI($current_user->getNickName()))); + $claim_set->addClaim(new JWTClaim(StandardClaims::Picture, new StringOrURI($pic_url))); + $claim_set->addClaim(new JWTClaim(StandardClaims::Birthdate, new StringOrURI($current_user->getDateOfBirth()))); + $claim_set->addClaim(new JWTClaim(StandardClaims::Gender, new StringOrURI($current_user->getGender()))); + $claim_set->addClaim(new JWTClaim(StandardClaims::Locale, new StringOrURI($current_user->getLanguage()))); } - if(in_array(self::UserProfileScope_Email, $scopes)){ + if (in_array(self::UserProfileScope_Email, $scopes)) + { // Address Claim - $data['email'] = $current_user->getEmail(); + $claim_set->addClaim(new JWTClaim(StandardClaims::Email, new StringOrURI($current_user->getEmail()))); + $claim_set->addClaim(new JWTClaim(StandardClaims::EmailVerified, new JsonValue(true))); } - } - catch(Exception $ex){ + } catch (Exception $ex) { $this->log_service->error($ex); throw $ex; } - return $data; + return $claim_set; } } \ No newline at end of file diff --git a/app/services/openid/AuthenticationStrategy.php b/app/services/openid/AuthenticationStrategy.php deleted file mode 100644 index 101a6106..00000000 --- a/app/services/openid/AuthenticationStrategy.php +++ /dev/null @@ -1,26 +0,0 @@ -with('context', $context); - } - - public function doConsent(OpenIdAuthenticationRequest $request, RequestContext $context) - { - return Redirect::action('UserController@getConsent')->with('context', $context); - } -} \ No newline at end of file diff --git a/app/services/openid/MementoRequestService.php b/app/services/openid/MementoRequestService.php deleted file mode 100644 index 3bb45d68..00000000 --- a/app/services/openid/MementoRequestService.php +++ /dev/null @@ -1,83 +0,0 @@ - $value) { - if (stristr($key, "openid") !== false) { - array_push($openid_params, $key); - } - } - if (count($openid_params) > 0) { - Input::flashOnly($openid_params); - return true; - } else { - $old_data = Input::old(); - $openid_params = array(); - foreach ($old_data as $key => $value) { - if (stristr($key, "openid") !== false) { - array_push($openid_params, $key); - } - } - if (count($openid_params) > 0) { - Session::reflash(); - return true; - } - } - - return false; - } - - public function getCurrentRequest() - { - $msg = new OpenIdMessage(Input::all()); - if (!$msg->isValid()) { - $msg = null; - $old_data = Input::old(); - $openid_params = array(); - foreach ($old_data as $key => $value) { - if (stristr($key, "openid") !== false) { - $openid_params[$key] = $value; - } - } - if (count($openid_params) > 0) { - $msg = new OpenIdMessage($openid_params); - } - } - return $msg; - } - - public function clearCurrentRequest() - { - $old_data = Input::old(); - $openid_params = array(); - - foreach ($old_data as $key => $value) { - if (stristr($key, "openid") !== false) { - array_push($openid_params, $key); - } - } - - if (count($openid_params) > 0) { - foreach ($openid_params as $open_id_param) { - Session::forget($open_id_param); - Session::remove($open_id_param); - } - } - } -} \ No newline at end of file diff --git a/app/services/openid/OpenIdMementoSessionSerializerService.php b/app/services/openid/OpenIdMementoSessionSerializerService.php new file mode 100644 index 00000000..00796b30 --- /dev/null +++ b/app/services/openid/OpenIdMementoSessionSerializerService.php @@ -0,0 +1,58 @@ +getState())); + Session::put('openid.request.state', $state); + Session::save(); + } + + /** + * @return OpenIdMessageMemento + */ + public function load() + { + $state = Session::get('openid.request.state', null); + + if(is_null($state)) return null; + + $state = json_decode( base64_decode($state), true); + + return OpenIdMessageMemento::buildFromState($state); + } + + /** + * @return void + */ + public function forget() + { + Session::remove('openid.request.state'); + Session::save(); + } + + /** + * @return bool + */ + public function exists() + { + return Session::has('openid.request.state'); + } +} \ No newline at end of file diff --git a/app/services/openid/OpenIdProvider.php b/app/services/openid/OpenIdProvider.php index 50d047de..771375ee 100644 --- a/app/services/openid/OpenIdProvider.php +++ b/app/services/openid/OpenIdProvider.php @@ -17,8 +17,7 @@ class OpenIdProvider extends ServiceProvider { public function register() { //register on boot bc we rely on Illuminate\Redis\ServiceProvider\RedisServiceProvider - App::singleton(OpenIdServiceCatalog::MementoService, 'services\\openid\\MementoRequestService'); - App::singleton(OpenIdServiceCatalog::AuthenticationStrategy, 'services\\openid\\AuthenticationStrategy'); + App::singleton(OpenIdServiceCatalog::MementoSerializerService, 'services\\openid\\OpenIdMementoSessionSerializerService'); App::singleton(OpenIdServiceCatalog::ServerExtensionsService, 'services\\openid\\ServerExtensionsService'); App::singleton(OpenIdServiceCatalog::AssociationService, 'services\\openid\\AssociationService'); App::singleton(OpenIdServiceCatalog::TrustedSitesService, 'services\\openid\\TrustedSitesService'); diff --git a/app/services/openid/UserService.php b/app/services/openid/UserService.php index 8b9b17a8..50c20bca 100644 --- a/app/services/openid/UserService.php +++ b/app/services/openid/UserService.php @@ -2,225 +2,198 @@ namespace services\openid; use auth\IUserRepository; -use openid\model\IOpenIdUser; +use auth\User; use Exception; +use openid\model\IOpenIdUser; use openid\services\IUserService; -use utils\services\ILogService; use utils\db\ITransactionService; +use utils\services\ILogService; + /** * Class UserService * @package services\openid */ -class UserService implements IUserService +final class UserService implements IUserService { - private $repository; - private $log_service; - private $tx_service; + /** + * @var IUserRepository + */ + private $repository; + /** + * @var ILogService + */ + private $log_service; + /** + * @var ITransactionService + */ + private $tx_service; - /** - * @param IUserRepository $repository - * @param ITransactionService $tx_service - * @param ILogService $log_service - */ - public function __construct(IUserRepository $repository, ITransactionService $tx_service, ILogService $log_service){ - $this->repository = $repository; - $this->log_service = $log_service; - $this->tx_service = $tx_service; - } - - - /** - * Associate openid url with given user - * @param IOpenIdUser $user - * @param $proposed_username - * @return bool|IOpenIdUser - * @throws \Exception - */ - public function associateUser(IOpenIdUser &$user, $proposed_username) + /** + * @param IUserRepository $repository + * @param ITransactionService $tx_service + * @param ILogService $log_service + */ + public function __construct(IUserRepository $repository, ITransactionService $tx_service, ILogService $log_service) { - try { - $repository = $this->repository; - if (!is_null($user) && strval($user->identifier) === strval($user->external_identifier)) { - $this->tx_service->transaction(function () use ($proposed_username,&$user,&$repository) { - - $done = false; - $fragment_nbr = 1; - $aux_proposed_username = $proposed_username; - do { - - $old_user = $repository->getOneByCriteria(array( - array('name' => 'identifier','op' => '=','value' => $aux_proposed_username), - array('name' => 'id','op' => '<>','value' => $user->id) )); - - if (is_null($old_user)) { - - $user->identifier = $aux_proposed_username; - $done = $repository->update($user); - } else { - $aux_proposed_username = $proposed_username . "." . $fragment_nbr; - $fragment_nbr++; - } - - } while (!$done); - return $user; - }); - } - } catch (Exception $ex) { - $this->log_service->error($ex); - throw $ex; - } - return false; + $this->repository = $repository; + $this->log_service = $log_service; + $this->tx_service = $tx_service; } - /** - * @param $identifier - * @return mixed|void - * @throws \Exception - */ - public function updateLastLoginDate($identifier) - { - try { - $user = $this->repository->get($identifier); - if (!is_null($user)) { - $user->last_login_date = gmdate("Y-m-d H:i:s", time()); - $this->repository->update($user); - } - } catch (Exception $ex) { - $this->log_service->error($ex); - throw $ex; - } - } - /** - * @param $identifier - * @return mixed|void - * @throws \Exception - */ - public function updateFailedLoginAttempts($identifier) + /** + * @param $identifier + * @return mixed|void + * @throws \Exception + */ + public function updateLastLoginDate($identifier) { try { - $user = $this->repository->get($identifier); + $user = $this->repository->get($identifier); if (!is_null($user)) { - $user->login_failed_attempt+=1; + $user->last_login_date = gmdate("Y-m-d H:i:s", time()); $this->repository->update($user); } } catch (Exception $ex) { - $this->log_service->error($ex); - throw $ex; + $this->log_service->error($ex); + throw $ex; } } - /** - * @param $identifier - * @return mixed|void - * @throws \Exception - */ - public function lockUser($identifier) + /** + * @param $identifier + * @return mixed|void + * @throws \Exception + */ + public function updateFailedLoginAttempts($identifier) { try { - $user = $this->repository->get($identifier); + $user = $this->repository->get($identifier); + if (!is_null($user)) { + $user->login_failed_attempt += 1; + $this->repository->update($user); + } + } catch (Exception $ex) { + $this->log_service->error($ex); + throw $ex; + } + } + + /** + * @param $identifier + * @return mixed|void + * @throws \Exception + */ + public function lockUser($identifier) + { + try { + $user = $this->repository->get($identifier); if (!is_null($user)) { - $user->lock = true; - $this->repository->update($user); + $user->lock = true; + $this->repository->update($user); Log::warning(sprintf("User %d locked ", $identifier)); } } catch (Exception $ex) { - $this->log_service->error($ex); - throw $ex; + $this->log_service->error($ex); + throw $ex; } } - /** - * @param $identifier - * @return mixed|void - * @throws \Exception - */ - public function unlockUser($identifier) - { - try { - $user = $this->repository->get($identifier); - if (!is_null($user)) { - - $user->lock = false; - $this->repository->update($user); - - Log::warning(sprintf("User %d unlocked ", $identifier)); - } - } catch (Exception $ex) { - $this->log_service->error($ex); - throw $ex; - } - } - - /** - * @param $identifier - * @return mixed|void - * @throws \Exception - */ - public function activateUser($identifier) + /** + * @param $identifier + * @return mixed|void + * @throws \Exception + */ + public function unlockUser($identifier) { try { - $user = $this->repository->get($identifier); + $user = $this->repository->get($identifier); if (!is_null($user)) { - $user->active = true; - $this->repository->update($user); + + $user->lock = false; + $this->repository->update($user); + + Log::warning(sprintf("User %d unlocked ", $identifier)); } } catch (Exception $ex) { - $this->log_service->error($ex); - throw $ex; + $this->log_service->error($ex); + throw $ex; } } - /** - * @param $identifier - * @return mixed|void - * @throws \Exception - */ - public function deActivateUser($identifier) - { - try { - $user = $this->repository->get($identifier); - if (!is_null($user)) { - $user->active = false; - $this->repository->update($user); - } - } catch (Exception $ex) { - $this->log_service->error($ex); - throw $ex; - } - } - - /** - * @param $identifier - * @param $show_pic - * @param $show_full_name - * @param $show_email - * @return bool - * @throws \Exception - */ - public function saveProfileInfo($identifier, $show_pic, $show_full_name, $show_email) + /** + * @param $identifier + * @return mixed|void + * @throws \Exception + */ + public function activateUser($identifier) { try { - $user = $this->repository->get($identifier); + $user = $this->repository->get($identifier); + if (!is_null($user)) { + $user->active = true; + $this->repository->update($user); + } + } catch (Exception $ex) { + $this->log_service->error($ex); + throw $ex; + } + } + + /** + * @param $identifier + * @return mixed|void + * @throws \Exception + */ + public function deActivateUser($identifier) + { + try { + $user = $this->repository->get($identifier); + if (!is_null($user)) { + $user->active = false; + $this->repository->update($user); + } + } catch (Exception $ex) { + $this->log_service->error($ex); + throw $ex; + } + } + + /** + * @param $identifier + * @param $show_pic + * @param $show_full_name + * @param $show_email + * @return bool + * @throws \Exception + */ + public function saveProfileInfo($identifier, $show_pic, $show_full_name, $show_email) + { + try { + $user = $this->repository->get($identifier); if (!is_null($user)) { $user->public_profile_show_photo = $show_pic; $user->public_profile_show_fullname = $show_full_name; $user->public_profile_show_email = $show_email; - return $this->repository->update($user); + + return $this->repository->update($user); } } catch (Exception $ex) { - $this->log_service->error($ex); - throw $ex; + $this->log_service->error($ex); + throw $ex; } - return false; + + return false; } - public function get($id){ + public function get($id) + { return $this->repository->get($id); } + /** * @param int $page_nbr * @param int $page_size @@ -230,6 +203,59 @@ class UserService implements IUserService */ public function getAll($page_nbr = 1, $page_size = 10, array $filters = array(), array $fields = array('*')) { - return $this->repository->getByPage($page_nbr, $page_size, $filters,$fields); + return $this->repository->getAll($page_nbr, $page_size, $filters, $fields); } + + /** + * @param \Member $member + * @return IOpenIdUser + */ + public function buildUser(\Member $member) + { + $repository = $this->repository; + return $this->tx_service->transaction(function () use($member, $repository){ + //create user + $user = new User(); + $user->external_identifier = $member->ID; + $user->identifier = $member->ID; + $user->last_login_date = gmdate("Y-m-d H:i:s", time()); + $user->active = true; + $user->lock = false; + $user->login_failed_attempt = 0; + + $repository->add($user); + $proposed_username = strtolower($member->FirstName . "." . $member->Surname); + $done = false; + $fragment_nbr = 1; + $aux_proposed_username = $proposed_username; + do + { + + $old_user = $repository->getOneByCriteria + ( + array + ( + array('name' => 'identifier', 'op' => '=', 'value' => $aux_proposed_username), + array('name' => 'id', 'op' => '<>', 'value' => $user->id) + ) + ); + + if (is_null($old_user)) + { + + $user->identifier = $aux_proposed_username; + $done = $repository->update($user); + } + else + { + $aux_proposed_username = $proposed_username . "." . $fragment_nbr; + $fragment_nbr++; + } + + } while (!$done); + + return $user; + }); + } + } \ No newline at end of file diff --git a/app/services/security_policies/AbstractBlacklistSecurityPolicy.php b/app/services/security_policies/AbstractBlacklistSecurityPolicy.php index bf67b3b0..50d7c78a 100644 --- a/app/services/security_policies/AbstractBlacklistSecurityPolicy.php +++ b/app/services/security_policies/AbstractBlacklistSecurityPolicy.php @@ -2,17 +2,17 @@ namespace services; +use Auth; use BannedIP; use DB; use Log; -use Auth; +use utils\db\ITransactionService; +use utils\IPHelper; use utils\services\ICacheService; use utils\services\ILockManagerService; use utils\services\ISecurityPolicy; use utils\services\ISecurityPolicyCounterMeasure; use utils\services\IServerConfigurationService; -use utils\IPHelper; -use utils\db\ITransactionService; /** * Class AbstractBlacklistSecurityPolicy @@ -22,22 +22,30 @@ abstract class AbstractBlacklistSecurityPolicy implements ISecurityPolicy { protected $server_configuration_service; + /** + * @var ISecurityPolicyCounterMeasure + */ protected $counter_measure; protected $lock_manager_service; protected $cache_service; - protected $tx_service; + protected $tx_service; - /** - * @param IServerConfigurationService $server_configuration_service - * @param ILockManagerService $lock_manager_service - * @param ICacheService $cache_service - * @param ITransactionService $tx_service - */ - public function __construct(IServerConfigurationService $server_configuration_service, ILockManagerService $lock_manager_service, ICacheService $cache_service,ITransactionService $tx_service) { + /** + * @param IServerConfigurationService $server_configuration_service + * @param ILockManagerService $lock_manager_service + * @param ICacheService $cache_service + * @param ITransactionService $tx_service + */ + public function __construct( + IServerConfigurationService $server_configuration_service, + ILockManagerService $lock_manager_service, + ICacheService $cache_service, + ITransactionService $tx_service + ) { $this->server_configuration_service = $server_configuration_service; - $this->lock_manager_service = $lock_manager_service; - $this->cache_service = $cache_service; - $this->tx_service = $tx_service; + $this->lock_manager_service = $lock_manager_service; + $this->cache_service = $cache_service; + $this->tx_service = $tx_service; } public function setCounterMeasure(ISecurityPolicyCounterMeasure $counter_measure) @@ -55,24 +63,26 @@ abstract class AbstractBlacklistSecurityPolicy implements ISecurityPolicy try { $remote_address = IPHelper::getUserIp(); //try to create on cache - $this->cache_service->addSingleValue($remote_address, $initial_hits, intval($this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.BannedIpLifeTimeSeconds"))); + $this->cache_service->addSingleValue($remote_address, $initial_hits, + intval($this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.BannedIpLifeTimeSeconds"))); - Log::warning(sprintf("AbstractBlacklistSecurityPolicy: Banning ip %s by Exception %s", $remote_address, $exception_type)); + Log::warning(sprintf("AbstractBlacklistSecurityPolicy: Banning ip %s by Exception %s", $remote_address, + $exception_type)); //try to create on db - $this->tx_service->transaction(function () use ($remote_address, $exception_type, $initial_hits) { + $this->tx_service->transaction(function () use ($remote_address, $exception_type, $initial_hits) { - $banned_ip = BannedIP::where("ip", "=", $remote_address)->first(); + $banned_ip = BannedIP::where("ip", "=", $remote_address)->first(); if (!$banned_ip) { - $banned_ip = new BannedIP(); + $banned_ip = new BannedIP(); $banned_ip->ip = $remote_address; } $banned_ip->exception_type = $exception_type; - $banned_ip->hits = $initial_hits; + $banned_ip->hits = $initial_hits; - if(Auth::check()){ - $banned_ip->user_id = Auth::user()->getId(); + if (Auth::check()) { + $banned_ip->user_id = Auth::user()->getId(); } $banned_ip->Save(); diff --git a/app/services/security_policies/BlacklistSecurityPolicy.php b/app/services/security_policies/BlacklistSecurityPolicy.php index eedd4a8e..00e1aa2b 100644 --- a/app/services/security_policies/BlacklistSecurityPolicy.php +++ b/app/services/security_policies/BlacklistSecurityPolicy.php @@ -8,12 +8,13 @@ use DB; use Exception; use Log; use UserExceptionTrail; +use utils\db\ITransactionService; use utils\exceptions\UnacquiredLockException; +use utils\IPHelper; use utils\services\ICacheService; use utils\services\ILockManagerService; use utils\services\IServerConfigurationService; -use utils\IPHelper; -use utils\db\ITransactionService; + /** * Class BlacklistSecurityPolicy * implements check point security pattern @@ -24,29 +25,66 @@ class BlacklistSecurityPolicy extends AbstractBlacklistSecurityPolicy private $exception_dictionary = array(); - /** - * @param IServerConfigurationService $server_configuration_service - * @param ILockManagerService $lock_manager_service - * @param ICacheService $cache_service - * @param ITransactionService $tx_service - */ - public function __construct(IServerConfigurationService $server_configuration_service, ILockManagerService $lock_manager_service, ICacheService $cache_service,ITransactionService $tx_service) - { - parent::__construct($server_configuration_service, $lock_manager_service, $cache_service,$tx_service); + /** + * @param IServerConfigurationService $server_configuration_service + * @param ILockManagerService $lock_manager_service + * @param ICacheService $cache_service + * @param ITransactionService $tx_service + */ + public function __construct( + IServerConfigurationService $server_configuration_service, + ILockManagerService $lock_manager_service, + ICacheService $cache_service, + ITransactionService $tx_service + ) { + parent::__construct($server_configuration_service, $lock_manager_service, $cache_service, $tx_service); // here we configure on which exceptions are we interested and the max occurrence attempts and initial delay on tar pit for // offending IP address $this->exception_dictionary = array( - 'openid\exceptions\ReplayAttackException' => array(null,'BlacklistSecurityPolicy.ReplayAttackExceptionInitialDelay'), - 'openid\exceptions\InvalidNonce' => array('BlacklistSecurityPolicy.MaxInvalidNonceAttempts','BlacklistSecurityPolicy.InvalidNonceInitialDelay'), - 'openid\exceptions\InvalidOpenIdMessageException' => array('BlacklistSecurityPolicy.MaxInvalidOpenIdMessageExceptionAttempts','BlacklistSecurityPolicy.InvalidOpenIdMessageExceptionInitialDelay'), - 'openid\exceptions\OpenIdInvalidRealmException' => array('BlacklistSecurityPolicy.MaxOpenIdInvalidRealmExceptionAttempts','BlacklistSecurityPolicy.OpenIdInvalidRealmExceptionInitialDelay'), - 'openid\exceptions\InvalidOpenIdMessageMode' => array('BlacklistSecurityPolicy.MaxInvalidOpenIdMessageModeAttempts','BlacklistSecurityPolicy.InvalidOpenIdMessageModeInitialDelay'), - 'openid\exceptions\InvalidOpenIdAuthenticationRequestMode' => array('BlacklistSecurityPolicy.MaxInvalidOpenIdAuthenticationRequestModeAttempts','BlacklistSecurityPolicy.InvalidOpenIdAuthenticationRequestModeInitialDelay'), - 'openid\exceptions\InvalidAssociation' => array('BlacklistSecurityPolicy.MaxInvalidAssociationAttempts','BlacklistSecurityPolicy.InvalidAssociationInitialDelay'), - 'auth\exceptions\AuthenticationException' => array('BlacklistSecurityPolicy.MaxAuthenticationExceptionAttempts','BlacklistSecurityPolicy.AuthenticationExceptionInitialDelay'), - 'oauth2\exceptions\ReplayAttackException' => array(null,'BlacklistSecurityPolicy.OAuth2.AuthCodeReplayAttackInitialDelay'), - 'oauth2\exceptions\InvalidAuthorizationCodeException' => array('BlacklistSecurityPolicy.OAuth2.MaxInvalidAuthorizationCodeAttempts','BlacklistSecurityPolicy.OAuth2.InvalidAuthorizationCodeInitialDelay'), - 'oauth2\exceptions\BearerTokenDisclosureAttemptException' => array('BlacklistSecurityPolicy.OAuth2.MaxInvalidBearerTokenDisclosureAttempt','BlacklistSecurityPolicy.OAuth2.BearerTokenDisclosureAttemptInitialDelay'), + 'openid\exceptions\ReplayAttackException' => array( + 'BlacklistSecurityPolicy.MaxReplayAttackExceptionAttempts', + 'BlacklistSecurityPolicy.ReplayAttackExceptionInitialDelay' + ), + 'openid\exceptions\InvalidNonce' => array( + 'BlacklistSecurityPolicy.MaxInvalidNonceAttempts', + 'BlacklistSecurityPolicy.InvalidNonceInitialDelay' + ), + 'openid\exceptions\InvalidOpenIdMessageException' => array( + 'BlacklistSecurityPolicy.MaxInvalidOpenIdMessageExceptionAttempts', + 'BlacklistSecurityPolicy.InvalidOpenIdMessageExceptionInitialDelay' + ), + 'openid\exceptions\OpenIdInvalidRealmException' => array( + 'BlacklistSecurityPolicy.MaxOpenIdInvalidRealmExceptionAttempts', + 'BlacklistSecurityPolicy.OpenIdInvalidRealmExceptionInitialDelay' + ), + 'openid\exceptions\InvalidOpenIdMessageMode' => array( + 'BlacklistSecurityPolicy.MaxInvalidOpenIdMessageModeAttempts', + 'BlacklistSecurityPolicy.InvalidOpenIdMessageModeInitialDelay' + ), + 'openid\exceptions\InvalidOpenIdAuthenticationRequestMode' => array( + 'BlacklistSecurityPolicy.MaxInvalidOpenIdAuthenticationRequestModeAttempts', + 'BlacklistSecurityPolicy.InvalidOpenIdAuthenticationRequestModeInitialDelay' + ), + 'openid\exceptions\InvalidAssociation' => array( + 'BlacklistSecurityPolicy.MaxInvalidAssociationAttempts', + 'BlacklistSecurityPolicy.InvalidAssociationInitialDelay' + ), + 'auth\exceptions\AuthenticationException' => array( + 'BlacklistSecurityPolicy.MaxAuthenticationExceptionAttempts', + 'BlacklistSecurityPolicy.AuthenticationExceptionInitialDelay' + ), + 'oauth2\exceptions\ReplayAttackException' => array( + 'BlacklistSecurityPolicy.OAuth2.MaxAuthCodeReplayAttackAttempts', + 'BlacklistSecurityPolicy.OAuth2.AuthCodeReplayAttackInitialDelay' + ), + 'oauth2\exceptions\InvalidAuthorizationCodeException' => array( + 'BlacklistSecurityPolicy.OAuth2.MaxInvalidAuthorizationCodeAttempts', + 'BlacklistSecurityPolicy.OAuth2.InvalidAuthorizationCodeInitialDelay' + ), + 'oauth2\exceptions\BearerTokenDisclosureAttemptException' => array( + 'BlacklistSecurityPolicy.OAuth2.MaxInvalidBearerTokenDisclosureAttempt', + 'BlacklistSecurityPolicy.OAuth2.BearerTokenDisclosureAttemptInitialDelay' + ), ); } @@ -60,8 +98,9 @@ class BlacklistSecurityPolicy extends AbstractBlacklistSecurityPolicy $remote_address = IPHelper::getUserIp(); try { //check if banned ip is on cache ... - if ($this->cache_service->incCounterIfExists($remote_address)){ + if ($this->cache_service->incCounterIfExists($remote_address)) { $this->counter_measure->trigger(); + return false; } //check on db @@ -70,7 +109,7 @@ class BlacklistSecurityPolicy extends AbstractBlacklistSecurityPolicy $this->lock_manager_service->acquireLock("lock.ip." . $remote_address); try { //check lifetime - $issued = $banned_ip->created_at; + $issued = $banned_ip->created_at; $utc_now = gmdate("Y-m-d H:i:s", time()); $utc_now = DateTime::createFromFormat("Y-m-d H:i:s", $utc_now); //get time lived on seconds @@ -78,30 +117,32 @@ class BlacklistSecurityPolicy extends AbstractBlacklistSecurityPolicy if ($time_lived_seconds >= intval($this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.BannedIpLifeTimeSeconds"))) { //void banned ip $banned_ip->delete(); + return true; } $banned_ip->hits = $banned_ip->hits + 1; $banned_ip->Save(); //save ip on cache - $this->cache_service->addSingleValue($banned_ip->ip, $banned_ip->hits,intval($this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.BannedIpLifeTimeSeconds") - $time_lived_seconds)); - } - catch (Exception $ex) { + $this->cache_service->addSingleValue($banned_ip->ip, $banned_ip->hits, + intval($this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.BannedIpLifeTimeSeconds") - $time_lived_seconds)); + } catch (Exception $ex) { Log::error($ex); } //release lock $this->lock_manager_service->releaseLock("lock.ip." . $remote_address); $this->counter_measure->trigger(); + return false; } - } - catch (UnacquiredLockException $ex1) { + } catch (UnacquiredLockException $ex1) { Log::error($ex1); $res = false; } catch (Exception $ex) { Log::error($ex); $res = false; } + return $res; } @@ -112,23 +153,56 @@ class BlacklistSecurityPolicy extends AbstractBlacklistSecurityPolicy */ public function apply(Exception $ex) { - try { - $remote_ip = IPHelper::getUserIp(); + try + { + $remote_ip = IPHelper::getUserIp(); $exception_class = get_class($ex); //check exception count by type on last "MinutesWithoutExceptions" minutes... $exception_count = intval(UserExceptionTrail::where('from_ip', '=', $remote_ip) ->where('exception_type', '=', $exception_class) - ->where('created_at', '>', DB::raw('( UTC_TIMESTAMP() - INTERVAL ' . $this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.MinutesWithoutExceptions") . ' MINUTE )')) + ->where('created_at', '>', + DB::raw('( UTC_TIMESTAMP() - INTERVAL ' . $this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.MinutesWithoutExceptions") . ' MINUTE )')) ->count()); - if(array_key_exists($exception_class,$this->exception_dictionary)){ - $params = $this->exception_dictionary[$exception_class]; - $max_attempts = !is_null($params[0]) && !empty($params[0])? intval($this->server_configuration_service->getConfigValue($params[0])):0; + if (array_key_exists($exception_class, $this->exception_dictionary)) + { + $params = $this->exception_dictionary[$exception_class]; + $max_attempts = !is_null($params[0]) && !empty($params[0]) ? intval($this->server_configuration_service->getConfigValue($params[0])) : 0; + + Log::info + ( + sprintf + ( + 'IP %s, - exception_class %s - exception_count %s - max allowed attempts %s', + $remote_ip, + $exception_class, + $exception_count, + $max_attempts + ) + ); + $initial_delay_on_tar_pit = intval($this->server_configuration_service->getConfigValue($params[1])); if ($exception_count >= $max_attempts) + { + Log::warning + ( + sprintf + ( + 'banning IP %s, - exception_class %s - exception_count %s - max allowed attempts %s', + $remote_ip, + $exception_class, + $exception_count, + $max_attempts + ) + ); + $this->createBannedIP($initial_delay_on_tar_pit, $exception_class); + } } - } catch (Exception $ex) { + } + catch (Exception $ex) + { Log::error($ex); + throw $ex; } } diff --git a/app/services/security_policies/DelayCounterMeasure.php b/app/services/security_policies/DelayCounterMeasure.php index 2b2a9853..89f5219e 100644 --- a/app/services/security_policies/DelayCounterMeasure.php +++ b/app/services/security_policies/DelayCounterMeasure.php @@ -8,6 +8,10 @@ use utils\services\ICacheService; use utils\services\ISecurityPolicyCounterMeasure; use utils\IPHelper; +/** + * Class DelayCounterMeasure + * @package services + */ class DelayCounterMeasure implements ISecurityPolicyCounterMeasure { private $cache_service; diff --git a/app/services/utils/BannedIPService.php b/app/services/utils/BannedIPService.php index 89ac7f5a..b5f656e0 100644 --- a/app/services/utils/BannedIPService.php +++ b/app/services/utils/BannedIPService.php @@ -1,45 +1,50 @@ cache_service = $cache_service; + /** + * @param ICacheService $cache_service + * @param IServerConfigurationService $server_configuration_service + * @param IAuthService $auth_service + * @param ILogService $log_service + * @param ITransactionService $tx_service + */ + public function __construct( + ICacheService $cache_service, + IServerConfigurationService $server_configuration_service, + IAuthService $auth_service, + ILogService $log_service, + ITransactionService $tx_service + ) { + + $this->cache_service = $cache_service; $this->server_configuration_service = $server_configuration_service; - $this->log_service = $log_service; - $this->auth_service = $auth_service; - $this->tx_service = $tx_service; + $this->log_service = $log_service; + $this->auth_service = $auth_service; + $this->tx_service = $tx_service; } /** @@ -53,9 +58,10 @@ class BannedIPService implements IBannedIPService { try { $remote_address = Request::server('REMOTE_ADDR'); //try to create on cache - $this->cache_service->addSingleValue($remote_address, $initial_hits, intval($this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.BannedIpLifeTimeSeconds"))); + $this->cache_service->addSingleValue($remote_address, $initial_hits, + intval($this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.BannedIpLifeTimeSeconds"))); - $this->tx_service->transaction(function () use ($remote_address, $exception_type, $initial_hits,&$res) { + $this->tx_service->transaction(function () use ($remote_address, $exception_type, $initial_hits, &$res) { $banned_ip = BannedIP::where("ip", "=", $remote_address)->first(); if (!$banned_ip) { @@ -63,34 +69,36 @@ class BannedIPService implements IBannedIPService { $banned_ip->ip = $remote_address; } $banned_ip->exception_type = $exception_type; - $banned_ip->hits = $initial_hits; + $banned_ip->hits = $initial_hits; - if(Auth::check()){ - $banned_ip->user_id = Auth::user()->getId(); - } + if (Auth::check()) { + $banned_ip->user_id = Auth::user()->getId(); + } - $res = $banned_ip->Save(); + $res = $banned_ip->Save(); }); } catch (Exception $ex) { $this->log_service->error($ex); $res = false; } + return $res; } public function delete($ip) { - $res = false; - $cache_service = $this->cache_service; - $this_var = $this; - $this->tx_service->transaction(function () use ($ip,&$res,&$cache_service,&$this_var) { + $res = false; + $cache_service = $this->cache_service; + $this_var = $this; + $this->tx_service->transaction(function () use ($ip, &$res, &$cache_service, &$this_var) { - if($banned_ip = $this_var->getByIP($ip)){ + if ($banned_ip = $this_var->getByIP($ip)) { $res = $banned_ip->delete(); - $cache_service->delete($ip); + $cache_service->delete($ip); } }); + return $res; } @@ -99,13 +107,15 @@ class BannedIPService implements IBannedIPService { return BannedIP::find($id); } - public function getByIP($ip){ - return BannedIP::where('ip','=',$ip)->first(); + public function getByIP($ip) + { + return BannedIP::where('ip', '=', $ip)->first(); } public function getByPage($page_nbr = 1, $page_size = 10, array $filters = array(), array $fields = array('*')) { DB::getPaginator()->setCurrentPage($page_nbr); - return BannedIP::Filter($filters)->paginate($page_size,$fields); + + return BannedIP::Filter($filters)->paginate($page_size, $fields); } } \ No newline at end of file diff --git a/app/services/utils/ServerConfigurationService.php b/app/services/utils/ServerConfigurationService.php index 11249838..2230706a 100644 --- a/app/services/utils/ServerConfigurationService.php +++ b/app/services/utils/ServerConfigurationService.php @@ -7,9 +7,10 @@ use DB; use Exception; use openid\services\IServerConfigurationService as IOpenIdServerConfigurationService; use ServerConfiguration; +use utils\db\ITransactionService; use utils\services\ICacheService; use utils\services\IServerConfigurationService; -use utils\db\ITransactionService; + /** * Class ServerConfigurationService * @package services @@ -25,88 +26,132 @@ class ServerConfigurationService implements IOpenIdServerConfigurationService, I const DefaultNonceLifetime = 360; private $default_config_params; - private $tx_service; + private $tx_service; - /*** - * @param ICacheService $cache_service - * @param ITransactionService $tx_service - */ - public function __construct(ICacheService $cache_service, ITransactionService $tx_service) + /*** + * @param ICacheService $cache_service + * @param ITransactionService $tx_service + */ + public function __construct(ICacheService $cache_service, ITransactionService $tx_service) { $this->cache_service = $cache_service; - $this->tx_service = $tx_service; + $this->tx_service = $tx_service; $this->default_config_params = array(); //default config values //general - $this->default_config_params["MaxFailed.Login.Attempts"] = Config::get('server.MaxFailed_Login_Attempts', 10); - $this->default_config_params["MaxFailed.LoginAttempts.2ShowCaptcha"] = Config::get('server.MaxFailed_LoginAttempts_2ShowCaptcha', 3); - $this->default_config_params["Assets.Url"] = Config::get('server.Assets_Url', 'http://www.openstack.org/'); - // remember me cookie lifetime (minutes) - $this->default_config_params["Remember.ExpirationTime"] = Config::get('Remember.ExpirationTime',120); + $this->default_config_params["MaxFailed.Login.Attempts"] = Config::get('server.MaxFailed_Login_Attempts', 10); + $this->default_config_params["MaxFailed.LoginAttempts.2ShowCaptcha"] = Config::get('server.MaxFailed_LoginAttempts_2ShowCaptcha', + 3); + $this->default_config_params["Assets.Url"] = Config::get('server.Assets_Url', self::DefaultAssetsUrl ); + // remember me cookie lifetime (minutes) + $this->default_config_params["Remember.ExpirationTime"] = Config::get('Remember.ExpirationTime', 120); //openid - $this->default_config_params["OpenId.Private.Association.Lifetime"] = Config::get('server.OpenId_Private_Association_Lifetime', 240); - $this->default_config_params["OpenId.Session.Association.Lifetime"] = Config::get('server.OpenId_Session_Association_Lifetime', 21600); - $this->default_config_params["OpenId.Nonce.Lifetime"] = Config::get('server.OpenId_Nonce_Lifetime', 360); + $this->default_config_params["OpenId.Private.Association.Lifetime"] = Config::get('server.OpenId_Private_Association_Lifetime', + 240); + $this->default_config_params["OpenId.Session.Association.Lifetime"] = Config::get('server.OpenId_Session_Association_Lifetime', + 21600); + $this->default_config_params["OpenId.Nonce.Lifetime"] = Config::get('server.OpenId_Nonce_Lifetime', 360); //policies - $this->default_config_params["BlacklistSecurityPolicy.BannedIpLifeTimeSeconds"] = Config::get('server.BlacklistSecurityPolicy_BannedIpLifeTimeSeconds', 21600); - $this->default_config_params["BlacklistSecurityPolicy.MinutesWithoutExceptions"] = Config::get('server.BlacklistSecurityPolicy_MinutesWithoutExceptions', 5);; - $this->default_config_params["BlacklistSecurityPolicy.ReplayAttackExceptionInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_ReplayAttackExceptionInitialDelay', 10); - $this->default_config_params["BlacklistSecurityPolicy.MaxInvalidNonceAttempts"] = Config::get('server.BlacklistSecurityPolicy_MaxInvalidNonceAttempts', 10); - $this->default_config_params["BlacklistSecurityPolicy.InvalidNonceInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_InvalidNonceInitialDelay', 10); - $this->default_config_params["BlacklistSecurityPolicy.MaxInvalidOpenIdMessageExceptionAttempts"] = Config::get('server.BlacklistSecurityPolicy_MaxInvalidOpenIdMessageExceptionAttempts', 10); - $this->default_config_params["BlacklistSecurityPolicy.InvalidOpenIdMessageExceptionInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_InvalidOpenIdMessageExceptionInitialDelay', 10); - $this->default_config_params["BlacklistSecurityPolicy.MaxOpenIdInvalidRealmExceptionAttempts"] = Config::get('server.BlacklistSecurityPolicy_MaxOpenIdInvalidRealmExceptionAttempts', 10); - $this->default_config_params["BlacklistSecurityPolicy.OpenIdInvalidRealmExceptionInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_OpenIdInvalidRealmExceptionInitialDelay', 10); - $this->default_config_params["BlacklistSecurityPolicy.MaxInvalidOpenIdMessageModeAttempts"] = Config::get('server.BlacklistSecurityPolicy_MaxInvalidOpenIdMessageModeAttempts', 10); - $this->default_config_params["BlacklistSecurityPolicy.InvalidOpenIdMessageModeInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_InvalidOpenIdMessageModeInitialDelay', 10); - $this->default_config_params["BlacklistSecurityPolicy.MaxInvalidOpenIdAuthenticationRequestModeAttempts"] = Config::get('server.BlacklistSecurityPolicy_MaxInvalidOpenIdAuthenticationRequestModeAttempts', 10); - $this->default_config_params["BlacklistSecurityPolicy.InvalidOpenIdAuthenticationRequestModeInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_InvalidOpenIdAuthenticationRequestModeInitialDelay', 10); - $this->default_config_params["BlacklistSecurityPolicy.MaxAuthenticationExceptionAttempts"] = Config::get('server.BlacklistSecurityPolicy_MaxAuthenticationExceptionAttempts', 10); - $this->default_config_params["BlacklistSecurityPolicy.AuthenticationExceptionInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_AuthenticationExceptionInitialDelay', 20); - $this->default_config_params["BlacklistSecurityPolicy.MaxInvalidAssociationAttempts"] = Config::get('server.BlacklistSecurityPolicy_MaxInvalidAssociationAttempts', 10); - $this->default_config_params["BlacklistSecurityPolicy.InvalidAssociationInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_InvalidAssociationInitialDelay', 20); + $this->default_config_params["BlacklistSecurityPolicy.BannedIpLifeTimeSeconds"] = Config::get('server.BlacklistSecurityPolicy_BannedIpLifeTimeSeconds', + 21600); + $this->default_config_params["BlacklistSecurityPolicy.MinutesWithoutExceptions"] = Config::get('server.BlacklistSecurityPolicy_MinutesWithoutExceptions', + 5);; + $this->default_config_params["BlacklistSecurityPolicy.ReplayAttackExceptionInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_ReplayAttackExceptionInitialDelay', + 10); + $this->default_config_params["BlacklistSecurityPolicy.MaxInvalidNonceAttempts"] = Config::get('server.BlacklistSecurityPolicy_MaxInvalidNonceAttempts', + 10); + $this->default_config_params["BlacklistSecurityPolicy.InvalidNonceInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_InvalidNonceInitialDelay', + 10); + $this->default_config_params["BlacklistSecurityPolicy.MaxInvalidOpenIdMessageExceptionAttempts"] = Config::get('server.BlacklistSecurityPolicy_MaxInvalidOpenIdMessageExceptionAttempts', + 10); + $this->default_config_params["BlacklistSecurityPolicy.InvalidOpenIdMessageExceptionInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_InvalidOpenIdMessageExceptionInitialDelay', + 10); + $this->default_config_params["BlacklistSecurityPolicy.MaxOpenIdInvalidRealmExceptionAttempts"] = Config::get('server.BlacklistSecurityPolicy_MaxOpenIdInvalidRealmExceptionAttempts', + 10); + $this->default_config_params["BlacklistSecurityPolicy.OpenIdInvalidRealmExceptionInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_OpenIdInvalidRealmExceptionInitialDelay', + 10); + $this->default_config_params["BlacklistSecurityPolicy.MaxInvalidOpenIdMessageModeAttempts"] = Config::get('server.BlacklistSecurityPolicy_MaxInvalidOpenIdMessageModeAttempts', + 10); + $this->default_config_params["BlacklistSecurityPolicy.InvalidOpenIdMessageModeInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_InvalidOpenIdMessageModeInitialDelay', + 10); + $this->default_config_params["BlacklistSecurityPolicy.MaxInvalidOpenIdAuthenticationRequestModeAttempts"] = Config::get('server.BlacklistSecurityPolicy_MaxInvalidOpenIdAuthenticationRequestModeAttempts', + 10); + $this->default_config_params["BlacklistSecurityPolicy.InvalidOpenIdAuthenticationRequestModeInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_InvalidOpenIdAuthenticationRequestModeInitialDelay', + 10); + $this->default_config_params["BlacklistSecurityPolicy.MaxAuthenticationExceptionAttempts"] = Config::get('server.BlacklistSecurityPolicy_MaxAuthenticationExceptionAttempts', + 10); + $this->default_config_params["BlacklistSecurityPolicy.AuthenticationExceptionInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_AuthenticationExceptionInitialDelay', + 20); + $this->default_config_params["BlacklistSecurityPolicy.MaxInvalidAssociationAttempts"] = Config::get('server.BlacklistSecurityPolicy_MaxInvalidAssociationAttempts', + 10); + $this->default_config_params["BlacklistSecurityPolicy.InvalidAssociationInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_InvalidAssociationInitialDelay', + 20); //oauth2 - $this->default_config_params["OAuth2.Enable"] = Config::get('server.OAuth2_Enable', false); - $this->default_config_params["BlacklistSecurityPolicy.OAuth2.MaxAuthCodeReplayAttackAttempts"] = Config::get('server.BlacklistSecurityPolicy_OAuth2_MaxAuthCodeReplayAttackAttempts', 3); - $this->default_config_params["BlacklistSecurityPolicy.OAuth2.AuthCodeReplayAttackInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_OAuth2_AuthCodeReplayAttackInitialDelay', 10); + $this->default_config_params["OAuth2.Enable"] = Config::get('server.OAuth2_Enable', false); + $this->default_config_params["BlacklistSecurityPolicy.OAuth2.MaxAuthCodeReplayAttackAttempts"] = Config::get('server.BlacklistSecurityPolicy_OAuth2_MaxAuthCodeReplayAttackAttempts', + 3); + $this->default_config_params["BlacklistSecurityPolicy.OAuth2.AuthCodeReplayAttackInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_OAuth2_AuthCodeReplayAttackInitialDelay', + 10); - $this->default_config_params["BlacklistSecurityPolicy.OAuth2.MaxInvalidAuthorizationCodeAttempts"] = Config::get('server.BlacklistSecurityPolicy_OAuth2_MaxInvalidAuthorizationCodeAttempts', 3); - $this->default_config_params["BlacklistSecurityPolicy.OAuth2.InvalidAuthorizationCodeInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_OAuth2_InvalidAuthorizationCodeInitialDelay', 10); + $this->default_config_params["BlacklistSecurityPolicy.OAuth2.MaxInvalidAuthorizationCodeAttempts"] = Config::get('server.BlacklistSecurityPolicy_OAuth2_MaxInvalidAuthorizationCodeAttempts', + 3); + $this->default_config_params["BlacklistSecurityPolicy.OAuth2.InvalidAuthorizationCodeInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_OAuth2_InvalidAuthorizationCodeInitialDelay', + 10); - $this->default_config_params["BlacklistSecurityPolicy.OAuth2.MaxInvalidBearerTokenDisclosureAttempt"] = Config::get('server.BlacklistSecurityPolicy_OAuth2_MaxInvalidBearerTokenDisclosureAttempt', 3); - $this->default_config_params["BlacklistSecurityPolicy.OAuth2.BearerTokenDisclosureAttemptInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_OAuth2_BearerTokenDisclosureAttemptInitialDelay', 10); + $this->default_config_params["BlacklistSecurityPolicy.OAuth2.MaxInvalidBearerTokenDisclosureAttempt"] = Config::get('server.BlacklistSecurityPolicy_OAuth2_MaxInvalidBearerTokenDisclosureAttempt', + 3); + $this->default_config_params["BlacklistSecurityPolicy.OAuth2.BearerTokenDisclosureAttemptInitialDelay"] = Config::get('server.BlacklistSecurityPolicy_OAuth2_BearerTokenDisclosureAttemptInitialDelay', + 10); - $this->default_config_params["OAuth2.AuthorizationCode.Lifetime"] = Config::get('server.OAuth2_AuthorizationCode_Lifetime', 240); - $this->default_config_params["OAuth2.AccessToken.Lifetime"] = Config::get('server.OAuth2_AccessToken_Lifetime', 3600); + $this->default_config_params["OAuth2.AuthorizationCode.Lifetime"] = Config::get('server.OAuth2_AuthorizationCode_Lifetime', + 240); + $this->default_config_params["OAuth2.AccessToken.Lifetime"] = Config::get('server.OAuth2_AccessToken_Lifetime', + 3600); + + $this->default_config_params["OAuth2.IdToken.Lifetime"] = Config::get('server.OAuth2_IdToken_Lifetime', + 3600); //infinite by default - $this->default_config_params["OAuth2.RefreshToken.Lifetime"] = Config::get('server.OAuth2_RefreshToken_Lifetime', 0); + $this->default_config_params["OAuth2.RefreshToken.Lifetime"] = Config::get('server.OAuth2_RefreshToken_Lifetime', + 0); + + $this->default_config_params["OAuth2.AccessToken.Revoked.Lifetime"] = Config::get('server.OAuth2_AccessToken_Revoked_Lifetime', 600); + $this->default_config_params["OAuth2.AccessToken.Void.Lifetime"] = Config::get('server.OAuth2_AccessToken_Void_Lifetime', 600); + $this->default_config_params["OAuth2.RefreshToken.Revoked.Lifetime"] = Config::get('server.OAuth2_RefreshToken_Revoked_Lifetime', 600); //oauth2 policy defaults - $this->default_config_params["OAuth2SecurityPolicy.MinutesWithoutExceptions"] = Config::get('server.OAuth2SecurityPolicy_MinutesWithoutExceptions', 2); - $this->default_config_params["OAuth2SecurityPolicy.MaxBearerTokenDisclosureAttempts"] = Config::get('server.OAuth2SecurityPolicy_MaxBearerTokenDisclosureAttempts', 5); - $this->default_config_params["OAuth2SecurityPolicy.MaxInvalidClientExceptionAttempts"] = Config::get('server.OAuth2SecurityPolicy_MaxInvalidClientExceptionAttempts', 10); - $this->default_config_params["OAuth2SecurityPolicy.MaxInvalidRedeemAuthCodeAttempts"] = Config::get('server.OAuth2SecurityPolicy_MaxInvalidRedeemAuthCodeAttempts', 10); - $this->default_config_params["OAuth2SecurityPolicy.MaxInvalidInvalidClientCredentialsAttempts"] = Config::get('server.OAuth2SecurityPolicy_MaxInvalidInvalidClientCredentialsAttempts', 5); + $this->default_config_params["OAuth2SecurityPolicy.MinutesWithoutExceptions"] = Config::get('server.OAuth2SecurityPolicy_MinutesWithoutExceptions', + 2); + $this->default_config_params["OAuth2SecurityPolicy.MaxBearerTokenDisclosureAttempts"] = Config::get('server.OAuth2SecurityPolicy_MaxBearerTokenDisclosureAttempts', + 5); + $this->default_config_params["OAuth2SecurityPolicy.MaxInvalidClientExceptionAttempts"] = Config::get('server.OAuth2SecurityPolicy_MaxInvalidClientExceptionAttempts', + 10); + $this->default_config_params["OAuth2SecurityPolicy.MaxInvalidRedeemAuthCodeAttempts"] = Config::get('server.OAuth2SecurityPolicy_MaxInvalidRedeemAuthCodeAttempts', + 10); + $this->default_config_params["OAuth2SecurityPolicy.MaxInvalidInvalidClientCredentialsAttempts"] = Config::get('server.OAuth2SecurityPolicy_MaxInvalidInvalidClientCredentialsAttempts', + 5); //ssl $this->default_config_params["SSL.Enable"] = Config::get('server.SSL_Enable', true); + } public function getUserIdentityEndpointURL($identifier) { $url = action("UserController@getIdentity", array("identifier" => $identifier)); + return $url; } public function getOPEndpointURL() { $url = action("OpenIdProviderController@endpoint"); + return $url; } @@ -117,24 +162,26 @@ class ServerConfigurationService implements IOpenIdServerConfigurationService, I */ public function getConfigValue($key) { - $res = null; - $cache_service = $this->cache_service; - $default_config_params = $this->default_config_params; + $res = null; + $cache_service = $this->cache_service; + $default_config_params = $this->default_config_params; - $this->tx_service->transaction(function () use ($key, &$res,&$cache_service,&$default_config_params) { + $this->tx_service->transaction(function () use ($key, &$res, &$cache_service, &$default_config_params) { try { if (!$cache_service->exists($key)) { - if (!is_null($conf = ServerConfiguration::where('key', '=', $key)->first())) - $cache_service->addSingleValue($key, $conf->value); - else - if (isset($default_config_params[$key])) - $cache_service->addSingleValue($key, $default_config_params[$key]); - else { + if (!is_null($conf = ServerConfiguration::where('key', '=', $key)->first())) { + $cache_service->addSingleValue($key, $conf->value); + } else { + if (isset($default_config_params[$key])) { + $cache_service->addSingleValue($key, $default_config_params[$key]); + } else { $res = null; + return; } + } } $res = $cache_service->getSingleValue($key); @@ -156,25 +203,34 @@ class ServerConfigurationService implements IOpenIdServerConfigurationService, I public function saveConfigValue($key, $value) { - $res = false; - $cache_service = $this->cache_service; + $res = false; + $cache_service = $this->cache_service; - $this->tx_service->transaction(function () use ($key, $value, &$res,&$cache_service) { + $this->tx_service->transaction(function () use ($key, $value, &$res, &$cache_service) { $conf = ServerConfiguration::where('key', '=', $key)->first(); if (is_null($conf)) { - $conf = new ServerConfiguration(); - $conf->key = $key; + $conf = new ServerConfiguration(); + $conf->key = $key; $conf->value = $value; $res = $conf->Save(); } else { $conf->value = $value; - $res = $conf->Save(); + $res = $conf->Save(); } - $cache_service->delete($key); + $cache_service->delete($key); }); + return $res; } + + /** + * @return string + */ + public function getSiteUrl() + { + return Config::get('app.url'); + } } diff --git a/app/start/global.php b/app/start/global.php index 43e677ed..c749aa69 100644 --- a/app/start/global.php +++ b/app/start/global.php @@ -98,7 +98,7 @@ App::error(function (Exception $exception, $code) { if ($checkpoint_service) { $checkpoint_service->trackException($exception); } - return View::make('404'); + return Response::view('404', array(), 404); } }); @@ -110,7 +110,7 @@ App::error(function (InvalidOpenIdMessageException $exception, $code) { if ($checkpoint_service) { $checkpoint_service->trackException($exception); } - return View::make('404'); + return Response::view('404', array(), 404); } }); @@ -121,7 +121,7 @@ App::error(function (InvalidOAuth2Request $exception, $code) { if ($checkpoint_service) { $checkpoint_service->trackException($exception); } - return View::make('404'); + return Response::view('404', array(), 404); } }); diff --git a/app/strategies/DefaultLoginStrategy.php b/app/strategies/DefaultLoginStrategy.php index 30c6598f..30066080 100644 --- a/app/strategies/DefaultLoginStrategy.php +++ b/app/strategies/DefaultLoginStrategy.php @@ -1,4 +1,5 @@ getReturnTo(); if (is_null($return_to) || empty($return_to)) { - return \View::make('404'); + return Response::view('404', array(), 404); } $return_to = (strpos($return_to, "?") == false) ? $return_to . "?" . $query_string : $return_to . "&" . $query_string; return Redirect::to($return_to); diff --git a/app/strategies/IndirectResponseUrlFragmentStrategy.php b/app/strategies/IndirectResponseUrlFragmentStrategy.php index 779c50ea..c1c28d35 100644 --- a/app/strategies/IndirectResponseUrlFragmentStrategy.php +++ b/app/strategies/IndirectResponseUrlFragmentStrategy.php @@ -24,7 +24,7 @@ class IndirectResponseUrlFragmentStrategy implements IHttpResponseStrategy $return_to = $response->getReturnTo(); if (is_null($return_to) || empty($return_to)) { - return \View::make('404'); + return Response::view('404', array(), 404);; } $return_to = (strpos($return_to, "#") == false) ? $return_to . "#" . $fragment : $return_to . "&" . $fragment; diff --git a/app/strategies/OAuth2ConsentStrategy.php b/app/strategies/OAuth2ConsentStrategy.php index 4189db2d..9e2cb178 100644 --- a/app/strategies/OAuth2ConsentStrategy.php +++ b/app/strategies/OAuth2ConsentStrategy.php @@ -1,30 +1,51 @@ auth_service = $auth_service; $this->memento_service = $memento_service; @@ -34,16 +55,26 @@ class OAuth2ConsentStrategy implements IConsentStrategy { public function getConsent() { - $request = $this->memento_service->getCurrentAuthorizationRequest(); - $client_id = $request->getClientId(); + $auth_request = OAuth2AuthorizationRequestFactory::getInstance()->build + ( + OAuth2Message::buildFromMemento + ( + $this->memento_service->load() + ) + ); + + $client_id = $auth_request->getClientId(); $client = $this->client_service->getClientById($client_id); - $scopes = explode(' ',$request->getScope()); + $scopes = explode(' ',$auth_request->getScope()); $requested_scopes = $this->scope_service->getScopesByName($scopes); + $data = array(); $data['requested_scopes'] = $requested_scopes; $data['app_name'] = $client->getApplicationName(); - $data['redirect_to'] = $request->getRedirectUri(); + $data['redirect_to'] = $auth_request->getRedirectUri(); $data['website'] = $client->getWebsite(); + $data['tos_uri'] = $client->getTermOfServiceUri(); + $data['policy_uri'] = $client->getPolicyUri(); $app_logo = $client->getApplicationLogo(); @@ -57,6 +88,7 @@ class OAuth2ConsentStrategy implements IConsentStrategy { public function postConsent($trust_action) { $this->auth_service->setUserAuthorizationResponse($trust_action); + return Redirect::action('OAuth2ProviderController@authorize'); } } \ No newline at end of file diff --git a/app/strategies/OAuth2LoginStrategy.php b/app/strategies/OAuth2LoginStrategy.php index abb97e17..0e075e0f 100644 --- a/app/strategies/OAuth2LoginStrategy.php +++ b/app/strategies/OAuth2LoginStrategy.php @@ -3,32 +3,72 @@ namespace strategies; use Auth; -use oauth2\services\IMementoOAuth2AuthenticationRequestService; +use oauth2\factories\OAuth2AuthorizationRequestFactory; +use oauth2\OAuth2Message; +use oauth2\services\IMementoOAuth2SerializerService; +use oauth2\services\ISecurityContextService; use Redirect; -use View; use services\IUserActionService; -use utils\services\IAuthService; use utils\IPHelper; +use utils\services\IAuthService; +use View; +use Session; -class OAuth2LoginStrategy implements ILoginStrategy{ +/** + * Class OAuth2LoginStrategy + * @package strategies + */ +class OAuth2LoginStrategy implements ILoginStrategy +{ - private $memento_service; - private $user_action_service; - private $auth_service; + /** + * @var IMementoOAuth2SerializerService + */ + private $memento_service; + /** + * @var IUserActionService + */ + private $user_action_service; + /** + * @var IAuthService + */ + private $auth_service; - public function __construct(IAuthService $auth_service, - IMementoOAuth2AuthenticationRequestService $memento_service, - IUserActionService $user_action_service - ) - { - $this->memento_service = $memento_service; - $this->user_action_service = $user_action_service; - $this->auth_service = $auth_service; - } + /** + * @var ISecurityContextService + */ + private $security_context_service; - public function getLogin() + /** + * @param IAuthService $auth_service + * @param IMementoOAuth2SerializerService $memento_service + * @param IUserActionService $user_action_service + * @param ISecurityContextService $security_context_service + */ + public function __construct + ( + IAuthService $auth_service, + IMementoOAuth2SerializerService $memento_service, + IUserActionService $user_action_service, + ISecurityContextService $security_context_service + ) { - if (Auth::guest()) { + $this->memento_service = $memento_service; + $this->user_action_service = $user_action_service; + $this->auth_service = $auth_service; + $this->security_context_service = $security_context_service; + } + + public function getLogin() + { + if (Auth::guest()) + { + $requested_user_id = $this->security_context_service->get()->getRequestedUserId(); + if(!is_null($requested_user_id)) + { + Session::put('username', $this->auth_service->getUserById($requested_user_id)->getEmail()); + Session::save(); + } return View::make("login"); } else { return Redirect::action("UserController@getProfile"); @@ -37,14 +77,22 @@ class OAuth2LoginStrategy implements ILoginStrategy{ public function postLogin() { - $auth_request = $this->memento_service->getCurrentAuthorizationRequest(); - $this->user_action_service->addUserAction($this->auth_service->getCurrentUser(), IPHelper::getUserIp(), IUserActionService::LoginAction, $auth_request->getRedirectUri() ); + $auth_request = OAuth2AuthorizationRequestFactory::getInstance()->build( + OAuth2Message::buildFromMemento( + $this->memento_service->load() + ) + ); + + $this->user_action_service->addUserAction($this->auth_service->getCurrentUser(), IPHelper::getUserIp(), + IUserActionService::LoginAction, $auth_request->getRedirectUri()); + return Redirect::action("OAuth2ProviderController@authorize"); } public function cancelLogin() { - $this->auth_service->setUserAuthenticationResponse(IAuthService::AuthenticationResponse_Cancel); + $this->auth_service->setUserAuthenticationResponse(IAuthService::AuthenticationResponse_Cancel); + return Redirect::action("OAuth2ProviderController@authorize"); } } \ No newline at end of file diff --git a/app/strategies/OpenIdAuthenticationStrategy.php b/app/strategies/OpenIdAuthenticationStrategy.php new file mode 100644 index 00000000..c8092150 --- /dev/null +++ b/app/strategies/OpenIdAuthenticationStrategy.php @@ -0,0 +1,42 @@ +memento_service = $memento_service; $this->auth_service = $auth_service; @@ -38,14 +65,20 @@ class OpenIdConsentStrategy implements IConsentStrategy return View::make("openid.consent", $data); } + /** + * @return array + * @throws InvalidRequestContextException + */ private function getViewData() { - $context = Session::get('context'); + $context = Session::get('openid.auth.context'); + if (is_null($context)) throw new InvalidRequestContextException(); - $partial_views = $context->getPartials(); + + $partial_views = $context->getPartials(); $data = array(); - $request = $this->memento_service->getCurrentRequest(); + $request = OpenIdMessage::buildFromMemento( $this->memento_service->load()); $user = $this->auth_service->getCurrentUser(); $data['realm'] = $request->getParam(OpenIdProtocol::OpenIDProtocol_Realm); $data['openid_url'] = $this->server_configuration_service->getUserIdentityEndpointURL($user->getIdentifier()); @@ -53,14 +86,21 @@ class OpenIdConsentStrategy implements IConsentStrategy return $data; } + /** + * @param $trust_action + * @return mixed + * @throws InvalidOpenIdMessageException + */ public function postConsent($trust_action) { if (is_array($trust_action)) { - $msg = $this->memento_service->getCurrentRequest(); + $msg = OpenIdMessage::buildFromMemento( $this->memento_service->load()); if (is_null($msg) || !$msg->isValid()) throw new InvalidOpenIdMessageException(); $this->user_action_service->addUserAction($this->auth_service->getCurrentUser(), IPHelper::getUserIp(), IUserActionService::ConsentAction, $msg->getParam(OpenIdProtocol::OpenIDProtocol_Realm)); $this->auth_service->setUserAuthorizationResponse($trust_action[0]); + Session::remove('openid.auth.context'); + Session::save(); return Redirect::action('OpenIdProviderController@endpoint'); } return Redirect::action('UserController@getConsent'); diff --git a/app/strategies/OpenIdLoginStrategy.php b/app/strategies/OpenIdLoginStrategy.php index b206cb12..bd008622 100644 --- a/app/strategies/OpenIdLoginStrategy.php +++ b/app/strategies/OpenIdLoginStrategy.php @@ -3,29 +3,46 @@ namespace strategies; use Auth; -use Redirect; -use View; +use openid\OpenIdMessage; use openid\OpenIdProtocol; use openid\requests\OpenIdAuthenticationRequest; -use openid\responses\OpenIdNonImmediateNegativeAssertion; -use openid\services\IMementoOpenIdRequestService; -use openid\strategies\OpenIdResponseStrategyFactoryMethod; -use utils\IPHelper; +use openid\services\IMementoOpenIdSerializerService; +use Redirect; use services\IUserActionService; +use utils\IPHelper; use utils\services\IAuthService; +use View; - -class OpenIdLoginStrategy implements ILoginStrategy +/** + * Class OpenIdLoginStrategy + * @package strategies + */ +final class OpenIdLoginStrategy implements ILoginStrategy { + /** + * @var IMementoOpenIdSerializerService + */ private $memento_service; + /** + * @var IUserActionService + */ private $user_action_service; + /** + * @var IAuthService + */ private $auth_service; - public function __construct(IMementoOpenIdRequestService $memento_service, - IUserActionService $user_action_service, - IAuthService $auth_service) - { + /** + * @param IMementoOpenIdSerializerService $memento_service + * @param IUserActionService $user_action_service + * @param IAuthService $auth_service + */ + public function __construct( + IMementoOpenIdSerializerService $memento_service, + IUserActionService $user_action_service, + IAuthService $auth_service + ) { $this->memento_service = $memento_service; $this->user_action_service = $user_action_service; $this->auth_service = $auth_service; @@ -34,16 +51,18 @@ class OpenIdLoginStrategy implements ILoginStrategy public function getLogin() { if (Auth::guest()) { - $msg = $this->memento_service->getCurrentRequest(); + $msg = OpenIdMessage::buildFromMemento($this->memento_service->load()); $auth_request = new OpenIdAuthenticationRequest($msg); - $params = array('realm' => $auth_request->getRealm()); + $params = array('realm' => $auth_request->getRealm()); + if (!$auth_request->isIdentitySelectByOP()) { - $params['claimed_id'] = $auth_request->getClaimedId(); - $params['identity'] = $auth_request->getIdentity(); + $params['claimed_id'] = $auth_request->getClaimedId(); + $params['identity'] = $auth_request->getIdentity(); $params['identity_select'] = false; } else { $params['identity_select'] = true; } + return View::make("login", $params); } else { return Redirect::action("UserController@getProfile"); @@ -53,14 +72,17 @@ class OpenIdLoginStrategy implements ILoginStrategy public function postLogin() { //go to authentication flow again - $msg = $this->memento_service->getCurrentRequest(); - $this->user_action_service->addUserAction($this->auth_service->getCurrentUser(), IPHelper::getUserIp(), IUserActionService::LoginAction, $msg->getParam(OpenIdProtocol::OpenIDProtocol_Realm)); + $msg = OpenIdMessage::buildFromMemento($this->memento_service->load()); + $this->user_action_service->addUserAction($this->auth_service->getCurrentUser(), IPHelper::getUserIp(), + IUserActionService::LoginAction, $msg->getParam(OpenIdProtocol::OpenIDProtocol_Realm)); + return Redirect::action("OpenIdProviderController@endpoint"); } public function cancelLogin() { - $this->auth_service->setUserAuthenticationResponse(IAuthService::AuthenticationResponse_Cancel); - return Redirect::action("OpenIdProviderController@endpoint"); + $this->auth_service->setUserAuthenticationResponse(IAuthService::AuthenticationResponse_Cancel); + + return Redirect::action("OpenIdProviderController@endpoint"); } } \ No newline at end of file diff --git a/app/strategies/PostResponseStrategy.php b/app/strategies/PostResponseStrategy.php new file mode 100644 index 00000000..80659899 --- /dev/null +++ b/app/strategies/PostResponseStrategy.php @@ -0,0 +1,35 @@ +getContent(), $response->getHttpCode()); + $http_response->header('Content-Type', $response->getContentType()); + $http_response->header('Cache-Control','no-cache, no-store, max-age=0, must-revalidate'); + $http_response->header('Pragma','no-cache'); + return $http_response; + } +} \ No newline at end of file diff --git a/app/strategies/StrategyProvider.php b/app/strategies/StrategyProvider.php index 95d2529e..2dcdc145 100644 --- a/app/strategies/StrategyProvider.php +++ b/app/strategies/StrategyProvider.php @@ -2,15 +2,22 @@ namespace strategies; +use App; use Illuminate\Support\ServiceProvider; use oauth2\responses\OAuth2DirectResponse; use oauth2\responses\OAuth2IndirectResponse; +use oauth2\responses\OAuth2PostResponse; use openid\responses\OpenIdDirectResponse; use openid\responses\OpenIdIndirectResponse; use oauth2\responses\OAuth2IndirectFragmentResponse; -use App; +use openid\services\OpenIdServiceCatalog; +use oauth2\services\OAuth2ServiceCatalog; -class StrategyProvider extends ServiceProvider +/** + * Class StrategyProvider + * @package strategies + */ +final class StrategyProvider extends ServiceProvider { public function boot() @@ -20,13 +27,16 @@ class StrategyProvider extends ServiceProvider public function register() { //direct response strategy + App::singleton(OAuth2PostResponse::OAuth2PostResponse, 'strategies\\PostResponseStrategy'); App::singleton(OAuth2DirectResponse::OAuth2DirectResponse, 'strategies\\DirectResponseStrategy'); App::singleton(OpenIdDirectResponse::OpenIdDirectResponse, 'strategies\\DirectResponseStrategy'); //indirect response strategy App::singleton(OpenIdIndirectResponse::OpenIdIndirectResponse, 'strategies\\IndirectResponseQueryStringStrategy'); App::singleton(OAuth2IndirectResponse::OAuth2IndirectResponse, 'strategies\\IndirectResponseQueryStringStrategy'); App::singleton(OAuth2IndirectFragmentResponse::OAuth2IndirectFragmentResponse,'strategies\\IndirectResponseUrlFragmentStrategy'); - App::singleton('oauth2\\strategies\\IOAuth2AuthenticationStrategy', 'strategies\\OAuth2AuthenticationStrategy'); + // authentication strategies + App::singleton(OAuth2ServiceCatalog::AuthenticationStrategy, 'strategies\\OAuth2AuthenticationStrategy'); + App::singleton(OpenIdServiceCatalog::AuthenticationStrategy, 'strategies\\OpenIdAuthenticationStrategy'); } public function provides() diff --git a/app/tests/ClientApiTest.php b/app/tests/ClientApiTest.php new file mode 100644 index 00000000..0e1a2499 --- /dev/null +++ b/app/tests/ClientApiTest.php @@ -0,0 +1,76 @@ +current_realm = Config::get('app.url'); + $parts = parse_url($this->current_realm); + $this->current_host = $parts['host']; + } + + public function testGetById(){ + + $client = Client::where('app_name','=','oauth2_test_app')->first(); + + $response = $this->action("GET", "ClientApiController@get", + $parameters = array('id' => $client->id), + array(), + array(), + array()); + + $content = $response->getContent(); + $response_client = json_decode($content); + + $this->assertResponseStatus(200); + $this->assertTrue($response_client->id === $client->id); + } + + + public function testCreate(){ + + $user = User::where('identifier','=','sebastian.marcet')->first(); + + $data = array( + 'user_id' => $user->id, + 'app_name' => 'test_app', + 'app_description' => 'test app', + 'website' => 'http://www.test.com', + 'application_type' => IClient::ApplicationType_Native + ); + + $response = $this->action("POST", "ClientApiController@create", + $data, + array(), + array(), + array()); + + $content = $response->getContent(); + $json_response = json_decode($content); + + $this->assertResponseStatus(201); + $this->assertTrue(isset($json_response->client_id) && !empty($json_response->client_id)); + } + +} \ No newline at end of file diff --git a/app/tests/ClientPublicKeyApiTest.php b/app/tests/ClientPublicKeyApiTest.php new file mode 100644 index 00000000..db032369 --- /dev/null +++ b/app/tests/ClientPublicKeyApiTest.php @@ -0,0 +1,63 @@ +current_realm = Config::get('app.url'); + $parts = parse_url($this->current_realm); + $this->current_host = $parts['host']; + } + + public function testCreate(){ + + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + $client = Client::where('client_id','=', $client_id)->first(); + + $data = array( + 'kid' => 'test key', + 'pem_content' => TestKeys::$public_key2_pem, + 'usage' => JSONWebKeyPublicKeyUseValues::Signature, + 'type' => JSONWebKeyTypes::RSA + ); + + $response = $this->action("POST", "ClientPublicKeyApiController@create", + $wildcards = array('id' => $client->id), + $data, + array(), + array()); + + $content = $response->getContent(); + $json_response = json_decode($content); + + $this->assertResponseStatus(201); + $this->assertTrue(isset($json_response->id) && !empty($json_response->id)); + + $public_key = $client->getPublicKeyByIdentifier('test key'); + + $this->assertTrue(!is_null($public_key) && $json_response->id === $public_key->getId()); + } +} \ No newline at end of file diff --git a/app/tests/OAuth2ProtectedApiTest.php b/app/tests/OAuth2ProtectedApiTest.php index ed046561..19251ae6 100644 --- a/app/tests/OAuth2ProtectedApiTest.php +++ b/app/tests/OAuth2ProtectedApiTest.php @@ -43,7 +43,7 @@ abstract class OAuth2ProtectedApiTest extends OpenStackIDBaseTest { $scope = $this->getScopes(); $this->client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; - $this->client_secret = 'ITc/6Y5N7kOtGKhg'; + $this->client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg'; $params = array( 'client_id' => $this->client_id, diff --git a/app/tests/OAuth2ProtocolTest.php b/app/tests/OAuth2ProtocolTest.php index cea81aaf..b4b955e2 100644 --- a/app/tests/OAuth2ProtocolTest.php +++ b/app/tests/OAuth2ProtocolTest.php @@ -33,7 +33,6 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest { parent::prepareForTests(); App::singleton(UtilsServiceCatalog::ServerConfigurationService, 'StubServerConfigurationService'); - //Route::enableFilters(); $this->current_realm = Config::get('app.url'); $user = User::where('identifier', '=', 'sebastian.marcet')->first(); $this->be($user); @@ -46,6 +45,8 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest public function testAuthCode() { + Route::enableFilters(); + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; $params = array( @@ -55,8 +56,56 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest 'scope' => sprintf('%s/resource-server/read', $this->current_realm), ); + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); - Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce); + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $consent_response = $this->call('POST', $url, array( + 'trust' => 'AllowOnce', + '_token' => Session::token() + )); + + $this->assertResponseStatus(302); + + $auth_response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $auth_response->getTargetUrl(); + + $comps = @parse_url($url); + $query = $comps['query']; + $output = array(); + parse_str($query, $output); + + $this->assertTrue(array_key_exists('code', $output)); + $this->assertTrue(!empty($output['code'])); + + } + + public function testAuthCodeInvalidRedirectUri() + { + + Route::enableFilters(); + + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + + $params = array( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/invalid_uri', + 'response_type' => 'code', + 'scope' => sprintf('%s/resource-server/read', $this->current_realm), + ); $response = $this->action("POST", "OAuth2ProviderController@authorize", $params, @@ -64,10 +113,11 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest array(), array()); - $url = $response->getTargetUrl(); - $content = $response->getContent(); + $this->assertResponseStatus(400); - $this->assertResponseStatus(302); + $body = $response->getContent(); + + $this->assertTrue(str_contains($body, 'redirect_uri_mismatch')); } /** Get Token Test @@ -77,9 +127,10 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest { $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; - $client_secret = 'ITc/6Y5N7kOtGKhg'; + $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg'; - $params = array( + $params = array + ( 'client_id' => $client_id, 'redirect_uri' => 'https://www.test.com/oauth2', 'response_type' => OAuth2Protocol::OAuth2Protocol_ResponseType_Code, @@ -87,7 +138,6 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest OAuth2Protocol::OAuth2Protocol_AccessType => OAuth2Protocol::OAuth2Protocol_AccessType_Offline, ); - Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce); $response = $this->action("POST", "OAuth2ProviderController@authorize", @@ -122,7 +172,7 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest $status = $response->getStatusCode(); $this->assertResponseStatus(200); - + $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type')); $content = $response->getContent(); $response = json_decode($content); @@ -132,6 +182,78 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest $this->assertTrue(!empty($access_token)); $this->assertTrue(!empty($refresh_token)); + } + + /** Get Token Test + * @throws Exception + */ + public function testAuthCodeReplayAttack() + { + + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => OAuth2Protocol::OAuth2Protocol_ResponseType_Code, + 'scope' => sprintf('%s/resource-server/read', $this->current_realm), + OAuth2Protocol::OAuth2Protocol_AccessType => OAuth2Protocol::OAuth2Protocol_AccessType_Offline, + ); + + Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $status = $response->getStatusCode(); + $url = $response->getTargetUrl(); + $content = $response->getContent(); + + $comps = @parse_url($url); + $query = $comps['query']; + $output = array(); + parse_str($query, $output); + + $params = array( + 'code' => $output['code'], + 'redirect_uri' => 'https://www.test.com/oauth2', + 'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode, + ); + + + $response = $this->action("POST", "OAuth2ProviderController@token", + $params, + array(), + array(), + // Symfony interally prefixes headers with "HTTP", so + array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret))); + + $status = $response->getStatusCode(); + + $this->assertResponseStatus(200); + $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type')); + $content = $response->getContent(); + + $response = json_decode($content); + $access_token = $response->access_token; + $refresh_token = $response->refresh_token; + + $this->assertTrue(!empty($access_token)); + $this->assertTrue(!empty($refresh_token)); + + $response = $this->action("POST", "OAuth2ProviderController@token", + $params, + array(), + array(), + // Symfony interally prefixes headers with "HTTP", so + array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret))); + + $this->assertResponseStatus(400); } @@ -144,7 +266,7 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest try { $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; - $client_secret = 'ITc/6Y5N7kOtGKhg'; + $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg'; Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce); @@ -191,7 +313,7 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret))); $this->assertResponseStatus(200); - + $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type')); $content = $response->getContent(); $response = json_decode($content); @@ -215,7 +337,7 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret))); $this->assertResponseStatus(200); - + $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type')); $content = $response->getContent(); $response = json_decode($content); @@ -228,7 +350,6 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest } } - /** test validate token grant * @throws Exception */ @@ -240,7 +361,7 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest $_ENV['access.token.lifetime'] = 1; $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; - $client_secret = 'ITc/6Y5N7kOtGKhg'; + $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg'; Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce); @@ -342,7 +463,7 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest try { $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; - $client_secret = 'ITc/6Y5N7kOtGKhg'; + $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg'; Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce); @@ -390,7 +511,7 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret))); $this->assertResponseStatus(200); - + $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type')); $content = $response->getContent(); $response = json_decode($content); @@ -415,7 +536,7 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret))); $this->assertResponseStatus(200); - + $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type')); $content = $response->getContent(); $response = json_decode($content); @@ -441,7 +562,7 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest try { $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; - $client_secret = 'ITc/6Y5N7kOtGKhg'; + $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg'; Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce); @@ -513,7 +634,7 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret))); $this->assertResponseStatus(200); - + $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type')); $content = $response->getContent(); $response = json_decode($content); @@ -800,7 +921,7 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest try { $client_id = '11z87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; - $client_secret = '11c/6Y5N7kOtGKhg'; + $client_secret = '11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhg'; //do get auth token... $params = array( diff --git a/app/tests/OAuth2UserServiceApiTest.php b/app/tests/OAuth2UserServiceApiTest.php index 1c4b6ddd..72ff21e2 100644 --- a/app/tests/OAuth2UserServiceApiTest.php +++ b/app/tests/OAuth2UserServiceApiTest.php @@ -12,6 +12,7 @@ class OAuth2UserServiceApiTest extends OAuth2ProtectedApiTest { * @covers OAuth2UserApiController::get() */ public function testGetInfo(){ + $response = $this->action("GET", "OAuth2UserApiController@me", array(), array(), diff --git a/app/tests/OIDCProtocolTest.php b/app/tests/OIDCProtocolTest.php new file mode 100644 index 00000000..42c22d22 --- /dev/null +++ b/app/tests/OIDCProtocolTest.php @@ -0,0 +1,2520 @@ +current_realm = Config::get('app.url'); + Route::enableFilters(); + Session::start(); + } + + public function testNonePrompt() + { + + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + + $params = array( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => 'code', + 'scope' => 'openid profile email', + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_None + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $comps = @parse_url($url); + $query = $comps['query']; + $output = array(); + parse_str($query, $output); + + $this->assertTrue(array_key_exists('error', $output)); + $this->assertTrue(!empty($output['error'])); + $this->assertTrue($output['error'] === OAuth2Protocol::OAuth2Protocol_Error_Interaction_Required); + + } + + public function testConsentPrompt() + { + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => 'code', + 'scope' => 'openid profile email', + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_MaxAge => 3200, + OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $response = $this->call('GET', $url); + + $this->assertResponseStatus(200); + + // verify that login hint (email) is populated + $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net')); + + // do login + $response = $this->action('POST', "UserController@postLogin", + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + //do consent + $url = $response->getTargetUrl(); + + $response = $this->action('POST', "UserController@postConsent", array( + 'trust' => IAuthService::AuthorizationResponse_DenyOnce, + '_token' => Session::token() + )); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + $url = $response->getTargetUrl(); + + $comps = @parse_url($url); + $query = $comps['query']; + $output = array(); + parse_str($query, $output); + + $this->assertTrue(array_key_exists('error', $output)); + $this->assertTrue(!empty($output['error'])); + $this->assertTrue($output['error'] === OAuth2Protocol::OAuth2Protocol_Error_Consent_Required); + + } + + public function testConsentLogin() + { + //already logged user + $user = User::where('identifier', '=', 'sebastian.marcet')->first(); + $this->be($user); + + //already given consent + + Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce); + + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => 'code', + 'scope' => 'openid profile email', + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_MaxAge => 3200, + OAuth2Protocol::OAuth2Protocol_Prompt => sprintf('%s %s', OAuth2Protocol::OAuth2Protocol_Prompt_Consent, + OAuth2Protocol::OAuth2Protocol_Prompt_Login) + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $response = $this->call('GET', $url); + + $this->assertResponseStatus(200); + + // verify that login hint (email) is populated + $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net')); + + // do login + $response = $this->action('POST', "UserController@postLogin", + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + //do consent + $url = $response->getTargetUrl(); + + $response = $this->action('POST', "UserController@postConsent", array( + 'trust' => IAuthService::AuthorizationResponse_DenyOnce, + '_token' => Session::token() + )); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + $url = $response->getTargetUrl(); + + $comps = @parse_url($url); + $query = $comps['query']; + $output = array(); + parse_str($query, $output); + + $this->assertTrue(array_key_exists('error', $output)); + $this->assertTrue(!empty($output['error'])); + $this->assertTrue($output['error'] === OAuth2Protocol::OAuth2Protocol_Error_Consent_Required); + + } + + public function testAuthCode() + { + + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => 'code', + 'scope' => 'openid profile email', + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_MaxAge => 3200 + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $response = $this->call('GET', $url); + + $this->assertResponseStatus(200); + + // verify that login hint (email) is populated + $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net')); + + // do login + $response = $this->action('POST', "UserController@postLogin", + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + //do consent + $url = $response->getTargetUrl(); + + $response = $this->action('POST', "UserController@postConsent", array( + 'trust' => 'AllowOnce', + '_token' => Session::token() + )); + + $this->assertResponseStatus(302); + + // get auth code + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $comps = @parse_url($url); + $query = $comps['query']; + $output = array(); + parse_str($query, $output); + + $this->assertTrue(array_key_exists('code', $output)); + $this->assertTrue(!empty($output['code'])); + + } + + public function testAuthCodeInvalidLoginHint() + { + + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => 'code', + 'scope' => 'openid profile email', + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@sebastian.net', + OAuth2Protocol::OAuth2Protocol_MaxAge => 3200 + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + + $url = $response->getTargetUrl(); + + $this->assertTrue(str_contains($url, '/login')); + + } + + public function testAuthCodeOpenIdScopeOnly() + { + + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg'; + + $params = array + ( + 'scope' => 'openid', + 'state' => 'KtWzJk5Vmk8CZwC0', + 'redirect_uri' => 'https://op.certification.openid.net:60393/authz_cb', + 'response_type' => 'code', + 'client_id' => $client_id, + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $response = $this->call('GET', $url); + + $this->assertResponseStatus(200); + + // do login + $response = $this->action('POST', "UserController@postLogin", + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + //do consent + $url = $response->getTargetUrl(); + + $response = $this->action('POST', "UserController@postConsent", array( + 'trust' => 'AllowOnce', + '_token' => Session::token() + )); + + $this->assertResponseStatus(302); + + // get auth code + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $comps = @parse_url($url); + $query = $comps['query']; + $output = array(); + parse_str($query, $output); + + $this->assertTrue(array_key_exists('code', $output)); + $this->assertTrue(!empty($output['code'])); + + } + + public function testMaxAge1AndWait2() + { + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => 'code', + 'scope' => 'openid profile email', + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_MaxAge => 1 + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $response = $this->call('GET', $url); + + $this->assertResponseStatus(200); + + // verify that login hint (email) is populated + $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net')); + + // do login + $response = $this->action('POST', "UserController@postLogin", + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + $this->assertResponseStatus(302); + + sleep(2); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $this->assertTrue($response->getTargetUrl() === URL::action("UserController@postLogin")); + } + + public function testToken + ( + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client', + $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg', + $use_enc = true + ) { + + + $params = array( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => 'code', + 'scope' => sprintf('%s profile email address %s', OAuth2Protocol::OpenIdConnect_Scope, + OAuth2Protocol::OfflineAccess_Scope), + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_Nonce => 'test_nonce', + OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent, + OAuth2Protocol::OAuth2Protocol_MaxAge => 3200 + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $response = $this->call('GET', $url); + + $this->assertResponseStatus(200); + + // verify that login hint (email) is populated + $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net')); + + // do login + $response = $this->call('POST', $url, + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $response = $this->action('GET', 'UserController@getConsent'); + + $this->assertResponseStatus(200); + + $response = $this->action('POST', 'UserController@getConsent', array( + 'trust' => 'AllowOnce', + '_token' => Session::token() + )); + + $this->assertResponseStatus(302); + + // get auth code + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $comps = @parse_url($url); + $query = $comps['query']; + $output = array(); + parse_str($query, $output); + + $this->assertTrue(array_key_exists('code', $output)); + $this->assertTrue(!empty($output['code'])); + + $params = array( + 'code' => $output['code'], + 'redirect_uri' => 'https://www.test.com/oauth2', + 'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode, + ); + + + $response = $this->action("POST", "OAuth2ProviderController@token", + $params, + array(), + array(), + // Symfony interally prefixes headers with "HTTP", so + array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret))); + + + $this->assertResponseStatus(200); + + $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type')); + + $content = $response->getContent(); + + $response = json_decode($content); + $access_token = $response->access_token; + $refresh_token = $response->refresh_token; + $id_token = $response->id_token; + + $this->assertTrue(!empty($access_token)); + $this->assertTrue(!empty($refresh_token)); + $this->assertTrue(!empty($id_token)); + + $jwt = BasicJWTFactory::build($id_token); + + if ($use_enc) { + $this->assertTrue($jwt instanceof IJWE); + + $recipient_key = RSAJWKFactory::build + ( + new RSAJWKPEMPrivateKeySpecification + ( + TestSeeder::$client_private_key_1, + RSAJWKPEMPrivateKeySpecification::WithoutPassword, + $jwt->getJOSEHeader()->getAlgorithm()->getString() + ) + ); + + $recipient_key->setKeyUse(JSONWebKeyPublicKeyUseValues::Encryption)->setId('recipient_public_key'); + + + $jwt->setRecipientKey($recipient_key); + + $payload = $jwt->getPlainText(); + + $jwt = BasicJWTFactory::build($payload); + + $this->assertTrue($jwt instanceof IJWS); + } + + return $access_token; + } + + public function testTokenResponseModePost() + { + + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => 'code', + 'scope' => sprintf('%s profile email %s', OAuth2Protocol::OpenIdConnect_Scope, + OAuth2Protocol::OfflineAccess_Scope), + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent, + OAuth2Protocol::OAuth2Protocol_MaxAge => 1, + OAuth2Protocol::OAuth2Protocol_ResponseMode => OAuth2Protocol::OAuth2Protocol_ResponseMode_FormPost + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $response = $this->call('GET', $url); + + $this->assertResponseStatus(200); + + // verify that login hint (email) is populated + $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net')); + + // do login + $response = $this->call('POST', $url, + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $response = $this->action('GET', 'UserController@getConsent'); + + $this->assertResponseStatus(200); + + $response = $this->action('POST', 'UserController@getConsent', array( + 'trust' => 'AllowOnce', + '_token' => Session::token() + )); + + $this->assertResponseStatus(302); + + // get auth code + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(200); + $content = $response->getContent(); + $this->assertEquals('application/x-www-form-urlencoded', $response->headers->get('Content-Type')); + + $output = array(); + parse_str($content, $output); + + $this->assertTrue(array_key_exists('code', $output)); + $this->assertTrue(!empty($output['code'])); + + $params = array + ( + 'code' => $output['code'], + 'redirect_uri' => 'https://www.test.com/oauth2', + 'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode, + ); + + + $response = $this->action("POST", "OAuth2ProviderController@token", + $params, + array(), + array(), + // Symfony interally prefixes headers with "HTTP", so + array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret))); + + $this->assertResponseStatus(200); + + $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type')); + + $content = $response->getContent(); + + $response = json_decode($content); + $access_token = $response->access_token; + $refresh_token = $response->refresh_token; + $id_token = $response->id_token; + + $this->assertTrue(!empty($access_token)); + $this->assertTrue(!empty($refresh_token)); + $this->assertTrue(!empty($id_token)); + + $jwt = BasicJWTFactory::build($id_token); + + $this->assertTrue($jwt instanceof IJWE); + + $recipient_key = RSAJWKFactory::build + ( + new RSAJWKPEMPrivateKeySpecification + ( + TestSeeder::$client_private_key_1, + RSAJWKPEMPrivateKeySpecification::WithoutPassword, + $jwt->getJOSEHeader()->getAlgorithm()->getString() + ) + ); + + $recipient_key->setKeyUse(JSONWebKeyPublicKeyUseValues::Encryption)->setId('recipient_public_key'); + + + $jwt->setRecipientKey($recipient_key); + + $payload = $jwt->getPlainText(); + + $jwt = BasicJWTFactory::build($payload); + + $this->assertTrue($jwt instanceof IJWS); + + $payload = $jwt->getPayload(); + + $claims_set = $payload->getClaimSet(); + } + + public function testNativeClientBasicAuth() + { + + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.android.openstack.client'; + $client_secret = '11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhgfdfdfdf'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'androipapp://oidc_endpoint_callback', + 'response_type' => 'code', + 'scope' => sprintf('%s profile email %s', OAuth2Protocol::OpenIdConnect_Scope, + OAuth2Protocol::OfflineAccess_Scope), + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent, + OAuth2Protocol::OAuth2Protocol_MaxAge => 10000, + OAuth2Protocol::OAuth2Protocol_ResponseMode => OAuth2Protocol::OAuth2Protocol_ResponseMode_FormPost + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $response = $this->call('GET', $url); + + $this->assertResponseStatus(200); + + // verify that login hint (email) is populated + $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net')); + + // do login + $response = $this->call('POST', $url, + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $response = $this->action('GET', 'UserController@getConsent'); + + $this->assertResponseStatus(200); + + $response = $this->action('POST', 'UserController@getConsent', array( + 'trust' => 'AllowOnce', + '_token' => Session::token() + )); + + $this->assertResponseStatus(302); + + // get auth code + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(200); + $content = $response->getContent(); + $this->assertEquals('application/x-www-form-urlencoded', $response->headers->get('Content-Type')); + + $output = array(); + parse_str($content, $output); + + $this->assertTrue(array_key_exists('code', $output)); + $this->assertTrue(!empty($output['code'])); + + $params = array + ( + 'code' => $output['code'], + 'redirect_uri' => 'androipapp://oidc_endpoint_callback', + 'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode, + ); + + + $response = $this->action("POST", "OAuth2ProviderController@token", + $params, + array(), + array(), + // Symfony interally prefixes headers with "HTTP", so + array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret))); + + $this->assertResponseStatus(200); + + $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type')); + + $content = $response->getContent(); + + $response = json_decode($content); + $access_token = $response->access_token; + $refresh_token = $response->refresh_token; + $id_token = $response->id_token; + + $this->assertTrue(!empty($access_token)); + $this->assertTrue(!empty($refresh_token)); + $this->assertTrue(!empty($id_token)); + + $jwt = BasicJWTFactory::build($id_token); + + $this->assertTrue($jwt instanceof UnsecuredJWT); + + $claims_set = $jwt->getClaimSet(); + + $this->assertTrue(!is_null($claims_set)); + + $params = array( + 'client_id' => $client_id, + 'redirect_uri' => 'androipapp://oidc_endpoint_callback', + 'response_type' => 'code', + 'scope' => 'openid profile email', + OAuth2Protocol::OAuth2Protocol_IDTokenHint => $jwt->toCompactSerialization(), + OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_None + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + } + + public function testClientAuthenticationClientSecretJwt() + { + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x2.openstack.client'; + $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => 'code', + 'scope' => sprintf('%s profile email %s', OAuth2Protocol::OpenIdConnect_Scope, + OAuth2Protocol::OfflineAccess_Scope), + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent, + OAuth2Protocol::OAuth2Protocol_MaxAge => 3200 + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $response = $this->call('GET', $url); + + $this->assertResponseStatus(200); + + // verify that login hint (email) is populated + $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net')); + + // do login + $response = $this->call('POST', $url, + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $response = $this->action('GET', 'UserController@getConsent'); + + $this->assertResponseStatus(200); + + $response = $this->action('POST', 'UserController@getConsent', array( + 'trust' => 'AllowOnce', + '_token' => Session::token() + )); + + $this->assertResponseStatus(302); + + // get auth code + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $comps = @parse_url($url); + $query = $comps['query']; + $output = array(); + parse_str($query, $output); + + $this->assertTrue(array_key_exists('code', $output)); + $this->assertTrue(!empty($output['code'])); + + $now = time(); + + $claim_set = JWTClaimSetFactory::build + ( + array + ( + RegisteredJWTClaimNames::Issuer => $client_id, + RegisteredJWTClaimNames::Subject => $client_id, + RegisteredJWTClaimNames::Audience => URL::action("OAuth2ProviderController@token"), + RegisteredJWTClaimNames::JWTID => '123456789', + RegisteredJWTClaimNames::ExpirationTime => $now + 3600, + RegisteredJWTClaimNames::IssuedAt => $now + ) + ); + + $key = OctetSequenceJWKFactory::build + ( + new OctetSequenceJWKSpecification + ( + $client_secret, + JSONWebSignatureAndEncryptionAlgorithms::HS512 + ) + ); + + $alg = new StringOrURI(JSONWebSignatureAndEncryptionAlgorithms::HS512); + + $jws = JWSFactory::build + ( + new JWS_ParamsSpecification + ( + $key, + $alg, + $claim_set + ) + ); + + $params = array + ( + 'code' => $output['code'], + 'redirect_uri' => 'https://www.test.com/oauth2', + 'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode, + OAuth2Protocol::OAuth2Protocol_ClientAssertionType => ClientAssertionAuthenticationContext::RegisteredAssertionType, + OAuth2Protocol::OAuth2Protocol_ClientAssertion => $jws->toCompactSerialization() + ); + + + $response = $this->action + ( + "POST", + "OAuth2ProviderController@token", + $params, + array(), + array(), + array() + ); + + $this->assertResponseStatus(200); + $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type')); + $content = $response->getContent(); + + $response = json_decode($content); + $access_token = $response->access_token; + $refresh_token = $response->refresh_token; + $id_token = $response->id_token; + + $this->assertTrue(!empty($access_token)); + $this->assertTrue(!empty($refresh_token)); + $this->assertTrue(!empty($id_token)); + + $jwt = BasicJWTFactory::build($id_token); + + $this->assertTrue($jwt instanceof IJWE); + + $recipient_key = RSAJWKFactory::build + ( + new RSAJWKPEMPrivateKeySpecification + ( + TestSeeder::$client_private_key_1, + RSAJWKPEMPrivateKeySpecification::WithoutPassword, + $jwt->getJOSEHeader()->getAlgorithm()->getString() + ) + ); + + $recipient_key->setKeyUse(JSONWebKeyPublicKeyUseValues::Encryption)->setId('recipient_public_key'); + + $jwt->setRecipientKey($recipient_key); + + $payload = $jwt->getPlainText(); + + $jwt = BasicJWTFactory::build($payload); + + $this->assertTrue($jwt instanceof IJWS); + } + + public function testClientAuthenticationPrivateKeyJwt() + { + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.android2.openstack.client'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'androipapp://oidc_endpoint_callback2', + 'response_type' => 'code', + 'scope' => sprintf('%s profile email %s', OAuth2Protocol::OpenIdConnect_Scope, + OAuth2Protocol::OfflineAccess_Scope), + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent, + OAuth2Protocol::OAuth2Protocol_MaxAge => 3200 + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $response = $this->call('GET', $url); + + $this->assertResponseStatus(200); + + // verify that login hint (email) is populated + $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net')); + + // do login + $response = $this->call('POST', $url, + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $response = $this->action('GET', 'UserController@getConsent'); + + $this->assertResponseStatus(200); + + $response = $this->action('POST', 'UserController@getConsent', array( + 'trust' => 'AllowOnce', + '_token' => Session::token() + )); + + $this->assertResponseStatus(302); + + // get auth code + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $comps = @parse_url($url); + $query = $comps['query']; + $output = array(); + parse_str($query, $output); + + $this->assertTrue(array_key_exists('code', $output)); + $this->assertTrue(!empty($output['code'])); + + $now = time(); + + $claim_set = JWTClaimSetFactory::build + ( + array + ( + RegisteredJWTClaimNames::Issuer => $client_id, + RegisteredJWTClaimNames::Subject => $client_id, + RegisteredJWTClaimNames::Audience => URL::action("OAuth2ProviderController@token"), + RegisteredJWTClaimNames::JWTID => '123456789', + RegisteredJWTClaimNames::ExpirationTime => $now + 3600, + RegisteredJWTClaimNames::IssuedAt => $now + ) + ); + + $key = RSAJWKFactory::build + ( + new RSAJWKPEMPrivateKeySpecification + ( + TestSeeder::$client_private_key_2, + RSAJWKPEMPrivateKeySpecification::WithoutPassword, + JSONWebSignatureAndEncryptionAlgorithms::RS512 + ) + ); + + $key->setId('public_key_44'); + + $alg = new StringOrURI(JSONWebSignatureAndEncryptionAlgorithms::RS512); + + $jws = JWSFactory::build + ( + new JWS_ParamsSpecification + ( + $key, + $alg, + $claim_set + ) + ); + + $params = array + ( + 'code' => $output['code'], + 'redirect_uri' => 'androipapp://oidc_endpoint_callback2', + 'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode, + OAuth2Protocol::OAuth2Protocol_ClientAssertionType => ClientAssertionAuthenticationContext::RegisteredAssertionType, + OAuth2Protocol::OAuth2Protocol_ClientAssertion => $jws->toCompactSerialization() + ); + + + $response = $this->action + ( + "POST", + "OAuth2ProviderController@token", + $params, + array(), + array(), + array() + ); + + $this->assertResponseStatus(200); + $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type')); + $content = $response->getContent(); + + $response = json_decode($content); + $access_token = $response->access_token; + $id_token = $response->id_token; + + $this->assertTrue(!empty($access_token)); + $this->assertTrue(!empty($id_token)); + } + + public function testImplicitFlowTokenIdToken() + { + // use a public client + + //already given consent + + Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce); + + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.openstack.client'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => OAuth2Protocol::OAuth2Protocol_ResponseType_IdToken . OAuth2Protocol::OAuth2Protocol_ResponseType_Delimiter . OAuth2Protocol::OAuth2Protocol_ResponseType_Token, + 'scope' => sprintf('%s profile email %s', OAuth2Protocol::OpenIdConnect_Scope, + OAuth2Protocol::OfflineAccess_Scope), + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent, + OAuth2Protocol::OAuth2Protocol_MaxAge => 3200, + OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ', + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + // do login + $response = $this->call('POST', $url, + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + + $response = $this->action('POST', 'UserController@getConsent', array( + 'trust' => 'AllowOnce', + '_token' => Session::token() + )); + + $this->assertResponseStatus(302); + + // get response + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + $comps = @parse_url($url); + $fragment = $comps['fragment']; + + $this->assertTrue(!empty($fragment)); + $output = array(); + parse_str($fragment, $output); + + $this->assertTrue(array_key_exists('access_token', $output)); + $this->assertTrue(!empty($output['access_token'])); + $this->assertTrue(array_key_exists('id_token', $output)); + $this->assertTrue(!empty($output['id_token'])); + + } + + public function testImplicitFlowIdToken() + { + // use a public client + + //already given consent + + Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce); + + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.openstack.client'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => OAuth2Protocol::OAuth2Protocol_ResponseType_IdToken, + 'scope' => sprintf('%s profile email %s', OAuth2Protocol::OpenIdConnect_Scope, + OAuth2Protocol::OfflineAccess_Scope), + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent, + OAuth2Protocol::OAuth2Protocol_MaxAge => 3200, + OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ', + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + // do login + $response = $this->call('POST', $url, + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + + $response = $this->action('POST', 'UserController@getConsent', array( + 'trust' => 'AllowOnce', + '_token' => Session::token() + )); + + $this->assertResponseStatus(302); + + // get response + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + $comps = @parse_url($url); + $fragment = $comps['fragment']; + + $this->assertTrue(!empty($fragment)); + $output = array(); + parse_str($fragment, $output); + + $this->assertTrue(!array_key_exists('access_token', $output)); + $this->assertTrue(empty($output['access_token'])); + $this->assertTrue(array_key_exists('id_token', $output)); + $this->assertTrue(!empty($output['id_token'])); + } + + public function testImplicitFlowIdTokenMaxAge1000() + { + // use a public client + + //already given consent + + Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce); + + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.openstack.client'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => OAuth2Protocol::OAuth2Protocol_ResponseType_IdToken, + 'scope' => sprintf('%s profile email %s', OAuth2Protocol::OpenIdConnect_Scope, + OAuth2Protocol::OfflineAccess_Scope), + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent, + OAuth2Protocol::OAuth2Protocol_MaxAge => 1000, + OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ', + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + // do login + $response = $this->call('POST', $url, + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + + $response = $this->action('POST', 'UserController@getConsent', array( + 'trust' => 'AllowOnce', + '_token' => Session::token() + )); + + $this->assertResponseStatus(302); + + // get response + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + $comps = @parse_url($url); + $fragment = $comps['fragment']; + + $this->assertTrue(!empty($fragment)); + $output = array(); + parse_str($fragment, $output); + + $this->assertTrue(!array_key_exists('access_token', $output)); + $this->assertTrue(empty($output['access_token'])); + $this->assertTrue(array_key_exists('id_token', $output)); + $this->assertTrue(!empty($output['id_token'])); + + sleep(10); + + $params[OAuth2Protocol::OAuth2Protocol_Prompt] = OAuth2Protocol::OAuth2Protocol_Prompt_None; + $params['scope'] = sprintf('%s profile email', OAuth2Protocol::OpenIdConnect_Scope); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + $comps = @parse_url($url); + $fragment = $comps['fragment']; + + $this->assertTrue(!empty($fragment)); + $output2 = array(); + parse_str($fragment, $output2); + + $this->assertTrue(!array_key_exists('access_token', $output2)); + $this->assertTrue(empty($output2['access_token'])); + $this->assertTrue(array_key_exists('id_token', $output2)); + $this->assertTrue(!empty($output2['id_token'])); + + } + + public function testImplicitFlowAccessToken() + { + // use a public client + + //already given consent + + Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce); + + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.openstack.client'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => OAuth2Protocol::OAuth2Protocol_ResponseType_Token, + 'scope' => sprintf('%s profile email %s', OAuth2Protocol::OpenIdConnect_Scope, + OAuth2Protocol::OfflineAccess_Scope), + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent, + OAuth2Protocol::OAuth2Protocol_MaxAge => 3200, + OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ', + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + // do login + $response = $this->call('POST', $url, + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + + $response = $this->action('POST', 'UserController@getConsent', array( + 'trust' => 'AllowOnce', + '_token' => Session::token() + )); + + $this->assertResponseStatus(302); + + // get response + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + $comps = @parse_url($url); + $fragment = $comps['fragment']; + + $this->assertTrue(!empty($fragment)); + $output = array(); + parse_str($fragment, $output); + + $this->assertTrue(array_key_exists('access_token', $output)); + $this->assertTrue(!empty($output['access_token'])); + $this->assertTrue(!array_key_exists('id_token', $output)); + $this->assertTrue(empty($output['id_token'])); + + } + + public function testImplicitFlowResponseModePost() + { + // use a public client + + //already given consent + + Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce); + + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.openstack.client'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => OAuth2Protocol::OAuth2Protocol_ResponseType_IdToken . OAuth2Protocol::OAuth2Protocol_ResponseType_Delimiter . OAuth2Protocol::OAuth2Protocol_ResponseType_Token, + 'scope' => sprintf('%s profile email %s', OAuth2Protocol::OpenIdConnect_Scope, + OAuth2Protocol::OfflineAccess_Scope), + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent, + OAuth2Protocol::OAuth2Protocol_MaxAge => 3200, + OAuth2Protocol::OAuth2Protocol_ResponseMode => OAuth2Protocol::OAuth2Protocol_ResponseMode_FormPost, + OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ', + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + // do login + $response = $this->call('POST', $url, + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + + $response = $this->action('POST', 'UserController@getConsent', array( + 'trust' => 'AllowOnce', + '_token' => Session::token() + )); + + $this->assertResponseStatus(302); + + // get response + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(200); + + $this->assertEquals('application/x-www-form-urlencoded', $response->headers->get('Content-Type')); + $content = $response->getContent(); + $this->assertTrue(!empty($content)); + $output = array(); + parse_str($content, $output); + + $this->assertTrue(array_key_exists('access_token', $output)); + $this->assertTrue(!empty($output['access_token'])); + $this->assertTrue(array_key_exists('id_token', $output)); + $this->assertTrue(!empty($output['id_token'])); + + } + + public function testUserInfoEndpointGETAndBearerHeader() + { + $access_token = $this->testToken(); + $response = $this->action("GET", "OAuth2UserApiController@userInfo", + array(), + array(), + array(), + array("HTTP_Authorization" => " Bearer " . $access_token)); + + $this->assertResponseStatus(200); + $content = $response->getContent(); + $this->assertTrue(!empty($content)); + $user_info = json_decode($content, true); + + $this->assertTrue(isset($user_info['sub'])); + } + + public function testUserInfoEndpointPOSTAndBearerHeader() + { + $access_token = $this->testToken(); + $response = $this->action("POST", "OAuth2UserApiController@userInfo", + array(), + array(), + array(), + array("HTTP_Authorization" => " Bearer " . $access_token)); + + $this->assertResponseStatus(200); + $content = $response->getContent(); + $this->assertTrue(!empty($content)); + $user_info = json_decode($content, true); + + $this->assertTrue(isset($user_info['sub'])); + } + + public function testUserInfoEndpointPOSTAndBearerBody() + { + $access_token = $this->testToken(); + $response = $this->action("POST", "OAuth2UserApiController@userInfo", + array(), + array + ( + 'access_token' => $access_token + ), + array(), + array()); + + $this->assertResponseStatus(200); + $content = $response->getContent(); + $this->assertTrue(!empty($content)); + $user_info = json_decode($content, true); + + $this->assertTrue(isset($user_info['sub'])); + } + + public function testUserInfoEndpointPOSTAndBearerBodyRS512() + { + $access_token = $this->testToken + ( + 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ33.openstack.client', + 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N585OtGKhg55', + false + ); + + $response = $this->action("POST", "OAuth2UserApiController@userInfo", + array(), + array + ( + 'access_token' => $access_token + ), + array(), + array()); + + $this->assertResponseStatus(200); + $user_info_response = $response->getContent(); + + $this->assertTrue($response->headers->get('content-type') === \utils\http\HttpContentType::JWT); + $this->assertTrue(!empty($user_info_response)); + + $jwt = BasicJWTFactory::build($user_info_response); + $this->assertTrue($jwt instanceof IJWS); + + } + + public function testHybridFlowCodeIdToken() + { + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => 'id_token code', + 'scope' => 'openid profile email', + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_MaxAge => 1000, + OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ', + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $response = $this->call('GET', $url); + + $this->assertResponseStatus(200); + + // verify that login hint (email) is populated + $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net')); + + // do login + $response = $this->action('POST', "UserController@postLogin", + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + //do consent + $url = $response->getTargetUrl(); + + $response = $this->action('POST', "UserController@postConsent", array( + 'trust' => 'AllowOnce', + '_token' => Session::token() + )); + + $this->assertResponseStatus(302); + + // get auth code + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + $comps = @parse_url($url); + $fragment = $comps['fragment']; + + $this->assertTrue(!empty($fragment)); + $output = array(); + parse_str($fragment, $output); + + $this->assertTrue(array_key_exists('code', $output)); + $this->assertTrue(!empty($output['code'])); + $this->assertTrue(!array_key_exists('access_token', $output)); + $this->assertTrue(empty($output['access_token'])); + $this->assertTrue(array_key_exists('id_token', $output)); + $this->assertTrue(!empty($output['id_token'])); + + $params = array + ( + 'code' => $output['code'], + 'redirect_uri' => 'https://www.test.com/oauth2', + 'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode, + ); + + $response = $this->action("POST", "OAuth2ProviderController@token", + $params, + array(), + array(), + // Symfony interally prefixes headers with "HTTP", so + array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret))); + + $this->assertResponseStatus(200); + } + + public function testHybridFlowCodeIdTokenIdTokenHint() + { + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => 'id_token code', + 'scope' => 'openid profile email', + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_MaxAge => 1000, + OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ', + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $response = $this->call('GET', $url); + + $this->assertResponseStatus(200); + + // verify that login hint (email) is populated + $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net')); + + // do login + $response = $this->action('POST', "UserController@postLogin", + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + //do consent + $url = $response->getTargetUrl(); + + $response = $this->action('POST', "UserController@postConsent", array( + 'trust' => 'AllowOnce', + '_token' => Session::token() + )); + + $this->assertResponseStatus(302); + + // get auth code + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + $comps = @parse_url($url); + $fragment = $comps['fragment']; + + $this->assertTrue(!empty($fragment)); + $output = array(); + parse_str($fragment, $output); + + $this->assertTrue(array_key_exists('code', $output)); + $this->assertTrue(!empty($output['code'])); + $this->assertTrue(!array_key_exists('access_token', $output)); + $this->assertTrue(empty($output['access_token'])); + $this->assertTrue(array_key_exists('id_token', $output)); + $this->assertTrue(!empty($output['id_token'])); + + $id_token = $output['id_token']; + + $jwt = BasicJWTFactory::build($id_token); + + $this->assertTrue($jwt instanceof IJWE); + + $recipient_key = RSAJWKFactory::build + ( + new RSAJWKPEMPrivateKeySpecification + ( + TestSeeder::$client_private_key_1, + RSAJWKPEMPrivateKeySpecification::WithoutPassword, + $jwt->getJOSEHeader()->getAlgorithm()->getString() + ) + ); + + $recipient_key->setKeyUse(JSONWebKeyPublicKeyUseValues::Encryption)->setId('recipient_public_key'); + $jwt->setRecipientKey($recipient_key); + $payload = $jwt->getPlainText(); + $jwt = BasicJWTFactory::build($payload); + $this->assertTrue($jwt instanceof IJWS); + + + $jwk = OctetSequenceJWKFactory::build + ( + new OctetSequenceJWKSpecification + ( + $client_secret, + JSONWebSignatureAndEncryptionAlgorithms::HS512 + ) + ); + + $jwk->setId('shared_secret'); + + $jwt->setKey($jwk); + + $verified = $jwt->verify(JSONWebSignatureAndEncryptionAlgorithms::HS512); + + $this->assertTrue($verified); + + // ok send as id token hint again .... + + $id_token_hint = $jwt->toCompactSerialization(); + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => 'id_token code', + 'scope' => 'openid profile email', + OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_None, + OAuth2Protocol::OAuth2Protocol_IDTokenHint => $id_token_hint, + OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ', + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + $comps = @parse_url($url); + $fragment = $comps['fragment']; + + $this->assertTrue(!empty($fragment)); + $output = array(); + parse_str($fragment, $output); + + $this->assertTrue(array_key_exists('code', $output)); + $this->assertTrue(!empty($output['code'])); + $this->assertTrue(!array_key_exists('access_token', $output)); + $this->assertTrue(empty($output['access_token'])); + $this->assertTrue(array_key_exists('id_token', $output)); + $this->assertTrue(!empty($output['id_token'])); + + // try with another key client private key signing (RSA) + $claim_set = $jwt->getPayload()->getClaimSet(); + + $key = RSAJWKFactory::build + ( + new RSAJWKPEMPrivateKeySpecification + ( + TestSeeder::$client_private_key_2, + RSAJWKPEMPrivateKeySpecification::WithoutPassword, + JSONWebSignatureAndEncryptionAlgorithms::RS512 + ) + ); + + $key->setId('public_key_2'); + + $alg = new StringOrURI(JSONWebSignatureAndEncryptionAlgorithms::RS512); + $jws = JWSFactory::build( new JWS_ParamsSpecification($key,$alg, $claim_set) ); + // and sign with server private key + $id_token_hint = $jws->toCompactSerialization(); + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => 'id_token code', + 'scope' => 'openid profile email', + OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_None, + OAuth2Protocol::OAuth2Protocol_IDTokenHint => $id_token_hint, + OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ', + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + $comps = @parse_url($url); + $fragment = $comps['fragment']; + + $this->assertTrue(!empty($fragment)); + $output = array(); + parse_str($fragment, $output); + + $this->assertTrue(array_key_exists('error', $output)); + $this->assertTrue(!empty($output['error'])); + $this->assertTrue($output['error'] == 'server_error'); + + $this->assertTrue(array_key_exists('error_description', $output)); + $this->assertTrue(!empty($output['error_description'])); + $this->assertTrue($output['error_description'] == 'original kid public_key_2 - current kid shared_secret'); + } + + public function testHybridFlowCodeAccessToken() + { + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => 'code token', + 'scope' => 'openid profile email', + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_MaxAge => 1000, + OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ', + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $response = $this->call('GET', $url); + + $this->assertResponseStatus(200); + + // verify that login hint (email) is populated + $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net')); + + // do login + $response = $this->action('POST', "UserController@postLogin", + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + //do consent + $url = $response->getTargetUrl(); + + $response = $this->action('POST', "UserController@postConsent", array( + 'trust' => 'AllowOnce', + '_token' => Session::token() + )); + + $this->assertResponseStatus(302); + + // get auth code + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + $comps = @parse_url($url); + $fragment = $comps['fragment']; + + $this->assertTrue(!empty($fragment)); + $output = array(); + parse_str($fragment, $output); + + $this->assertTrue(array_key_exists('code', $output)); + $this->assertTrue(!empty($output['code'])); + $this->assertTrue(array_key_exists('access_token', $output)); + $this->assertTrue(!empty($output['access_token'])); + $this->assertTrue(!array_key_exists('id_token', $output)); + $this->assertTrue(empty($output['id_token'])); + + $params = array + ( + 'code' => $output['code'], + 'redirect_uri' => 'https://www.test.com/oauth2', + 'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode, + ); + + $response = $this->action("POST", "OAuth2ProviderController@token", + $params, + array(), + array(), + // Symfony interally prefixes headers with "HTTP", so + array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret))); + + $this->assertResponseStatus(200); + + $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type')); + + $content = $response->getContent(); + + $response = json_decode($content); + $access_token = $response->access_token; + $this->assertTrue(!empty($access_token)); + + $this->assertTrue($output['access_token'] !== $access_token); + + } + + public function testHybridFlowCodeAccessTokenIdToken() + { + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => 'code token id_token', + 'scope' => 'openid profile email', + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_MaxAge => 1000, + OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ', + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $response = $this->call('GET', $url); + + $this->assertResponseStatus(200); + + // verify that login hint (email) is populated + $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net')); + + // do login + $response = $this->action('POST', "UserController@postLogin", + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + //do consent + $url = $response->getTargetUrl(); + + $response = $this->action('POST', "UserController@postConsent", array( + 'trust' => 'AllowOnce', + '_token' => Session::token() + )); + + $this->assertResponseStatus(302); + + // get auth code + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + $comps = @parse_url($url); + $fragment = $comps['fragment']; + + $this->assertTrue(!empty($fragment)); + $output = array(); + parse_str($fragment, $output); + + $this->assertTrue(array_key_exists('code', $output)); + $this->assertTrue(!empty($output['code'])); + $this->assertTrue(array_key_exists('access_token', $output)); + $this->assertTrue(!empty($output['access_token'])); + $this->assertTrue(array_key_exists('id_token', $output)); + $this->assertTrue(!empty($output['id_token'])); + + + $params = array + ( + 'code' => $output['code'], + 'redirect_uri' => 'https://www.test.com/oauth2', + 'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode, + ); + + $response = $this->action("POST", "OAuth2ProviderController@token", + $params, + array(), + array(), + // Symfony interally prefixes headers with "HTTP", so + array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret))); + + $this->assertResponseStatus(200); + } + + public function testTryingAuthCodeTwice() + { + $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg'; + + $params = array + ( + 'client_id' => $client_id, + 'redirect_uri' => 'https://www.test.com/oauth2', + 'response_type' => 'code', + 'scope' => 'openid profile email', + OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net', + OAuth2Protocol::OAuth2Protocol_MaxAge => 3200, + OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ', + ); + + $response = $this->action("POST", "OAuth2ProviderController@authorize", + $params, + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $response = $this->call('GET', $url); + + $this->assertResponseStatus(200); + + // verify that login hint (email) is populated + $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net')); + + // do login + $response = $this->action('POST', "UserController@postLogin", + array + ( + 'username' => 'sebastian@tipit.net', + 'password' => '1qaz2wsx', + '_token' => Session::token() + ) + ); + + $this->assertResponseStatus(302); + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + //do consent + $url = $response->getTargetUrl(); + + $response = $this->action('POST', "UserController@postConsent", array( + 'trust' => 'AllowOnce', + '_token' => Session::token() + )); + + $this->assertResponseStatus(302); + + // get auth code + + $response = $this->action("GET", "OAuth2ProviderController@authorize", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $url = $response->getTargetUrl(); + + $comps = @parse_url($url); + $query = $comps['query']; + $output = array(); + parse_str($query, $output); + + $this->assertTrue(array_key_exists('code', $output)); + $this->assertTrue(!empty($output['code'])); + + + $params = array( + 'code' => $output['code'], + 'redirect_uri' => 'https://www.test.com/oauth2', + 'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode, + ); + + // 1st + $response = $this->action("POST", "OAuth2ProviderController@token", + $params, + array(), + array(), + // Symfony interally prefixes headers with "HTTP", so + array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret))); + + + $this->assertResponseStatus(200); + + $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type')); + + $content = $response->getContent(); + + $response = json_decode($content); + + // 2nd + + $response = $this->action("POST", "OAuth2ProviderController@token", + $params, + array(), + array(), + // Symfony interally prefixes headers with "HTTP", so + array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret))); + + + $this->assertResponseStatus(400); + + $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type')); + + $content = $response->getContent(); + + $response = json_decode($content); + + $this->assertTrue($response->error === 'invalid_grant'); + } + + public function testDiscovery() + { + $response = $this->action("GET", "OAuth2ProviderController@discovery"); + + $this->assertResponseStatus(200); + + $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type')); + + $content = $response->getContent(); + + $this->assertTrue(!empty($content)); + + $response = json_decode($content, true); + + $this->assertTrue(isset($response['issuer'])); + } + + public function testJWK() + { + $response = $this->action("GET", "OAuth2ProviderController@certs"); + + $this->assertResponseStatus(200); + + $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type')); + + $content = $response->getContent(); + + $this->assertTrue(!empty($content)); + + $response = json_decode($content, true); + + $this->assertTrue(isset($response['keys'])); + } + +} \ No newline at end of file diff --git a/app/tests/OpenIdProtocolTest.php b/app/tests/OpenIdProtocolTest.php index 0013d333..5851b45f 100644 --- a/app/tests/OpenIdProtocolTest.php +++ b/app/tests/OpenIdProtocolTest.php @@ -213,14 +213,13 @@ class OpenIdProtocolTest extends OpenStackIDBaseTest public function testAuthenticationSetupModePrivateAssociation() { //set login info - Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce); - $params = array( - OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_NS) => OpenIdProtocol::OpenID2MessageType, - OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_Mode) => OpenIdProtocol::SetupMode, - OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_Realm) => "https://www.test.com/", - OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_ReturnTo) => "https://www.test.com/oauth2", - OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_Identity) => "http://specs.openid.net/auth/2.0/identifier_select", + $params = array( + OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_NS) => OpenIdProtocol::OpenID2MessageType, + OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_Mode) => OpenIdProtocol::SetupMode, + OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_Realm) => "https://www.newsite.com/", + OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_ReturnTo) => "https://www.newsite.com/return_to/", + OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_Identity) => "http://specs.openid.net/auth/2.0/identifier_select", OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_ClaimedId) => "http://specs.openid.net/auth/2.0/identifier_select", ); @@ -228,15 +227,35 @@ class OpenIdProtocolTest extends OpenStackIDBaseTest $this->assertResponseStatus(302); - $openid_response = $this->parseOpenIdResponse($response->getTargetUrl()); + $url = $response->getTargetUrl(); + + // post consent response ... + + $consent_response = $this->call('POST', $url, array + ( + 'trust' => array('AllowOnce'), + '_token' => Session::token() + ) + ); + + $this->assertResponseStatus(302); + + $auth_response = $this->action("GET", "OpenIdProviderController@endpoint", + array(), + array(), + array(), + array()); + + $this->assertResponseStatus(302); + + $openid_response = $this->parseOpenIdResponse($auth_response->getTargetUrl()); $this->assertTrue(isset($openid_response[OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_Mode)])); $this->assertTrue(isset($openid_response[OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_NS)])); $this->assertTrue(isset($openid_response[OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_ReturnTo)])); //http://openid.net/specs/openid-authentication-2_0.html#check_auth - $response = $this->action("POST", "OpenIdProviderController@endpoint", - $this->prepareCheckAuthenticationParams($openid_response)); + $response = $this->action("POST", "OpenIdProviderController@endpoint", $this->prepareCheckAuthenticationParams($openid_response)); $openid_response = $this->getOpenIdResponseLineBreak($response->getContent()); $this->assertResponseStatus(200); $this->assertTrue($openid_response['is_valid'] === 'true'); @@ -471,7 +490,6 @@ class OpenIdProtocolTest extends OpenStackIDBaseTest $this->assertTrue(isset($openid_response[OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_ClaimedId)])); $this->assertTrue(!empty($openid_response[OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_ClaimedId)])); - //sreg $this->assertTrue(isset($openid_response[OpenIdSREGExtension::paramNamespace()])); @@ -483,7 +501,7 @@ class OpenIdProtocolTest extends OpenStackIDBaseTest $this->assertTrue(isset($openid_response[OpenIdSREGExtension::param(OpenIdSREGExtension::Email)])); $email = $openid_response[OpenIdSREGExtension::param(OpenIdSREGExtension::Email)]; - $this->assertTrue(!empty($email) && $email === 'smarcet@gmail.com'); + $this->assertTrue(!empty($email) && $email === 'sebastian@tipit.net'); //http://openid.net/specs/openid-authentication-2_0.html#check_auth $response = $this->action("POST", "OpenIdProviderController@endpoint", @@ -501,8 +519,7 @@ class OpenIdProtocolTest extends OpenStackIDBaseTest public function testCheckSetupOAuth2Extension() { - //set login info - Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowForever); + $sreg_required_params = array('email', 'fullname'); $scope = array( sprintf('%s/resource-server/read', $this->current_realm), @@ -526,12 +543,35 @@ class OpenIdProtocolTest extends OpenStackIDBaseTest OpenIdOAuth2Extension::param(OpenIdOAuth2Extension::ClientId) => $this->oauth2_client_id, OpenIdOAuth2Extension::param(OpenIdOAuth2Extension::Scope) => implode(' ', $scope), OpenIdOAuth2Extension::param(OpenIdOAuth2Extension::State) => uniqid(), + //sreg + OpenIdSREGExtension::paramNamespace() => OpenIdSREGExtension::NamespaceUrl, + OpenIdSREGExtension::param(OpenIdSREGExtension::Required) => implode(",", $sreg_required_params), ); $response = $this->action("POST", "OpenIdProviderController@endpoint", $params); $this->assertResponseStatus(302); + $url = $response->getTargetUrl(); + + $response = $this->call('GET', $url); + + $this->assertResponseStatus(200); + + $consent_html_content = $response->getContent(); + + $this->assertTrue(str_contains($consent_html_content, 'Welcome to openstackId - consent')); + $this->assertTrue(str_contains($consent_html_content, 'The site has also requested some personal information')); + $this->assertTrue(str_contains($consent_html_content, 'The site has also requested some permissions for following OAuth2 application')); + + + $response = $this->call('POST', $url, array( + 'trust' => array('AllowOnce'), + '_token' => Session::token() + )); + + $response = $this->call('GET', $response->getTargetUrl()); + $openid_response = $this->parseOpenIdResponse($response->getTargetUrl()); $this->assertTrue(isset($openid_response[OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_Mode)])); @@ -561,7 +601,6 @@ class OpenIdProtocolTest extends OpenStackIDBaseTest $this->assertTrue(isset($openid_response[OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_ClaimedId)])); $this->assertTrue(!empty($openid_response[OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_ClaimedId)])); - //oauth2 $this->assertTrue(isset($openid_response[OpenIdOAuth2Extension::paramNamespace()])); @@ -573,6 +612,18 @@ class OpenIdProtocolTest extends OpenStackIDBaseTest $this->assertTrue(isset($openid_response[OpenIdOAuth2Extension::param(OpenIdOAuth2Extension::Scope)])); $this->assertTrue(!empty($openid_response[OpenIdOAuth2Extension::param(OpenIdOAuth2Extension::Scope)])); + //sreg + + $this->assertTrue(isset($openid_response[OpenIdSREGExtension::paramNamespace()])); + $this->assertTrue($openid_response[OpenIdSREGExtension::paramNamespace()] === OpenIdSREGExtension::NamespaceUrl); + + $this->assertTrue(isset($openid_response[OpenIdSREGExtension::param(OpenIdSREGExtension::FullName)])); + $full_name = $openid_response[OpenIdSREGExtension::param(OpenIdSREGExtension::FullName)]; + $this->assertTrue(!empty($full_name) && $full_name === 'Sebastian Marcet'); + + $this->assertTrue(isset($openid_response[OpenIdSREGExtension::param(OpenIdSREGExtension::Email)])); + $email = $openid_response[OpenIdSREGExtension::param(OpenIdSREGExtension::Email)]; + $this->assertTrue(!empty($email) && $email === 'sebastian@tipit.net'); //http://openid.net/specs/openid-authentication-2_0.html#check_auth $response = $this->action("POST", "OpenIdProviderController@endpoint", diff --git a/app/tests/OpenStackIDBaseTest.php b/app/tests/OpenStackIDBaseTest.php index 5ac37ec5..e5a54ac8 100644 --- a/app/tests/OpenStackIDBaseTest.php +++ b/app/tests/OpenStackIDBaseTest.php @@ -41,6 +41,8 @@ abstract class OpenStackIDBaseTest extends TestCase { DB::table('openid_trusted_sites')->delete(); if (Schema::hasTable('openid_associations')) DB::table('openid_associations')->delete(); + if (Schema::hasTable('user_actions')) + DB::table('user_actions')->delete(); if (Schema::hasTable('openid_users')) DB::table('openid_users')->delete(); if (Schema::hasTable('oauth2_api_endpoint_api_scope')) diff --git a/app/tests/UserServiceTest.php b/app/tests/UserServiceTest.php new file mode 100644 index 00000000..43b81e9c --- /dev/null +++ b/app/tests/UserServiceTest.php @@ -0,0 +1,40 @@ +getByEmail("sebastian@tipit.net"); + $member2 = $member_repository->getByEmail("sebastian+1@tipit.net"); + $member3 = $member_repository->getByEmail("sebastian+2@tipit.net"); + + $user2 = $user_service->buildUser($member2); + $user3 = $user_service->buildUser($member3); + + $this->assertTrue($user2->identifier === 'sebastian.marcet.1'); + $this->assertTrue($user3->identifier === 'sebastian.marcet.2'); + } +} \ No newline at end of file diff --git a/app/tests/UserTest.php b/app/tests/UserTest.php index a69e39f8..228bda42 100644 --- a/app/tests/UserTest.php +++ b/app/tests/UserTest.php @@ -6,14 +6,6 @@ class UserTest extends TestCase public function testMember() { $member = Member::findOrFail(1); - $this->assertTrue($member->FirstName == 'Todd'); - } - - public function testOpenIdUserAssociation() - { - $username = 'sebastian@tipit.net'; - $password = 'Koguryo@1981'; - $member = Member::where('Email', '=', $username)->firstOrFail(); - $this->assertTrue($member->checkPassword($password)); + $this->assertTrue($member->FirstName == 'Sebastian'); } } \ No newline at end of file diff --git a/app/tests/features/bootstrap/FeatureContext.php b/app/tests/features/bootstrap/FeatureContext.php new file mode 100644 index 00000000..9169ae41 --- /dev/null +++ b/app/tests/features/bootstrap/FeatureContext.php @@ -0,0 +1,110 @@ +getHash(); + foreach ($hash as $row) { + $this->params[$row['param']] = $row['value']; + if($row['param'] == 'scope') + $this->params[$row['param']] = sprintf($this->params[$row['param']], $this->current_realm); + } + } + + /** + * @Given exits client Id :arg1 + */ + public function exitsClientId($client_id) + { + $client = Client::where('client_id', '=', $client_id)->first(); + if(is_null($client)) + throw new Exception(sprintf('client id %s does not exist', $client_id)); + } + + /** + * @Given navigate to controller action :arg1 with HTTP method :arg2 + */ + public function navigateToControllerActionWithHttpMethod($action, $method) + { + $this->response = $this->action($method, $action, + $this->params, + array(), + array(), + array()); + } + + /** + * @When i get log as user :arg1 using password :arg2 + */ + public function iGetLogAsUserUsingPassword($user, $password) + { + + Session::save(); + + $login_url = $this->response->getTargetUrl(); + + $this->response = $this->call('POST', $login_url, array( + 'username' => $user, + 'password' => $password, + '_token' => Session::token() + )); + + } + + /** + * @When allow consent :arg1 + */ + public function allowConsent($arg1) + { + Session::save(); + + $auth_url = $this->response->getTargetUrl(); + + $this->response = $this->call('GET', $auth_url); + + $consent_url = $this->response->getTargetUrl(); + + $this->response = $this->call('POST', $consent_url, array( + 'trust' => $arg1, + '_token' => Session::token() + )); + } + + /** + * @Then i get a valid Auth code + */ + public function iGetAValidAuthCode() + { + $response_url = $this->response->getTargetUrl(); + + $this->response = $this->call("GET", $response_url); + + $response_url = $this->response->getTargetUrl(); + $comps = @parse_url($response_url); + $query = $comps['query']; + $output = array(); + parse_str($query, $output); + $this->assertTrue(array_key_exists('code', $output) ); + $this->assertTrue(!empty($output['code']) ); + echo $output['code']; + } + +} \ No newline at end of file diff --git a/app/tests/features/bootstrap/LaravelContext.php b/app/tests/features/bootstrap/LaravelContext.php new file mode 100644 index 00000000..cfa2e6ee --- /dev/null +++ b/app/tests/features/bootstrap/LaravelContext.php @@ -0,0 +1,285 @@ +seed('TestSeeder'); + } + + /** + * Creates the application. + * + * @return Symfony\Component\HttpKernel\HttpKernelInterface + */ + public function createApplication() + { + $unitTesting = true; + + $testEnvironment = 'testing'; + + $app = require_once __DIR__ . '/../../../../bootstrap/start.php'; + return $app; + } + + public function setUp() + { + if ( ! $this->app) + { + $this->refreshApplication(); + } + + $this->redis = \RedisLV4::connection(); + $this->redis->flushall(); + $this->prepareForTests(); + } + + /** + * Refresh the application instance. + * + * @return void + */ + protected function refreshApplication() + { + $this->app = $this->createApplication(); + + $this->client = $this->createClient(); + + $this->app->setRequestForConsoleEnvironment(); + + $this->app->boot(); + } + + /** + * Set the session to the given array. + * + * @param array $data + * @return void + */ + public function session(array $data) + { + $this->startSession(); + + foreach ($data as $key => $value) + { + $this->app['session']->put($key, $value); + } + } + + /** + * Flush all of the current session data. + * + * @return void + */ + public function flushSession() + { + $this->startSession(); + + $this->app['session']->flush(); + } + + /** + * Start the session for the application. + * + * @return void + */ + protected function startSession() + { + if ( ! $this->app['session']->isStarted()) + { + $this->app['session']->start(); + } + } + + /** + * Set the currently logged in user for the application. + * + * @param \Illuminate\Auth\UserInterface $user + * @param string $driver + * @return void + */ + public function be(UserInterface $user, $driver = null) + { + $this->app['auth']->driver($driver)->setUser($user); + } + + /** + * Seed a given database connection. + * + * @param string $class + * @return void + */ + public function seed($class = 'DatabaseSeeder') + { + $this->app['artisan']->call('db:seed', array('--class' => $class)); + } + + /** + * Create a new HttpKernel client instance. + * + * @param array $server + * @return \Symfony\Component\HttpKernel\Client + */ + protected function createClient(array $server = array()) + { + return new Client($this->app, $server); + } + + + /** + * @Given Prepare For Tests is Done + */ + public function prepareForTestsIsDone() + { + $this->setUp(); + + if (Schema::hasTable('banned_ips')) + DB::table('banned_ips')->delete(); + if (Schema::hasTable('user_exceptions_trail')) + DB::table('user_exceptions_trail')->delete(); + if (Schema::hasTable('server_configuration')) + DB::table('server_configuration')->delete(); + if (Schema::hasTable('server_extensions')) + DB::table('server_extensions')->delete(); + if (Schema::hasTable('oauth2_client_api_scope')) + DB::table('oauth2_client_api_scope')->delete(); + if (Schema::hasTable('oauth2_client_authorized_uri')) + DB::table('oauth2_client_authorized_uri')->delete(); + if (Schema::hasTable('oauth2_access_token')) + DB::table('oauth2_access_token')->delete(); + if (Schema::hasTable('oauth2_refresh_token')) + DB::table('oauth2_refresh_token')->delete(); + if (Schema::hasTable('oauth2_client')) + DB::table('oauth2_client')->delete(); + if (Schema::hasTable('openid_trusted_sites')) + DB::table('openid_trusted_sites')->delete(); + if (Schema::hasTable('openid_associations')) + DB::table('openid_associations')->delete(); + if (Schema::hasTable('user_actions')) + DB::table('user_actions')->delete(); + if (Schema::hasTable('openid_users')) + DB::table('openid_users')->delete(); + if (Schema::hasTable('oauth2_api_endpoint_api_scope')) + DB::table('oauth2_api_endpoint_api_scope')->delete(); + if (Schema::hasTable('oauth2_api_endpoint')) + DB::table('oauth2_api_endpoint')->delete(); + if (Schema::hasTable('oauth2_api_scope')) + DB::table('oauth2_api_scope')->delete(); + if (Schema::hasTable('oauth2_api')) + DB::table('oauth2_api')->delete(); + if (Schema::hasTable('oauth2_resource_server')) + DB::table('oauth2_resource_server')->delete(); + + $this->prepareForTests(); + $this->current_realm = Config::get('app.url'); + } + + /** + * Call a controller action and return the Response. + * + * @param string $method + * @param string $action + * @param array $wildcards + * @param array $parameters + * @param array $files + * @param array $server + * @param string $content + * @param bool $changeHistory + * @return \Illuminate\Http\Response + */ + public function action($method, $action, $wildcards = array(), $parameters = array(), $files = array(), $server = array(), $content = null, $changeHistory = true) + { + $uri = $this->app['url']->action($action, $wildcards, true); + + return $this->call($method, $uri, $parameters, $files, $server, $content, $changeHistory); + } + + /** + * Call the given URI and return the Response. + * + * @param string $method + * @param string $uri + * @param array $parameters + * @param array $files + * @param array $server + * @param string $content + * @param bool $changeHistory + * @return \Illuminate\Http\Response + */ + public function call() + { + call_user_func_array(array($this->client, 'request'), func_get_args()); + + return $this->client->getResponse(); + } + + /** + * Asserts that a condition is true. + * + * @param boolean $condition + * @param string $message + * @throws PHPUnit_Framework_AssertionFailedError + */ + public static function assertTrue($condition, $message = '') + { + if(!$condition) + throw new \LogicException($message); + } + + +} \ No newline at end of file diff --git a/app/tests/features/get_auth_code.feature b/app/tests/features/get_auth_code.feature new file mode 100644 index 00000000..8c0d2542 --- /dev/null +++ b/app/tests/features/get_auth_code.feature @@ -0,0 +1,17 @@ +Feature: Get OAuth2 Auth Code + + Background: + Given Prepare For Tests is Done + + Scenario: Get Auth Code With Login + Given these OAuth2 parameters: + | param | value | + | client_id | Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client | + | redirect_uri | https://www.test.com/oauth2 | + | response_type | code | + | scope | profile | + And exits client Id "Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client" + And navigate to controller action "OAuth2ProviderController@authorize" with HTTP method "POST" + When i get log as user "test@test.com" using password "1qaz2wsx" + And allow consent "AllowOnce" + Then i get a valid Auth code diff --git a/app/validators/CustomValidator.php b/app/validators/CustomValidator.php index 97ed08ce..dfd68518 100644 --- a/app/validators/CustomValidator.php +++ b/app/validators/CustomValidator.php @@ -1,15 +1,27 @@ 'GET', - 'HEAD'=>'HEAD', - 'POST'=>'POST', - 'PUT'=>'PUT', - 'DELETE'=>'DELETE', - 'TRACE'=>'TRACE', - 'CONNECT'=>'CONNECT', - 'OPTIONS'=>'OPTIONS', + 'GET' => 'GET', + 'HEAD' => 'HEAD', + 'POST' => 'POST', + 'PUT' => 'PUT', + 'DELETE' => 'DELETE', + 'TRACE' => 'TRACE', + 'CONNECT' => 'CONNECT', + 'OPTIONS' => 'OPTIONS', ); - return array_key_exists($value,$allowed_http_verbs); + return array_key_exists($value, $allowed_http_verbs); } - public function validateRoute($attribute, $value, $parameters){ + public function validateRoute($attribute, $value, $parameters) + { return true; } - public function validateScopename($attribute, $value, $parameters){ + public function validateScopename($attribute, $value, $parameters) + { $value = trim($value); + return preg_match("/^[a-zA-Z0-9\-\.\,\:\_\/]+$/", $value) == 1; } - public function validateHost($attribute, $value, $parameters){ - return filter_var(gethostbyname($value), FILTER_VALIDATE_IP)?true:false; + public function validateHost($attribute, $value, $parameters) + { + return filter_var(gethostbyname($value), FILTER_VALIDATE_IP) ? true : false; } - public function validateApplicationtype($attribute, $value, $parameters){ - if(!is_string($value)) return false; + public function validateApplicationtype($attribute, $value, $parameters) + { + + if (!is_string($value)) { + return false; + } + $value = trim($value); - return ($value == IClient::ApplicationType_Service || $value==IClient::ApplicationType_Web_App || $value==IClient::ApplicationType_JS_Client); + + return in_array($value, Client::$valid_app_types); } - public function validateSslurl($attribute, $value, $parameters){ - return preg_match(";^https:\/\/([\w@][\w.:@]+)\/?[\w\.?=%&=\-@/$,]*$;i",$value)==1; + public function validateSslurl($attribute, $value, $parameters) + { + return preg_match(";^https:\/\/([\w@][\w.:@]+)\/?[\w\.?=%&=\-@/$,]*$;i", $value) == 1; } - public function validateFreeText($attribute, $value, $parameters){ - return preg_match('|^[a-z\-@_.,()\'"\s\:\/]+$|i',$value)==1; + public function validateFreeText($attribute, $value, $parameters) + { + return preg_match('|^[a-z\-@_.,()\'"\s\:\/]+$|i', $value) == 1; } - public function validateSslorigin($attribute, $value, $parameters){ - if(filter_var($value, FILTER_VALIDATE_URL)){ + public function validateSslorigin($attribute, $value, $parameters) + { + if (filter_var($value, FILTER_VALIDATE_URL)) { $parts = @parse_url($value); if ($parts == false) { return false; } - if($parts['scheme']!= 'https') + if ($parts['scheme'] != 'https') { return false; + } - if(isset($parts['query'])) + if (isset($parts['query'])) { return false; + } - if(isset($parts['fragment'])) + if (isset($parts['fragment'])) { return false; + } - if(isset($parts['path'])) + if (isset($parts['path'])) { return false; + } - if(isset($parts['user'])) + if (isset($parts['user'])) { return false; + } - if(isset($parts['pass'])) + if (isset($parts['pass'])) { return false; + } return true; } + return false; } + + public function validateEmailSet($attribute, $value, $parameters) + { + $emails = explode(',', $value); + $res = true; + foreach ($emails as $email) { + $res = $this->validateEmail($attribute, $email); + if (!$res) { + break; + } + } + + return $res; + } + + public function validateUrlSet($attribute, $value, $parameters) + { + $urls = explode(',', $value); + $res = true; + foreach ($urls as $url) { + $res = $this->validateUrl($attribute, $url); + if (!$res) { + break; + } + } + + return $res; + } + + public function validateTokenEndpointAuthMethod($attribute, $value, $parameters) + { + return in_array($value,OAuth2Protocol::$token_endpoint_auth_methods); + } + + public function validateSigningAlg($attribute, $value, $parameters) + { + return in_array($value,OAuth2Protocol::$supported_signing_algorithms); + } + + public function validateSubjectType($attribute, $value, $parameters) + { + return in_array($value, Client::$valid_subject_types); + } + + public function validateEncryptedAlg($attribute, $value, $parameters) + { + return in_array($value,OAuth2Protocol::$supported_key_management_algorithms); + } + + public function validateEncryptedEnc($attribute, $value, $parameters) + { + return in_array($value,OAuth2Protocol::$supported_content_encryption_algorithms); + } + + public function validatePublicKeyPem($attribute, $value, $parameters) + { + $res1 = strpos($value,'-----BEGIN PUBLIC KEY-----'); + $res2 = strpos($value,'-----BEGIN RSA PUBLIC KEY-----'); + $res3 = strpos($value,'-----END PUBLIC KEY-----'); + $res4 = strpos($value,'-----END RSA PUBLIC KEY-----'); + + $PKCS8 = $res1 !== false && $res3 !== false; + $PKCS1 = $res2 !== false && $res4 !== false; + + $rsa = new Crypt_RSA; + $parsed = $rsa->loadKey($value); + + return ($PKCS8 || $PKCS1) && $parsed; + } + + public function validatePublicKeyPemLength($attribute, $value, $parameters) + { + $rsa = new Crypt_RSA; + $parsed = $rsa->loadKey($value); + + return $parsed && $rsa->getSize() > 1024; + } + + public function validatePrivateKeyPem($attribute, $value, $parameters) + { + $res1 = strpos($value,'-----BEGIN PRIVATE KEY-----'); + $res2 = strpos($value,'-----BEGIN RSA PRIVATE KEY-----'); + $res3 = strpos($value,'-----END PRIVATE KEY-----'); + $res4 = strpos($value,'-----END RSA PRIVATE KEY-----'); + + $PKCS8 = $res1 !== false && $res3 !== false; + $PKCS1 = $res2 !== false && $res4 !== false; + + $encrypted = strpos($value,'ENCRYPTED') !== false ; + $password_param = $parameters[0]; + $rsa = new Crypt_RSA; + if(isset($this->data[$password_param]) && $encrypted){ + $rsa->setPassword($this->data[$password_param]); + } + + $parsed = $rsa->loadKey($value); + + return ($PKCS8 || $PKCS1) && $parsed; + } + + public function validatePrivateKeyPemLength($attribute, $value, $parameters) + { + + $encrypted = strpos($value,'ENCRYPTED') !== false ; + $password_param = $parameters[0]; + $rsa = new Crypt_RSA; + if(isset($this->data[$password_param]) && $encrypted){ + $rsa->setPassword($this->data[$password_param]); + } + + $parsed = $rsa->loadKey($value); + + return $parsed && $rsa->getSize() >= 2048; + } + + public function validatePublicKeyUsage($attribute, $value, $parameters) + { + return in_array($value, JSONWebKeyPublicKeyUseValues::$valid_uses); + } + + public function validatePublicKeyType($attribute, $value, $parameters) + { + return in_array($value, JSONWebKeyTypes::$valid_keys_set); + } + + public function validatePrivateKeyPassword($attribute, $value, $parameters){ + $pem_param = $parameters[0]; + if(!isset($this->data[$pem_param])) return true; + $pem_content = $this->data[$pem_param]; + $rsa = new Crypt_RSA; + $rsa->setPassword($value); + $parsed = $rsa->loadKey($pem_content); + return $parsed; + } + + public function validateCustomUrlSet($attribute, $value, $parameters) + { + $app_type_param = $parameters[0]; + if(!isset($this->data[$app_type_param])) return true; + $app_type = $this->data[$app_type_param]; + + $urls = explode(',', $value); + $res = true; + foreach ($urls as $url) { + $res = $app_type === IClient::ApplicationType_Native ? $this->validateUrl($attribute, $url, $parameters) : $this->validateSslurl($attribute, $url, $parameters); + if (!$res) { + break; + } + } + + return $res; + } + + public function validateSslUrlSet($attribute, $value, $parameters) + { + + $urls = explode(',', $value); + $res = true; + foreach ($urls as $url) { + $res = $this->validateSslurl($attribute, $url, $parameters); + if (!$res) { + break; + } + } + + return $res; + } + + public function validateKeyAlg($attribute, $value, $parameters) + { + $key_type_param = $parameters[0]; + $key_type = $this->data[$key_type_param]; + + if($key_type === JSONWebKeyPublicKeyUseValues::Signature) + { + return in_array($value, OAuth2Protocol::$supported_signing_algorithms); + } + else + { + return in_array($value, OAuth2Protocol::$supported_key_management_algorithms); + } + } } \ No newline at end of file diff --git a/app/views/400.blade.php b/app/views/400.blade.php new file mode 100644 index 00000000..d20cb849 --- /dev/null +++ b/app/views/400.blade.php @@ -0,0 +1,15 @@ +@extends('layout') +@section('content') +

OpenstackId Idp - 400

+
+

+ 400. That’s an error. +

+

+ {{ $error_code }} +

+

+ {{ $error_description }} +

+
+@stop \ No newline at end of file diff --git a/app/views/admin/banned-ips.blade.php b/app/views/admin/banned-ips.blade.php index 31cf2cfc..5d0907fe 100644 --- a/app/views/admin/banned-ips.blade.php +++ b/app/views/admin/banned-ips.blade.php @@ -7,8 +7,8 @@ @section('content') @include('menu',array('is_oauth2_admin' => $is_oauth2_admin, 'is_openstackid_admin' => $is_openstackid_admin)) Banned Ips -
-
+
+
@@ -36,7 +36,7 @@ @endif @endforeach @@ -48,5 +48,5 @@ @stop @section('scripts') -{{ HTML::script('js/admin/banned-ips.js') }} -@stop +{{ HTML::script('assets/js/admin/banned-ips.js') }} +@append diff --git a/app/views/admin/server-config.blade.php b/app/views/admin/server-config.blade.php index d7910802..fcd50903 100644 --- a/app/views/admin/server-config.blade.php +++ b/app/views/admin/server-config.blade.php @@ -6,38 +6,56 @@ @section('content') @include('menu',array('is_oauth2_admin' => $is_oauth2_admin, 'is_openstackid_admin' => $is_openstackid_admin)) -
-
+
+
-
General Configuration - - - - +
+ + +
+
+ + +
OPENID Configuration - - - - - - +
+ + +
+
+ + +
+
+ + +
OAUTH2 Configuration - - - - - - - -
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
@foreach($errors->all() as $message) -
- + @endforeach @@ -45,5 +63,5 @@ @stop @section('scripts') -{{ HTML::script('js/admin/server-config.js') }} -@stop \ No newline at end of file +{{ HTML::script('assets/js/admin/server-config.js') }} +@append \ No newline at end of file diff --git a/app/views/admin/users.blade.php b/app/views/admin/users.blade.php index 48021e61..548f89fb 100644 --- a/app/views/admin/users.blade.php +++ b/app/views/admin/users.blade.php @@ -7,8 +7,8 @@ @section('content') @include('menu',array('is_oauth2_admin' => $is_oauth2_admin, 'is_openstackid_admin' => $is_openstackid_admin)) Locked Users -
-
+
+
- {{ HTML::link(URL::action("ApiBannedIPController@delete",array("id"=>$ip->id)),'Revoke',array('data-ip-id'=>$ip->id,'class'=>'btn revoke-ip','title'=>'Revoke given banned ip address')) }} + {{ HTML::link(URL::action("ApiBannedIPController@delete",array("id"=>$ip->id)),'Revoke',array('data-ip-id'=>$ip->id,'class'=>'btn btn-default btn-md active revoke-ip','title'=>'Revoke given banned ip address')) }}
@@ -31,7 +31,7 @@ @endforeach @@ -42,5 +42,5 @@ @stop @section('scripts') -{{ HTML::script('js/admin/users.js') }} -@stop \ No newline at end of file +{{ HTML::script('assets/js/admin/users.js') }} +@append \ No newline at end of file diff --git a/app/views/extensions/ax.blade.php b/app/views/extensions/ax.blade.php index 98db2e6c..06b13c61 100644 --- a/app/views/extensions/ax.blade.php +++ b/app/views/extensions/ax.blade.php @@ -4,7 +4,7 @@
    @foreach ($attributes as $attr) -
  • {{$attr}} 
  • +
  • {{$attr}} 
  • @endforeach
@endif \ No newline at end of file diff --git a/app/views/extensions/oauth2.blade.php b/app/views/extensions/oauth2.blade.php index f77f0785..5bbe4fee 100644 --- a/app/views/extensions/oauth2.blade.php +++ b/app/views/extensions/oauth2.blade.php @@ -3,33 +3,31 @@ The site has also requested some permissions for following OAuth2 application
-
-
-
-
+
+
+
+
-
-

{{$app_name}} 

+
+

{{$app_name}} 

This app would like to:
    @foreach ($requested_scopes as $scope) -
  • {{$scope->short_description}} 
  • +
  • {{$scope->short_description}} 
  • @endforeach

** {{$app_name}} Application and Openstack will use this information in accordance with their respective terms of service and privacy policies.

-
@endif @section('scripts') -@parent -@stop \ No newline at end of file +@append \ No newline at end of file diff --git a/app/views/extensions/sreg.blade.php b/app/views/extensions/sreg.blade.php index 54e4da85..13ef06c9 100644 --- a/app/views/extensions/sreg.blade.php +++ b/app/views/extensions/sreg.blade.php @@ -4,7 +4,7 @@
    @foreach ($attributes as $attr) -
  • {{$attr}} 
  • +
  • {{$attr}} 
  • @endforeach
@endif diff --git a/app/views/home.blade.php b/app/views/home.blade.php index cfea31d0..0330e434 100644 --- a/app/views/home.blade.php +++ b/app/views/home.blade.php @@ -1,25 +1,26 @@ @extends('layout') @section('title') -Welcome to openstackId + Welcome to openstackId @stop @section('meta') -@stop +@append @section('content') -
-
-
- -

OpenstackId Identity Provider

-
-
Log in to OpenStack
-
- Sign in to your account - Register for an OpenStack ID +
+
+
+

OpenstackId Identity Provider

+
+
Log in to OpenStack
+ +

Once you're signed in, you can manage your trusted sites, change + your settings and more.

-

Once you're signed in, you can manage your trusted sites, change your settings and more.

-
@stop \ No newline at end of file diff --git a/app/views/identity.blade.php b/app/views/identity.blade.php index f221a067..25e9343a 100644 --- a/app/views/identity.blade.php +++ b/app/views/identity.blade.php @@ -4,7 +4,7 @@ @stop @section('meta') -@stop +@append @section('content')
@if(Auth::guest() || $another_user) @@ -15,16 +15,16 @@ {{ $username }} @endif @if( $show_pic && !empty($pic)) -
-
- +
+
+
@endif @if( $show_email ) -
- {{ HTML::link(URL::action("UserApiController@unlock",array("id"=>$user->id)),'Unlock',array('data-user-id'=>$user->id,'class'=>'btn unlock-user','title'=>'Unlocks given user')) }} + {{ HTML::link(URL::action("UserApiController@unlock", array("id"=>$user->id)),'Unlock',array('data-user-id'=>$user->id,'class'=>'btn btn-default btn-md active unlock-user','title'=>'Unlocks given user')) }}
+ + + + + + + + + @foreach ($groups as $group) + + + + + + @endforeach + +
NameActive 
{{$group->name}} + active) + checked + @endif + value="{{$group->id}}"/> + +   + {{ HTML::link(URL::action("AdminController@editApiScopeGroup",array("id"=>$group->id)),'Edit',array('class'=>'btn btn-default active edit-api-scope-group','title'=>'Edit a Registered Api Scope Group')) }} + {{ HTML::link(URL::action("ApiScopeGroupController@delete",array("id"=>$group->id)),'Delete',array('class'=>'btn btn-default btn-delete active delete-api-scope-group','title'=>'Deletes a Registered Api Scope Group')) }} +
+ + +
+ +@include('modal', array ('modal_id' => 'dialog-form-api-scope-group', 'modal_title' => 'Register New Api Scope Group', 'modal_save_css_class' => 'save-api-scope-group', 'modal_save_text' => 'Save', 'modal_form' => 'oauth2.profile.admin.api-scope-group-add-form', 'modal_form_data' => array())) + +@stop + +@section('scripts') + + {{ HTML::script('bower_assets/typeahead.js/dist/typeahead.bundle.js')}} + {{ HTML::script('bower_assets/bootstrap-tagsinput/dist/bootstrap-tagsinput.js')}} + {{ HTML::script('assets/js/oauth2/profile/admin/api-scope-groups.js') }} +@append + +@section('css') + {{ HTML::style('bower_assets/bootstrap-tagsinput/dist/bootstrap-tagsinput.css') }} + {{ HTML::style('bower_assets/bootstrap-tagsinput/dist/bootstrap-tagsinput-typeahead.css') }} +@append \ No newline at end of file diff --git a/app/views/oauth2/profile/admin/clients.blade.php b/app/views/oauth2/profile/admin/clients.blade.php index ce7cca60..236ba074 100644 --- a/app/views/oauth2/profile/admin/clients.blade.php +++ b/app/views/oauth2/profile/admin/clients.blade.php @@ -39,5 +39,5 @@
@stop @section('scripts') -{{ HTML::script('js/oauth2/profile/admin/clients.js') }} +{{ HTML::script('assets/js/oauth2/profile/admin/clients.js') }} @stop \ No newline at end of file diff --git a/app/views/oauth2/profile/admin/edit-api-scope-group.blade.php b/app/views/oauth2/profile/admin/edit-api-scope-group.blade.php new file mode 100644 index 00000000..32dbd754 --- /dev/null +++ b/app/views/oauth2/profile/admin/edit-api-scope-group.blade.php @@ -0,0 +1,94 @@ +@extends('layout') + +@section('title') + Welcome to openstackId - Server Admin - Edit Api Scope Group +@stop + +@section('content') + @include('menu',array('is_oauth2_admin' => $is_oauth2_admin, 'is_openstackid_admin' => $is_openstackid_admin)) + Go Back + Edit Api Scope Group - Id {{ $group->id }} +
+
+
+ +
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+ +
+
+ +
+
+
+ +@stop + +@section('scripts') + + {{ HTML::script('bower_assets/typeahead.js/dist/typeahead.bundle.js')}} + {{ HTML::script('bower_assets/bootstrap-tagsinput/dist/bootstrap-tagsinput.js')}} + {{ HTML::script('assets/js/oauth2/profile/admin/edit-api-scope-group.js') }} + + +@append + +@section('css') + {{ HTML::style('bower_assets/bootstrap-tagsinput/dist/bootstrap-tagsinput.css') }} + {{ HTML::style('bower_assets/bootstrap-tagsinput/dist/bootstrap-tagsinput-typeahead.css') }} +@append \ No newline at end of file diff --git a/app/views/oauth2/profile/admin/edit-api.blade.php b/app/views/oauth2/profile/admin/edit-api.blade.php index c9e68ce4..7ea02435 100644 --- a/app/views/oauth2/profile/admin/edit-api.blade.php +++ b/app/views/oauth2/profile/admin/edit-api.blade.php @@ -8,66 +8,57 @@ @include('menu',array('is_oauth2_admin' => $is_oauth2_admin, 'is_openstackid_admin' => $is_openstackid_admin)) {{ Lang::get("messages.edit_api_go_back") }} {{ Lang::get("messages.edit_api_title", array("id" => $api->id)) }} -
-
+ +
+
-
-
- -
- -
+
+ +
-
- -
- -
+
+ +
-
-
- -
-
-
-
- -
+
+
+ -
-
-
-
-

 Available Scopes

+
+
+
+

 Available Scopes

-
+
+ +
-
+
-
-
- {{ HTML::link(URL::action("ApiScopeController@create",null),'Register Scope',array('class'=>'btn add-scope','title'=>'Adds a New API Scope')) }} +
+
+ {{ HTML::link(URL::action("ApiScopeController@create",null),'Register Scope',array('class'=>'btn active btn-primary add-scope','title'=>'Adds a New API Scope')) }}
-
-
+
+
@@ -105,8 +96,8 @@ @endforeach @@ -117,65 +108,32 @@ - - +@include('modal', array ('modal_id' => 'dialog-form-scope', 'modal_title' => 'Register New API Scope', 'modal_save_css_class' => 'save-scope', 'modal_save_text' => 'Save', 'modal_form' => 'oauth2.profile.admin.scope-add-form', 'modal_form_data' => array())) -
-
-
-

 Available Endpoints

+
+
+
+

 Available Endpoints

-
+
+ +
-
+
-
-
- {{ HTML::link(URL::action("ApiEndpointController@create",null),'Register Endpoint',array('class'=>'btn add-endpoint','title'=>'Adds a New API Endpoint')) }} +
+
+ {{ HTML::link(URL::action("ApiEndpointController@create",null),'Register Endpoint',array('class'=>'btn active btn-primary add-endpoint','title'=>'Adds a New API Endpoint')) }}
-
-
+
+
  - {{ HTML::link(URL::action("AdminController@editScope",array("id"=>$scope->id)),'Edit',array('class'=>'btn edit-scope','title'=>'Edits a Registered API Scope')) }} - {{ HTML::link(URL::action("ApiScopeController@delete",array("id"=>$scope->id)),'Delete',array('class'=>'btn delete-scope','title'=>'Deletes a Registered API Scope'))}} + {{ HTML::link(URL::action("AdminController@editScope",array("id"=>$scope->id)),'Edit',array('class'=>'btn btn-default active edit-scope','title'=>'Edits a Registered API Scope')) }} + {{ HTML::link(URL::action("ApiScopeController@delete",array("id"=>$scope->id)),'Delete',array('class'=>'btn btn-default btn-delete active delete-scope','title'=>'Deletes a Registered API Scope'))}}
@@ -201,8 +159,8 @@ @endforeach @@ -213,54 +171,11 @@ - - - +@include('modal', array ('modal_id' => 'dialog-form-endpoint', 'modal_title' => 'Register New API Endpoint', 'modal_save_css_class' => 'save-endpoint', 'modal_save_text' => 'Save', 'modal_form' => 'oauth2.profile.admin.endpoint-add-form', 'modal_form_data' => array())) @stop + @section('scripts') + -{{ HTML::script('js/oauth2/profile/admin/edit-api.js') }} -@stop \ No newline at end of file +{{ HTML::script('assets/js/oauth2/profile/admin/edit-api.js') }} + +@append \ No newline at end of file diff --git a/app/views/oauth2/profile/admin/edit-endpoint.blade.php b/app/views/oauth2/profile/admin/edit-endpoint.blade.php index 4a848627..9f17c32b 100644 --- a/app/views/oauth2/profile/admin/edit-endpoint.blade.php +++ b/app/views/oauth2/profile/admin/edit-endpoint.blade.php @@ -1,112 +1,108 @@ @extends('layout') @section('title') -Welcome to openstackId - Server Admin - Edit API Endpoint + Welcome to openstackId - Server Admin - Edit API Endpoint @stop @section('content') -@include('menu',array('is_oauth2_admin' => $is_oauth2_admin, 'is_openstackid_admin' => $is_openstackid_admin)) -Go Back -{{ Lang::get("messages.edit_endpoint_title", array("id" => $endpoint->id)) }} -
-
-
-
-
- -
- -
+ @include('menu',array('is_oauth2_admin' => $is_oauth2_admin, 'is_openstackid_admin' => $is_openstackid_admin)) + Go Back + {{ Lang::get("messages.edit_endpoint_title", array("id" => $endpoint->id)) }} +
+
+ +
+ +
-
- -
- -
+
+ +
-
- -
- -
+
+ +
-
- -
- -
+
+ + +
+
+ + {{ Form::select('http_method', array('GET' => 'GET', 'POST' => 'POST', 'PUT' => 'PUT', 'DELETE' => 'DELETE'), $endpoint->http_method,array('class' => 'form-control', 'id' => 'http_method')); }} +
+
+ +
+
+
-
- -
- {{ Form::select('http_method', array('GET' => 'GET', 'POST' => 'POST', 'PUT' => 'PUT', 'DELETE' => 'DELETE'), $endpoint->http_method); }} -
-
-
-
- -
-
-
-
- -
-
-
-
- -
-
+ -
- + +
- -
-
- {{Lang::get("messages.edit_endpoint_scope_title")}}  -
    - @foreach($endpoint->api()->first()->scopes()->get() as $scope) - {{-- scope header --}} -
  • - -
  • - @endforeach -
+
+
+ {{Lang::get("messages.edit_endpoint_scope_title")}}  + +
    + @foreach($endpoint->api()->first()->scopes()->get() as $scope) + {{-- scope header --}} +
  • +
    + +
    +
  • + @endforeach +
+
-
@stop @section('scripts') - -{{ HTML::script('js/oauth2/profile/admin/edit-endpoint.js') }} -@stop \ No newline at end of file + + {{ HTML::script('assets/js/oauth2/profile/admin/edit-endpoint.js') }} +@append \ No newline at end of file diff --git a/app/views/oauth2/profile/admin/edit-resource-server.blade.php b/app/views/oauth2/profile/admin/edit-resource-server.blade.php index b2e0f8f0..980eb25c 100644 --- a/app/views/oauth2/profile/admin/edit-resource-server.blade.php +++ b/app/views/oauth2/profile/admin/edit-resource-server.blade.php @@ -8,140 +8,125 @@ @include('menu',array('is_oauth2_admin' => $is_oauth2_admin, 'is_openstackid_admin' => $is_openstackid_admin)) Go Back Edit Resource Server - Id {{ $resource_server->id }} -
-
-
-
-
- -
- -
+
+
+ +
+ +
-
- -
- -
+ +
+ +
-
- -
- -
+ +
+ +
-
-
- -
+ +
+
+ @if(!is_null($resource_server->client()->first())) -
-
- - {{ $resource_server->client()->first()->client_id }} - - {{ $resource_server->client()->first()->client_secret }} - {{ HTML::link(URL::action("ApiResourceServerController@regenerateClientSecret",array("id"=> $resource_server->id)),'Regenerate',array('class'=>'btn regenerate-client-secret','title'=>'Regenerates Client Secret')) }} +
+
+
+
+
+ +
+
+ {{ $resource_server->client()->first()->client_id }} +
+
+
+
+ +
+
+ {{ $resource_server->client()->first()->client_secret }} +
+
+ {{ HTML::link(URL::action("ApiResourceServerController@regenerateClientSecret",array("id"=> $resource_server->id)),'Regenerate',array('class'=>'btn regenerate-client-secret btn-xs btn-default active btn-delete','title'=>'Regenerates Client Secret')) }} +
+ +
+
@endif
- +
-
+
- -
-
-
-

 Available Apis

-
-
+
+Available Apis  +
+
+
+
+ {{ HTML::link(URL::action("ApiController@create"),'Register API',array('class'=>'btn btn-primary active btn-sm add-api','title'=>'Adds a New API')) }} +
-
-
- -
-
- {{ HTML::link(URL::action("ApiController@create"),'Register API',array('class'=>'btn add-api','title'=>'Adds a New API')) }} +
+
+
{{$endpoint->http_method}}   - {{ HTML::link(URL::action("AdminController@editEndpoint",array("id"=>$endpoint->id)),'Edit',array('class'=>'btn edit-endpoint','title'=>'Edits a Registered API Endpoint')) }} - {{ HTML::link(URL::action("ApiEndpointController@delete",array("id"=>$endpoint->id)),'Delete',array('class'=>'btn delete-endpoint','title'=>'Deletes a Registered API Endpoint'))}} + {{ HTML::link(URL::action("AdminController@editEndpoint",array("id"=>$endpoint->id)),'Edit',array('class'=>'btn btn-default active edit-endpoint','title'=>'Edits a Registered API Endpoint')) }} + {{ HTML::link(URL::action("ApiEndpointController@delete",array("id"=>$endpoint->id)),'Delete',array('class'=>'btn btn-default btn-delete active delete-endpoint','title'=>'Deletes a Registered API Endpoint'))}}
+ + + + + + + + + + @foreach($resource_server->apis()->get() as $api) + + + + + + + @endforeach + +
 NameActive 
{{ $api->name}} logo{{ $api->name}} + active) + checked + @endif + value="{{$api->id}}"/> + +   + {{ HTML::link(URL::action("AdminController@editApi",array("id"=>$api->id)),'Edit',array('class'=>'btn btn-default active edit-api','title'=>'Edits a Registered Resource Server API')) }} + {{ HTML::link(URL::action("ApiController@delete",array("id"=>$api->id)),'Delete',array('class'=>'btn btn-default btn-delete active delete-api','title'=>'Deletes a Registered Resource Server API'))}} +
+
-
-
- - - - - - - - - - - @foreach($resource_server->apis()->get() as $api) - - - - - - - @endforeach - -
 NameActive 
{{ $api->name}} logo{{ $api->name}} - active) - checked - @endif - value="{{$api->id}}"/> - -   - {{ HTML::link(URL::action("AdminController@editApi",array("id"=>$api->id)),'Edit',array('class'=>'btn edit-api','title'=>'Edits a Registered Resource Server API')) }} - {{ HTML::link(URL::action("ApiController@delete",array("id"=>$api->id)),'Delete',array('class'=>'btn delete-api','title'=>'Deletes a Registered Resource Server API'))}} -
-
-
-
- - - +@include('modal', array ('modal_id' => 'dialog-form-api', 'modal_title' => 'Register New Resource Server API', 'modal_save_css_class' => 'save-api', 'modal_save_text' => 'Save', 'modal_form' => 'oauth2.profile.admin.resource-server-api-add-form', 'modal_form_data' => array())) @stop @section('scripts') @@ -162,5 +147,5 @@ success : '{{ Lang::get("messages.global_successfully_save_entity", array("entity" => "Resource Server")) }}' }; -{{ HTML::script('js/oauth2/profile/admin/edit-resource-server.js') }} -@stop \ No newline at end of file +{{ HTML::script('assets/js/oauth2/profile/admin/edit-resource-server.js') }} +@append \ No newline at end of file diff --git a/app/views/oauth2/profile/admin/edit-scope.blade.php b/app/views/oauth2/profile/admin/edit-scope.blade.php index c26471b4..46ec94db 100644 --- a/app/views/oauth2/profile/admin/edit-scope.blade.php +++ b/app/views/oauth2/profile/admin/edit-scope.blade.php @@ -1,84 +1,77 @@ @extends('layout') @section('title') -Welcome to openstackId - Server Admin - Edit API Scope + Welcome to openstackId - Server Admin - Edit API Scope @stop @section('content') -@include('menu',array('is_oauth2_admin' => $is_oauth2_admin, 'is_openstackid_admin' => $is_openstackid_admin)) -Go Back -Edit API Scope - Id {{ $scope->id }} -
-
-
-
-
- -
- -
+ @include('menu',array('is_oauth2_admin' => $is_oauth2_admin, 'is_openstackid_admin' => $is_openstackid_admin)) + Go Back + Edit API Scope - Id {{ $scope->id }} +
+
+ +
+ +
-
- -
- -
+
+ +
-
- -
- -
+
+ +
-
-
-
-
-
-
-
-
- -
-
-
-
- -
+
+
+ -
-
+ +
-
@stop @section('scripts') - -{{ HTML::script('js/oauth2/profile/admin/edit-scope.js') }} -@stop \ No newline at end of file + + {{ HTML::script('assets/js/oauth2/profile/admin/edit-scope.js') }} +@append \ No newline at end of file diff --git a/app/views/oauth2/profile/admin/endpoint-add-form.blade.php b/app/views/oauth2/profile/admin/endpoint-add-form.blade.php new file mode 100644 index 00000000..3cbb0682 --- /dev/null +++ b/app/views/oauth2/profile/admin/endpoint-add-form.blade.php @@ -0,0 +1,45 @@ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ +
+
\ No newline at end of file diff --git a/app/views/oauth2/profile/admin/resource-server-add-form.blade.php b/app/views/oauth2/profile/admin/resource-server-add-form.blade.php new file mode 100644 index 00000000..fe8fb226 --- /dev/null +++ b/app/views/oauth2/profile/admin/resource-server-add-form.blade.php @@ -0,0 +1,20 @@ +
+
+ + +
+
+ + +
+ +
+ + +
+
+ +
+
\ No newline at end of file diff --git a/app/views/oauth2/profile/admin/resource-server-api-add-form.blade.php b/app/views/oauth2/profile/admin/resource-server-api-add-form.blade.php new file mode 100644 index 00000000..0de0f0b1 --- /dev/null +++ b/app/views/oauth2/profile/admin/resource-server-api-add-form.blade.php @@ -0,0 +1,15 @@ +
+
+ + +
+
+ + +
+
+ +
+
\ No newline at end of file diff --git a/app/views/oauth2/profile/admin/resource-servers.blade.php b/app/views/oauth2/profile/admin/resource-servers.blade.php index aa750ae7..ed14dd7d 100644 --- a/app/views/oauth2/profile/admin/resource-servers.blade.php +++ b/app/views/oauth2/profile/admin/resource-servers.blade.php @@ -6,22 +6,21 @@ @section('content') @include('menu',array('is_oauth2_admin' => $is_oauth2_admin, 'is_openstackid_admin' => $is_openstackid_admin)) -
- -
-

 Resource Servers

+
+
+

 Resource Servers

-
+
-
+
-
-
- {{ HTML::link(URL::action("ApiResourceServerController@create"),'Add Resource Server',array('class'=>'btn add-resource-server','title'=>'Adds a New Resource Server')) }} +
+
+ {{ HTML::link(URL::action("ApiResourceServerController@create"),'Add Resource Server',array('class'=>'btn active btn-primary add-resource-server','title'=>'Adds a New Resource Server')) }}
@@ -50,8 +49,8 @@ @endforeach @@ -59,31 +58,7 @@
  - {{ HTML::link(URL::action("AdminController@editResourceServer",array("id"=>$resource_server->id)),'Edit',array('class'=>'btn edit-resource-server','title'=>'Edits a Registered Resource Server')) }} - {{ HTML::link(URL::action("ApiResourceServerController@delete",array("id"=>$resource_server->id)),'Delete',array('class'=>'btn delete-resource-server','title'=>'Deletes a Registered Resource Server')) }} + {{ HTML::link(URL::action("AdminController@editResourceServer",array("id"=>$resource_server->id)),'Edit',array('class'=>'btn btn-default active edit-resource-server','title'=>'Edits a Registered Resource Server')) }} + {{ HTML::link(URL::action("ApiResourceServerController@delete",array("id"=>$resource_server->id)),'Delete',array('class'=>'btn btn-default btn-delete active delete-resource-server','title'=>'Deletes a Registered Resource Server')) }}
- +@include('modal', array ('modal_id' => 'dialog-form-resource-server', 'modal_title' => 'Register New Resource Server', 'modal_save_css_class' => 'save-resource-server', 'modal_save_text' => 'Save', 'modal_form' => 'oauth2.profile.admin.resource-server-add-form', 'modal_form_data' => array())) @stop @@ -98,5 +73,5 @@ add : '{{URL::action("ApiResourceServerController@create",null)}}' }; -{{ HTML::script('js/oauth2/profile/admin/resource-servers.js') }} -@stop \ No newline at end of file +{{ HTML::script('assets/js/oauth2/profile/admin/resource-servers.js') }} +@append \ No newline at end of file diff --git a/app/views/oauth2/profile/admin/scope-add-form.blade.php b/app/views/oauth2/profile/admin/scope-add-form.blade.php new file mode 100644 index 00000000..baa39c82 --- /dev/null +++ b/app/views/oauth2/profile/admin/scope-add-form.blade.php @@ -0,0 +1,30 @@ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+ +
+
\ No newline at end of file diff --git a/app/views/oauth2/profile/admin/server-private-key-add-form.blade.php b/app/views/oauth2/profile/admin/server-private-key-add-form.blade.php new file mode 100644 index 00000000..7efd7f98 --- /dev/null +++ b/app/views/oauth2/profile/admin/server-private-key-add-form.blade.php @@ -0,0 +1,45 @@ +
+
+ + +
+
+ +
+ + to + +
+
+
+ +
+
+ + {{ Form::select('usage', utils\ArrayUtils::convert2Assoc(\jwk\JSONWebKeyPublicKeyUseValues::$valid_uses) ,null , array('class' => 'form-control', 'id' => 'usage')) }} +
+
+ + {{ Form::select('alg', array() ,null , array('class' => 'form-control', 'id' => 'alg')) }} +
+
+ + +
+
+ + +
+
+ +
+ +
\ No newline at end of file diff --git a/app/views/oauth2/profile/admin/server-private-keys.blade.php b/app/views/oauth2/profile/admin/server-private-keys.blade.php new file mode 100644 index 00000000..fc5f5a67 --- /dev/null +++ b/app/views/oauth2/profile/admin/server-private-keys.blade.php @@ -0,0 +1,104 @@ +@extends('layout') +@section('title') + Welcome to openstackId - Server Admin - Server Private Keys +@stop +@section('css') + {{ HTML::style('bower_assets/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css') }} + {{ HTML::style('assets/css/private-keys.css') }} +@append +@section('scripts') + {{ HTML::script('bower_assets/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js')}} + {{ HTML::script('bower_assets/pwstrength-bootstrap/dist/pwstrength-bootstrap-1.2.7.min.js')}} + {{ HTML::script('assets/js/oauth2/profile/admin/server-private-keys.js') }} + + +@append +@section('content') + @include('menu',array('is_oauth2_admin' => $is_oauth2_admin, 'is_openstackid_admin' => $is_openstackid_admin)) + + + + + + + + + + + + + + @foreach ($private_keys as $private_key) + + + + + + @endforeach + +
+
+
Private keys 
+
+ Add Private Key +
+

This is a list of Private Keys keys associated with the server. Remove any keys that you do not recognize.

+
+
+
+   +
+
+ +
+
+
+
+
+
+
+ {{$private_key->kid}} {{$private_key->usage}} {{$private_key->type}} {{$private_key->alg}} +
+
+
+
+ {{$private_key->getSHA_256_Thumbprint()}} +
+
+
+
+ valid from {{$private_key->valid_from}} to {{$private_key->valid_to}} +
+
+
+
+
Delete
+ + + @include('modal', array ('modal_id' => 'ModalAddPrivateKey', 'modal_title' => 'Add Private Key', 'modal_save_css_class' => 'save-private-key', 'modal_save_text' => 'Save', 'modal_form' => 'oauth2.profile.admin.server-private-key-add-form', 'modal_form_data' => array())) +@stop \ No newline at end of file diff --git a/app/views/oauth2/profile/clients.blade.php b/app/views/oauth2/profile/clients.blade.php index ab981a97..0381d693 100644 --- a/app/views/oauth2/profile/clients.blade.php +++ b/app/views/oauth2/profile/clients.blade.php @@ -1,132 +1,94 @@ @extends('layout') @section('title') -Welcome to openstackId - OAUTH2 Console - Clients + Welcome to openstackId - OAUTH2 Console - Clients @stop @section('content') -@include('menu',array('is_oauth2_admin' => $is_oauth2_admin, 'is_openstackid_admin' => $is_openstackid_admin)) -
-
-  Registered Applications - {{ HTML::link(URL::action("ClientApiController@create",null),'Register Application',array('class'=>'btn add-client','title'=>'Adds a Registered Application')) }} - @if (count($clients)>0) - - - - - - - - - - - - - @foreach ($clients as $client) - - - - - - - - - @endforeach - -
Application NameApplication TypeIs ActiveIs LockedModified 
{{ $client->app_name }}{{ $client->getFriendlyApplicationType()}} - active) - checked - @endif - value="{{$client->id}}"/> - - locked) - checked - @endif - value="{{$client->id}}" disabled="disabled" /> - {{ $client->updated_at }}  - {{ HTML::link(URL::action("AdminController@editRegisteredClient",array("id"=>$client->id)),'Edit',array('class'=>'btn edit-client','title'=>'Edits a Registered Application')) }} - {{ HTML::link(URL::action("ClientApiController@delete",array("id"=>$client->id)),'Delete',array('class'=>'btn del-client','title'=>'Deletes a Registered Application')) }}
- @endif + @include('menu',array('is_oauth2_admin' => $is_oauth2_admin, 'is_openstackid_admin' => $is_openstackid_admin)) +
+
+  Registered + Applications + + {{ HTML::link(URL::action("ClientApiController@create",null),'Register Application',array('class'=>'btn btn-primary btn-md active add-client','title'=>'Adds a Registered Application')) }} + @if (count($clients)>0) + + + + + + + + + + + + + @foreach ($clients as $client) + + + + + + + + + @endforeach + +
Application NameApplication TypeIs ActiveIs LockedModified 
{{ $client->app_name }}{{ $client->getFriendlyApplicationType()}} + active) + checked + @endif + value="{{$client->id}}"/> + + locked) + checked + @endif + value="{{$client->id}}" disabled="disabled" /> + {{ $client->updated_at }}  + {{ HTML::link(URL::action("AdminController@editRegisteredClient",array("id"=>$client->id)),'Edit',array('class'=>'btn btn-default btn-md active edit-client','title'=>'Edits a Registered Application')) }} + {{ HTML::link(URL::action("ClientApiController@delete",array("id"=>$client->id)),'Delete',array('class'=>'btn btn-default btn-md active del-client','title'=>'Deletes a Registered Application')) }}
+ @endif +
-
-