diff --git a/app/Http/Controllers/OAuth2/OAuth2ProviderController.php b/app/Http/Controllers/OAuth2/OAuth2ProviderController.php index 285fc005..8151231c 100644 --- a/app/Http/Controllers/OAuth2/OAuth2ProviderController.php +++ b/app/Http/Controllers/OAuth2/OAuth2ProviderController.php @@ -1,4 +1,5 @@ -oauth2_protocol = $oauth2_protocol; $this->auth_service = $auth_service; $this->client_repository = $client_repository; @@ -76,6 +78,112 @@ public function __construct * use of the "POST" method as well. * @return mixed */ + #[OA\Get( + path: '/oauth2/auth', + operationId: 'oauth2AuthorizeGet', + summary: 'OAuth2 Authorization Endpoint (GET)', + description: 'Initiates an OAuth2 authorization flow. Supports Authorization Code, Implicit, Hybrid, and OpenID Connect flows. Per RFC 6749 §3.1, GET is required.', + tags: ['OAuth2 / OpenID Connect'], + parameters: [ + new OA\Parameter(name: 'response_type', in: 'query', required: true, description: '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. Possible values are: code, token, id_token, otp, none. The "none" value cannot be used with any other response type value.', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'client_id', in: 'query', required: true, description: 'OAuth2 client identifier', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'redirect_uri', in: 'query', required: true, description: 'Redirect URI', schema: new OA\Schema(type: 'string', format: 'uri')), + new OA\Parameter(name: 'scope', in: 'query', required: false, description: 'Space-delimited scopes', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'state', in: 'query', required: false, description: 'Opaque state parameter', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'approval_prompt', in: 'query', required: false, description: '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.', schema: new OA\Schema(type: 'string', enum: ['auto', 'force'])), + new OA\Parameter(name: 'access_type', in: 'query', required: false, description: 'Indicates whether your application needs to access an API when the user is not present at the browser. This parameter defaults to online. If your application needs to refresh access tokens 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.', schema: new OA\Schema(type: 'string', enum: ['online', 'offline'])), + new OA\Parameter(name: 'response_mode', in: 'query', required: false, description: 'OPTIONAL. 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.\nThe 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.', schema: new OA\Schema(type: 'string', enum: ['query', 'fragment', 'form_post', 'direct'])), + new OA\Parameter(name: 'code_challenge', in: 'query', required: false, description: 'PKCE code challenge', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'code_challenge_method', in: 'query', required: false, description: 'Optional. PKCE challenge method', schema: new OA\Schema(type: 'string', enum: ['plain', 'S256'])), + new OA\Parameter(name: 'display', in: 'query', required: false, description: 'UI display preference (OIDC)', schema: new OA\Schema(type: 'string', enum: ['page', 'popup', 'touch', 'wap', 'native'])), + new OA\Parameter(name: 'tenant', in: 'query', required: false, description: 'Tenant identifier', schema: new OA\Schema(type: 'string')), + ], + responses: [ + new OA\Response(response: HttpResponse::HTTP_OK, description: 'Authorization request processed (response in body), depends on "response_mode" param'), + new OA\Response(response: HttpResponse::HTTP_FOUND, description: 'Redirect to client redirect_uri with authorization code, tokens, or error'), + new OA\Response(response: HttpResponse::HTTP_BAD_REQUEST, description: 'Bad Request', content: new OA\JsonContent(ref: '#/components/schemas/OAuth2ErrorResponse')), + ] + )] + #[OA\Post( + path: '/oauth2/auth', + operationId: 'oauth2AuthorizePost', + summary: 'OAuth2 Authorization Endpoint (POST)', + description: 'Initiates an OAuth2 authorization flow via POST. Same parameters as GET but sent as form data.', + tags: ['OAuth2 / OpenID Connect'], + requestBody: new OA\RequestBody( + description: 'Authorization request parameters', + required: true, + content: new OA\MediaType( + mediaType: 'application/x-www-form-urlencoded', + schema: new OA\Schema(ref: '#/components/schemas/OAuth2AuthorizationRequest') + ) + ), + responses: [ + new OA\Response(response: HttpResponse::HTTP_OK, description: 'Authorization request processed (response in body), depends on "response_mode" param'), + new OA\Response(response: HttpResponse::HTTP_FOUND, description: 'Redirect to client redirect_uri with authorization code, tokens, or error'), + new OA\Response(response: HttpResponse::HTTP_BAD_REQUEST, description: 'Bad Request', content: new OA\JsonContent(ref: '#/components/schemas/OAuth2ErrorResponse')), + ] + )] + #[OA\Put( + path: '/oauth2/auth', + operationId: 'oauth2AuthorizePut', + summary: 'OAuth2 Authorization Endpoint (PUT)', + description: 'Initiates an OAuth2 authorization flow via PUT. Same parameters as GET but sent as form data.', + tags: ['OAuth2 / OpenID Connect'], + requestBody: new OA\RequestBody( + description: 'Authorization request parameters', + required: true, + content: new OA\MediaType( + mediaType: 'application/x-www-form-urlencoded', + schema: new OA\Schema(ref: '#/components/schemas/OAuth2AuthorizationRequest') + ) + ), + responses: [ + new OA\Response(response: HttpResponse::HTTP_OK, description: 'Authorization request processed (response in body), depends on "response_mode" param'), + new OA\Response(response: HttpResponse::HTTP_FOUND, description: 'Redirect to client redirect_uri with authorization code, tokens, or error'), + new OA\Response(response: HttpResponse::HTTP_BAD_REQUEST, description: 'Bad Request', content: new OA\JsonContent(ref: '#/components/schemas/OAuth2ErrorResponse')), + ] + )] + #[OA\Patch( + path: '/oauth2/auth', + operationId: 'oauth2AuthorizePatch', + summary: 'OAuth2 Authorization Endpoint (PATCH)', + description: 'Initiates an OAuth2 authorization flow via PATCH. Same parameters as GET but sent as form data.', + tags: ['OAuth2 / OpenID Connect'], + requestBody: new OA\RequestBody( + description: 'Authorization request parameters', + required: true, + content: new OA\MediaType( + mediaType: 'application/x-www-form-urlencoded', + schema: new OA\Schema(ref: '#/components/schemas/OAuth2AuthorizationRequest') + ) + ), + responses: [ + new OA\Response(response: HttpResponse::HTTP_OK, description: 'Authorization request processed (response in body), depends on "response_mode" param'), + new OA\Response(response: HttpResponse::HTTP_FOUND, description: 'Redirect to client redirect_uri with authorization code, tokens, or error'), + new OA\Response(response: HttpResponse::HTTP_BAD_REQUEST, description: 'Bad Request', content: new OA\JsonContent(ref: '#/components/schemas/OAuth2ErrorResponse')), + ] + )] + #[OA\Delete( + path: '/oauth2/auth', + operationId: 'oauth2AuthorizeDelete', + summary: 'OAuth2 Authorization Endpoint (DELETE)', + description: 'Initiates an OAuth2 authorization flow via DELETE. Same parameters as GET but sent as form data.', + tags: ['OAuth2 / OpenID Connect'], + requestBody: new OA\RequestBody( + description: 'Authorization request parameters', + required: true, + content: new OA\MediaType( + mediaType: 'application/x-www-form-urlencoded', + schema: new OA\Schema(ref: '#/components/schemas/OAuth2AuthorizationRequest') + ) + ), + responses: [ + new OA\Response(response: HttpResponse::HTTP_OK, description: 'Authorization request processed (response in body), depends on "response_mode" param'), + new OA\Response(response: HttpResponse::HTTP_FOUND, description: 'Redirect to client redirect_uri with authorization code, tokens, or error'), + new OA\Response(response: HttpResponse::HTTP_BAD_REQUEST, description: 'Bad Request', content: new OA\JsonContent(ref: '#/components/schemas/OAuth2ErrorResponse')), + ] + )] public function auth() { try { @@ -136,6 +244,26 @@ public function auth() * Token HTTP Endpoint * @return mixed */ + #[OA\Post( + path: '/oauth2/token', + operationId: 'oauth2Token', + summary: 'OAuth2 Token Endpoint', + description: 'Issues access tokens. Supports authorization_code, client_credentials, password, refresh_token, and passwordless grant types.', + tags: ['OAuth2 / OpenID Connect'], + security: [['OAuth2ProviderClientBasic' => []], ['OAuth2ProviderSecurity' => []]], + requestBody: new OA\RequestBody( + description: 'Token request parameters', + required: true, + content: new OA\MediaType( + mediaType: 'application/x-www-form-urlencoded', + schema: new OA\Schema(ref: '#/components/schemas/OAuth2TokenRequest') + ) + ), + responses: [ + new OA\Response(response: HttpResponse::HTTP_OK, description: 'Successful token response', content: new OA\JsonContent(ref: '#/components/schemas/OAuth2TokenResponse')), + new OA\Response(response: HttpResponse::HTTP_BAD_REQUEST, description: 'Token error', content: new OA\JsonContent(ref: '#/components/schemas/OAuth2ErrorResponse')), + ] + )] public function token() { @@ -166,6 +294,26 @@ public function token() * Revoke Token HTTP Endpoint * @return mixed */ + #[OA\Post( + path: '/oauth2/token/revoke', + operationId: 'oauth2RevokeToken', + summary: 'OAuth2 Token Revocation Endpoint', + description: 'Revokes an access token or refresh token per RFC 7009. The endpoint is idempotent — revoking a non-existent token returns success.', + tags: ['OAuth2 / OpenID Connect'], + security: [['OAuth2ProviderClientBasic' => []], ['OAuth2ProviderSecurity' => []]], + requestBody: new OA\RequestBody( + description: 'Token revocation parameters', + required: true, + content: new OA\MediaType( + mediaType: 'application/x-www-form-urlencoded', + schema: new OA\Schema(ref: '#/components/schemas/OAuth2TokenRevocationRequest') + ) + ), + responses: [ + new OA\Response(response: HttpResponse::HTTP_OK, description: 'Token successfully revoked'), + new OA\Response(response: HttpResponse::HTTP_BAD_REQUEST, description: 'Revocation error', content: new OA\JsonContent(ref: '#/components/schemas/OAuth2ErrorResponse')), + ] + )] public function revoke() { $response = $this->oauth2_protocol->revoke @@ -196,6 +344,26 @@ public function revoke() * Introspection Token HTTP Endpoint * @return mixed */ + #[OA\Post( + path: '/oauth2/token/introspection', + operationId: 'oauth2IntrospectToken', + summary: 'OAuth2 Token Introspection Endpoint', + description: 'Validates and returns metadata about an access token per RFC 7662. Returns detailed information about the token including associated user data.', + tags: ['OAuth2 / OpenID Connect'], + security: [['OAuth2ProviderClientBasic' => []], ['OAuth2ProviderSecurity' => []]], + requestBody: new OA\RequestBody( + description: 'Token introspection parameters', + required: true, + content: new OA\MediaType( + mediaType: 'application/x-www-form-urlencoded', + schema: new OA\Schema(ref: '#/components/schemas/OAuth2TokenIntrospectionRequest') + ) + ), + responses: [ + new OA\Response(response: HttpResponse::HTTP_OK, description: 'Token introspection response', content: new OA\JsonContent(ref: '#/components/schemas/OAuth2IntrospectionResponse')), + new OA\Response(response: HttpResponse::HTTP_BAD_REQUEST, description: 'Introspection error', content: new OA\JsonContent(ref: '#/components/schemas/OAuth2ErrorResponse')), + ] + )] public function introspection() { @@ -226,6 +394,16 @@ public function introspection() * OP's JSON Web Key Set [JWK] document. * @return string */ + #[OA\Get( + path: '/oauth2/certs', + operationId: 'oauth2GetCerts', + summary: 'JSON Web Key Set (JWKS) Endpoint', + description: 'Returns the OpenID Provider JSON Web Key Set document containing public keys used for signing tokens (RFC 7517).', + tags: ['OAuth2 / OpenID Connect'], + responses: [ + new OA\Response(response: HttpResponse::HTTP_OK, description: 'JWKS document', content: new OA\JsonContent(ref: '#/components/schemas/JWKSResponse')), + ] + )] public function certs() { @@ -236,6 +414,16 @@ public function certs() return $response; } + #[OA\Get( + path: '/.well-known/openid-configuration', + operationId: 'oauth2Discovery', + summary: 'OpenID Connect Discovery Endpoint', + description: 'Returns the OpenID Provider Configuration document per OpenID Connect Discovery 1.0.', + tags: ['OAuth2 / OpenID Connect'], + responses: [ + new OA\Response(response: HttpResponse::HTTP_OK, description: 'OpenID Connect Discovery document', content: new OA\JsonContent(ref: '#/components/schemas/OpenIDDiscoveryResponse')), + ] + )] public function discovery() { @@ -258,6 +446,44 @@ public function checkSessionIFrame() /** * @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout */ + #[OA\Get( + path: '/oauth2/end-session', + operationId: 'oauth2EndSessionGet', + summary: 'OpenID Connect End Session Endpoint (GET)', + description: 'Initiates RP-Initiated Logout per OpenID Connect Session Management 1.0. Terminates the user session and optionally redirects to the post-logout URI.', + tags: ['OAuth2 / OpenID Connect'], + parameters: [ + new OA\Parameter(name: 'client_id', in: 'query', required: true, description: 'OAuth2 client identifier', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'id_token_hint', in: 'query', required: false, description: 'Previously issued ID token', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'post_logout_redirect_uri', in: 'query', required: false, description: 'URI to redirect after logout (must be registered)', schema: new OA\Schema(type: 'string', format: 'uri')), + new OA\Parameter(name: 'state', in: 'query', required: false, description: 'Opaque state parameter returned in the redirect', schema: new OA\Schema(type: 'string')), + ], + responses: [ + new OA\Response(response: HttpResponse::HTTP_FOUND, description: 'Redirect to post_logout_redirect_uri'), + new OA\Response(response: HttpResponse::HTTP_OK, description: 'Session ended page (HTML)'), + new OA\Response(response: HttpResponse::HTTP_BAD_REQUEST, description: 'Invalid logout request', content: new OA\JsonContent(ref: '#/components/schemas/OAuth2ErrorResponse')), + ] + )] + #[OA\Post( + path: '/oauth2/end-session', + operationId: 'oauth2EndSessionPost', + summary: 'OpenID Connect End Session Endpoint (POST)', + description: 'Initiates RP-Initiated Logout via POST. Same parameters as GET but sent as form data.', + tags: ['OAuth2 / OpenID Connect'], + requestBody: new OA\RequestBody( + description: 'End session parameters', + required: true, + content: new OA\MediaType( + mediaType: 'application/x-www-form-urlencoded', + schema: new OA\Schema(ref: '#/components/schemas/OAuth2EndSessionRequest') + ) + ), + responses: [ + new OA\Response(response: HttpResponse::HTTP_FOUND, description: 'Redirect to post_logout_redirect_uri'), + new OA\Response(response: HttpResponse::HTTP_OK, description: 'Session ended page (HTML)'), + new OA\Response(response: HttpResponse::HTTP_BAD_REQUEST, description: 'Invalid logout request', content: new OA\JsonContent(ref: '#/components/schemas/OAuth2ErrorResponse')), + ] + )] public function endSession() { $request = new OAuth2LogoutRequest @@ -285,4 +511,4 @@ public function endSession() return View::make('oauth2.session.session-ended'); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/app/Swagger/OAuth2ProviderControllerSchemas.php b/app/Swagger/OAuth2ProviderControllerSchemas.php new file mode 100644 index 00000000..a964aa56 --- /dev/null +++ b/app/Swagger/OAuth2ProviderControllerSchemas.php @@ -0,0 +1,176 @@ +