mirror of https://github.com/ory/hydra
855 lines
24 KiB
Go
855 lines
24 KiB
Go
// Copyright © 2022 Ory Corp
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package client
|
|
|
|
import (
|
|
"context"
|
|
"crypto/subtle"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/julienschmidt/httprouter"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/ory/fosite"
|
|
"github.com/ory/herodot"
|
|
"github.com/ory/hydra/v2/x"
|
|
"github.com/ory/x/errorsx"
|
|
"github.com/ory/x/httprouterx"
|
|
"github.com/ory/x/jsonx"
|
|
"github.com/ory/x/openapix"
|
|
"github.com/ory/x/pagination/tokenpagination"
|
|
"github.com/ory/x/urlx"
|
|
"github.com/ory/x/uuidx"
|
|
)
|
|
|
|
type Handler struct {
|
|
r InternalRegistry
|
|
}
|
|
|
|
const (
|
|
ClientsHandlerPath = "/clients"
|
|
DynClientsHandlerPath = "/oauth2/register"
|
|
)
|
|
|
|
func NewHandler(r InternalRegistry) *Handler {
|
|
return &Handler{
|
|
r: r,
|
|
}
|
|
}
|
|
|
|
func (h *Handler) SetRoutes(admin *httprouterx.RouterAdmin, public *httprouterx.RouterPublic) {
|
|
admin.GET(ClientsHandlerPath, h.listOAuth2Clients)
|
|
admin.POST(ClientsHandlerPath, h.createOAuth2Client)
|
|
admin.GET(ClientsHandlerPath+"/:id", h.Get)
|
|
admin.PUT(ClientsHandlerPath+"/:id", h.setOAuth2Client)
|
|
admin.PATCH(ClientsHandlerPath+"/:id", h.patchOAuth2Client)
|
|
admin.DELETE(ClientsHandlerPath+"/:id", h.deleteOAuth2Client)
|
|
admin.PUT(ClientsHandlerPath+"/:id/lifespans", h.setOAuth2ClientLifespans)
|
|
|
|
public.POST(DynClientsHandlerPath, h.createOidcDynamicClient)
|
|
public.GET(DynClientsHandlerPath+"/:id", h.getOidcDynamicClient)
|
|
public.PUT(DynClientsHandlerPath+"/:id", h.setOidcDynamicClient)
|
|
public.DELETE(DynClientsHandlerPath+"/:id", h.deleteOidcDynamicClient)
|
|
}
|
|
|
|
// OAuth 2.0 Client Creation Parameters
|
|
//
|
|
// swagger:parameters createOAuth2Client
|
|
//
|
|
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
|
|
type createOAuth2Client struct {
|
|
// OAuth 2.0 Client Request Body
|
|
//
|
|
// in: body
|
|
// required: true
|
|
Body Client
|
|
}
|
|
|
|
// swagger:route POST /admin/clients oAuth2 createOAuth2Client
|
|
//
|
|
// # Create OAuth 2.0 Client
|
|
//
|
|
// Create a new OAuth 2.0 client. If you pass `client_secret` the secret is used, otherwise a random secret
|
|
// is generated. The secret is echoed in the response. It is not possible to retrieve it later on.
|
|
//
|
|
// Consumes:
|
|
// - application/json
|
|
//
|
|
// Produces:
|
|
// - application/json
|
|
//
|
|
// Schemes: http, https
|
|
//
|
|
// Responses:
|
|
// 201: oAuth2Client
|
|
// 400: errorOAuth2BadRequest
|
|
// default: errorOAuth2Default
|
|
func (h *Handler) createOAuth2Client(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
c, err := h.CreateClient(r, h.r.ClientValidator().Validate, false)
|
|
if err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
h.r.Writer().WriteCreated(w, r, "/admin"+ClientsHandlerPath+"/"+c.GetID(), &c)
|
|
}
|
|
|
|
// OpenID Connect Dynamic Client Registration Parameters
|
|
//
|
|
// swagger:parameters createOidcDynamicClient
|
|
//
|
|
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
|
|
type createOidcDynamicClient struct {
|
|
// Dynamic Client Registration Request Body
|
|
//
|
|
// in: body
|
|
// required: true
|
|
Body Client
|
|
}
|
|
|
|
// swagger:route POST /oauth2/register oidc createOidcDynamicClient
|
|
//
|
|
// # Register OAuth2 Client using OpenID Dynamic Client Registration
|
|
//
|
|
// This endpoint behaves like the administrative counterpart (`createOAuth2Client`) but is capable of facing the
|
|
// public internet directly and can be used in self-service. It implements the OpenID Connect
|
|
// Dynamic Client Registration Protocol. This feature needs to be enabled in the configuration. This endpoint
|
|
// is disabled by default. It can be enabled by an administrator.
|
|
//
|
|
// Please note that using this endpoint you are not able to choose the `client_secret` nor the `client_id` as those
|
|
// values will be server generated when specifying `token_endpoint_auth_method` as `client_secret_basic` or
|
|
// `client_secret_post`.
|
|
//
|
|
// The `client_secret` will be returned in the response and you will not be able to retrieve it later on.
|
|
// Write the secret down and keep it somewhere safe.
|
|
//
|
|
// Consumes:
|
|
// - application/json
|
|
//
|
|
// Produces:
|
|
// - application/json
|
|
//
|
|
// Schemes: http, https
|
|
//
|
|
// Responses:
|
|
// 201: oAuth2Client
|
|
// 400: errorOAuth2BadRequest
|
|
// default: errorOAuth2Default
|
|
func (h *Handler) createOidcDynamicClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
if err := h.requireDynamicAuth(r); err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
c, err := h.CreateClient(r, h.r.ClientValidator().ValidateDynamicRegistration, true)
|
|
if err != nil {
|
|
h.r.Writer().WriteError(w, r, errorsx.WithStack(err))
|
|
return
|
|
}
|
|
|
|
h.r.Writer().WriteCreated(w, r, "/admin"+ClientsHandlerPath+"/"+c.GetID(), &c)
|
|
}
|
|
|
|
func (h *Handler) CreateClient(r *http.Request, validator func(context.Context, *Client) error, isDynamic bool) (*Client, error) {
|
|
var c Client
|
|
if err := json.NewDecoder(r.Body).Decode(&c); err != nil {
|
|
return nil, errorsx.WithStack(herodot.ErrBadRequest.WithReasonf("Unable to decode the request body: %s", err))
|
|
}
|
|
|
|
if isDynamic {
|
|
if c.Secret != "" {
|
|
return nil, errorsx.WithStack(herodot.ErrBadRequest.WithReasonf("It is not allowed to choose your own OAuth2 Client secret."))
|
|
}
|
|
// We do not allow to set the client ID for dynamic clients.
|
|
c.ID = uuidx.NewV4().String()
|
|
}
|
|
|
|
if len(c.Secret) == 0 {
|
|
secretb, err := x.GenerateSecret(26)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.Secret = string(secretb)
|
|
}
|
|
|
|
if err := validator(r.Context(), &c); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
secret := c.Secret
|
|
c.CreatedAt = time.Now().UTC().Round(time.Second)
|
|
c.UpdatedAt = c.CreatedAt
|
|
|
|
token, signature, err := h.r.OAuth2HMACStrategy().GenerateAccessToken(r.Context(), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c.RegistrationAccessToken = token
|
|
c.RegistrationAccessTokenSignature = signature
|
|
c.RegistrationClientURI = urlx.AppendPaths(h.r.Config().PublicURL(r.Context()), DynClientsHandlerPath+"/"+c.GetID()).String()
|
|
|
|
if err := h.r.ClientManager().CreateClient(r.Context(), &c); err != nil {
|
|
return nil, err
|
|
}
|
|
c.Secret = ""
|
|
if !c.IsPublic() {
|
|
c.Secret = secret
|
|
}
|
|
return &c, nil
|
|
}
|
|
|
|
// Set OAuth 2.0 Client Parameters
|
|
//
|
|
// swagger:parameters setOAuth2Client
|
|
//
|
|
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
|
|
type setOAuth2Client struct {
|
|
// OAuth 2.0 Client ID
|
|
//
|
|
// in: path
|
|
// required: true
|
|
ID string `json:"id"`
|
|
|
|
// OAuth 2.0 Client Request Body
|
|
//
|
|
// in: body
|
|
// required: true
|
|
Body Client
|
|
}
|
|
|
|
// swagger:route PUT /admin/clients/{id} oAuth2 setOAuth2Client
|
|
//
|
|
// # Set OAuth 2.0 Client
|
|
//
|
|
// Replaces an existing OAuth 2.0 Client with the payload you send. If you pass `client_secret` the secret is used,
|
|
// otherwise the existing secret is used.
|
|
//
|
|
// If set, the secret is echoed in the response. It is not possible to retrieve it later on.
|
|
//
|
|
// OAuth 2.0 Clients are used to perform OAuth 2.0 and OpenID Connect flows. Usually, OAuth 2.0 clients are
|
|
// generated for applications which want to consume your OAuth 2.0 or OpenID Connect capabilities.
|
|
//
|
|
// Consumes:
|
|
// - application/json
|
|
//
|
|
// Produces:
|
|
// - application/json
|
|
//
|
|
// Schemes: http, https
|
|
//
|
|
// Responses:
|
|
// 200: oAuth2Client
|
|
// 400: errorOAuth2BadRequest
|
|
// 404: errorOAuth2NotFound
|
|
// default: errorOAuth2Default
|
|
func (h *Handler) setOAuth2Client(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
var c Client
|
|
if err := json.NewDecoder(r.Body).Decode(&c); err != nil {
|
|
h.r.Writer().WriteError(w, r, errorsx.WithStack(herodot.ErrBadRequest.WithReasonf("Unable to decode the request body: %s", err)))
|
|
return
|
|
}
|
|
|
|
c.ID = ps.ByName("id")
|
|
if err := h.updateClient(r.Context(), &c, h.r.ClientValidator().Validate); err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
h.r.Writer().Write(w, r, &c)
|
|
}
|
|
|
|
func (h *Handler) updateClient(ctx context.Context, c *Client, validator func(context.Context, *Client) error) error {
|
|
var secret string
|
|
if len(c.Secret) > 0 {
|
|
secret = c.Secret
|
|
}
|
|
|
|
if err := validator(ctx, c); err != nil {
|
|
return err
|
|
}
|
|
|
|
c.UpdatedAt = time.Now().UTC().Round(time.Second)
|
|
if err := h.r.ClientManager().UpdateClient(ctx, c); err != nil {
|
|
return err
|
|
}
|
|
c.Secret = secret
|
|
return nil
|
|
}
|
|
|
|
// Set Dynamic Client Parameters
|
|
//
|
|
// swagger:parameters setOidcDynamicClient
|
|
//
|
|
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
|
|
type setOidcDynamicClient struct {
|
|
// OAuth 2.0 Client ID
|
|
//
|
|
// in: path
|
|
// required: true
|
|
ID string `json:"id"`
|
|
|
|
// OAuth 2.0 Client Request Body
|
|
//
|
|
// in: body
|
|
// required: true
|
|
Body Client
|
|
}
|
|
|
|
// swagger:route PUT /oauth2/register/{id} oidc setOidcDynamicClient
|
|
//
|
|
// # Set OAuth2 Client using OpenID Dynamic Client Registration
|
|
//
|
|
// This endpoint behaves like the administrative counterpart (`setOAuth2Client`) but is capable of facing the
|
|
// public internet directly to be used by third parties. It implements the OpenID Connect
|
|
// Dynamic Client Registration Protocol.
|
|
//
|
|
// This feature is disabled per default. It can be enabled by a system administrator.
|
|
//
|
|
// If you pass `client_secret` the secret is used, otherwise the existing secret is used. If set, the secret is echoed in the response.
|
|
// It is not possible to retrieve it later on.
|
|
//
|
|
// To use this endpoint, you will need to present the client's authentication credentials. If the OAuth2 Client
|
|
// uses the Token Endpoint Authentication Method `client_secret_post`, you need to present the client secret in the URL query.
|
|
// If it uses `client_secret_basic`, present the Client ID and the Client Secret in the Authorization header.
|
|
//
|
|
// OAuth 2.0 clients are used to perform OAuth 2.0 and OpenID Connect flows. Usually, OAuth 2.0 clients are
|
|
// generated for applications which want to consume your OAuth 2.0 or OpenID Connect capabilities.
|
|
//
|
|
// Consumes:
|
|
// - application/json
|
|
//
|
|
// Produces:
|
|
// - application/json
|
|
//
|
|
// Security:
|
|
// bearer:
|
|
//
|
|
// Schemes: http, https
|
|
//
|
|
// Responses:
|
|
// 200: oAuth2Client
|
|
// 404: errorOAuth2NotFound
|
|
// default: errorOAuth2Default
|
|
func (h *Handler) setOidcDynamicClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
if err := h.requireDynamicAuth(r); err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
client, err := h.ValidDynamicAuth(r, ps)
|
|
if err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
var c Client
|
|
if err := json.NewDecoder(r.Body).Decode(&c); err != nil {
|
|
h.r.Writer().WriteError(w, r, errorsx.WithStack(herodot.ErrBadRequest.WithReasonf("Unable to decode the request body. Is it valid JSON?").WithDebug(err.Error())))
|
|
return
|
|
}
|
|
|
|
if c.Secret != "" {
|
|
h.r.Writer().WriteError(w, r, errorsx.WithStack(herodot.ErrForbidden.WithReasonf("It is not allowed to choose your own OAuth2 Client secret.")))
|
|
return
|
|
}
|
|
|
|
// Regenerate the registration access token
|
|
token, signature, err := h.r.OAuth2HMACStrategy().GenerateAccessToken(r.Context(), nil)
|
|
if err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
c.RegistrationAccessToken = token
|
|
c.RegistrationAccessTokenSignature = signature
|
|
|
|
c.ID = client.GetID()
|
|
if err := h.updateClient(r.Context(), &c, h.r.ClientValidator().ValidateDynamicRegistration); err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
h.r.Writer().Write(w, r, &c)
|
|
}
|
|
|
|
// Patch OAuth 2.0 Client Parameters
|
|
//
|
|
// swagger:parameters patchOAuth2Client
|
|
//
|
|
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
|
|
type patchOAuth2Client struct {
|
|
// The id of the OAuth 2.0 Client.
|
|
//
|
|
// in: path
|
|
// required: true
|
|
ID string `json:"id"`
|
|
|
|
// OAuth 2.0 Client JSON Patch Body
|
|
//
|
|
// in: body
|
|
// required: true
|
|
Body openapix.JSONPatchDocument
|
|
}
|
|
|
|
// swagger:route PATCH /admin/clients/{id} oAuth2 patchOAuth2Client
|
|
//
|
|
// # Patch OAuth 2.0 Client
|
|
//
|
|
// Patch an existing OAuth 2.0 Client using JSON Patch. If you pass `client_secret`
|
|
// the secret will be updated and returned via the API. This is the
|
|
// only time you will be able to retrieve the client secret, so write it down and keep it safe.
|
|
//
|
|
// OAuth 2.0 clients are used to perform OAuth 2.0 and OpenID Connect flows. Usually, OAuth 2.0 clients are
|
|
// generated for applications which want to consume your OAuth 2.0 or OpenID Connect capabilities.
|
|
//
|
|
// Consumes:
|
|
// - application/json
|
|
//
|
|
// Produces:
|
|
// - application/json
|
|
//
|
|
// Schemes: http, https
|
|
//
|
|
// Responses:
|
|
// 200: oAuth2Client
|
|
// 404: errorOAuth2NotFound
|
|
// default: errorOAuth2Default
|
|
func (h *Handler) patchOAuth2Client(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
patchJSON, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
id := ps.ByName("id")
|
|
c, err := h.r.ClientManager().GetConcreteClient(r.Context(), id)
|
|
if err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
oldSecret := c.Secret
|
|
|
|
if err := jsonx.ApplyJSONPatch(patchJSON, c, "/id"); err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
// fix for #2869
|
|
// GetConcreteClient returns a client with the hashed secret, however updateClient expects
|
|
// an empty secret if the secret hasn't changed. As such we need to check if the patch has
|
|
// updated the secret or not
|
|
if oldSecret == c.Secret {
|
|
c.Secret = ""
|
|
}
|
|
|
|
if err := h.updateClient(r.Context(), c, h.r.ClientValidator().Validate); err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
h.r.Writer().Write(w, r, c)
|
|
}
|
|
|
|
// Paginated OAuth2 Client List Response
|
|
//
|
|
// swagger:response listOAuth2Clients
|
|
//
|
|
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
|
|
type listOAuth2ClientsResponse struct {
|
|
tokenpagination.ResponseHeaders
|
|
|
|
// List of OAuth 2.0 Clients
|
|
//
|
|
// in:body
|
|
Body []Client
|
|
}
|
|
|
|
// Paginated OAuth2 Client List Parameters
|
|
//
|
|
// swagger:parameters listOAuth2Clients
|
|
//
|
|
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
|
|
type listOAuth2ClientsParameters struct {
|
|
tokenpagination.RequestParameters
|
|
|
|
// The name of the clients to filter by.
|
|
//
|
|
// in: query
|
|
Name string `json:"client_name"`
|
|
|
|
// The owner of the clients to filter by.
|
|
//
|
|
// in: query
|
|
Owner string `json:"owner"`
|
|
}
|
|
|
|
// swagger:route GET /admin/clients oAuth2 listOAuth2Clients
|
|
//
|
|
// # List OAuth 2.0 Clients
|
|
//
|
|
// This endpoint lists all clients in the database, and never returns client secrets.
|
|
// As a default it lists the first 100 clients.
|
|
//
|
|
// Consumes:
|
|
// - application/json
|
|
//
|
|
// Produces:
|
|
// - application/json
|
|
//
|
|
// Schemes: http, https
|
|
//
|
|
// Responses:
|
|
// 200: listOAuth2Clients
|
|
// default: errorOAuth2Default
|
|
func (h *Handler) listOAuth2Clients(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
page, itemsPerPage := x.ParsePagination(r)
|
|
filters := Filter{
|
|
Limit: itemsPerPage,
|
|
Offset: page * itemsPerPage,
|
|
Name: r.URL.Query().Get("client_name"),
|
|
Owner: r.URL.Query().Get("owner"),
|
|
}
|
|
|
|
c, err := h.r.ClientManager().GetClients(r.Context(), filters)
|
|
if err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
if c == nil {
|
|
c = []Client{}
|
|
}
|
|
|
|
for k := range c {
|
|
c[k].Secret = ""
|
|
}
|
|
|
|
total, err := h.r.ClientManager().CountClients(r.Context())
|
|
if err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
x.PaginationHeader(w, r.URL, int64(total), page, itemsPerPage)
|
|
h.r.Writer().Write(w, r, c)
|
|
}
|
|
|
|
// Get OAuth2 Client Parameters
|
|
//
|
|
// swagger:parameters getOAuth2Client
|
|
//
|
|
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
|
|
type adminGetOAuth2Client struct {
|
|
// The id of the OAuth 2.0 Client.
|
|
//
|
|
// in: path
|
|
// required: true
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
// swagger:route GET /admin/clients/{id} oAuth2 getOAuth2Client
|
|
//
|
|
// # Get an OAuth 2.0 Client
|
|
//
|
|
// Get an OAuth 2.0 client by its ID. This endpoint never returns the client secret.
|
|
//
|
|
// OAuth 2.0 clients are used to perform OAuth 2.0 and OpenID Connect flows. Usually, OAuth 2.0 clients are
|
|
// generated for applications which want to consume your OAuth 2.0 or OpenID Connect capabilities.
|
|
//
|
|
// Consumes:
|
|
// - application/json
|
|
//
|
|
// Produces:
|
|
// - application/json
|
|
//
|
|
// Schemes: http, https
|
|
//
|
|
// Responses:
|
|
// 200: oAuth2Client
|
|
// default: errorOAuth2Default
|
|
func (h *Handler) Get(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
var id = ps.ByName("id")
|
|
c, err := h.r.ClientManager().GetConcreteClient(r.Context(), id)
|
|
if err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
c.Secret = ""
|
|
h.r.Writer().Write(w, r, c)
|
|
}
|
|
|
|
// Get OpenID Connect Dynamic Client Parameters
|
|
//
|
|
// swagger:parameters getOidcDynamicClient
|
|
//
|
|
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
|
|
type getOidcDynamicClient struct {
|
|
// The id of the OAuth 2.0 Client.
|
|
//
|
|
// in: path
|
|
// required: true
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
// swagger:route GET /oauth2/register/{id} oidc getOidcDynamicClient
|
|
//
|
|
// # Get OAuth2 Client using OpenID Dynamic Client Registration
|
|
//
|
|
// This endpoint behaves like the administrative counterpart (`getOAuth2Client`) but is capable of facing the
|
|
// public internet directly and can be used in self-service. It implements the OpenID Connect
|
|
// Dynamic Client Registration Protocol.
|
|
//
|
|
// To use this endpoint, you will need to present the client's authentication credentials. If the OAuth2 Client
|
|
// uses the Token Endpoint Authentication Method `client_secret_post`, you need to present the client secret in the URL query.
|
|
// If it uses `client_secret_basic`, present the Client ID and the Client Secret in the Authorization header.
|
|
//
|
|
// Consumes:
|
|
// - application/json
|
|
//
|
|
// Produces:
|
|
// - application/json
|
|
//
|
|
// Schemes: http, https
|
|
//
|
|
// Security:
|
|
// bearer:
|
|
//
|
|
// Responses:
|
|
// 200: oAuth2Client
|
|
// default: errorOAuth2Default
|
|
func (h *Handler) getOidcDynamicClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
if err := h.requireDynamicAuth(r); err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
client, err := h.ValidDynamicAuth(r, ps)
|
|
if err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
c, err := h.r.ClientManager().GetConcreteClient(r.Context(), client.GetID())
|
|
if err != nil {
|
|
err = herodot.ErrUnauthorized.WithReason("The requested OAuth 2.0 client does not exist or you did not provide the necessary credentials")
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
c.Secret = ""
|
|
c.Metadata = nil
|
|
h.r.Writer().Write(w, r, c)
|
|
}
|
|
|
|
// Delete OAuth2 Client Parameters
|
|
//
|
|
// swagger:parameters deleteOAuth2Client
|
|
//
|
|
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
|
|
type deleteOAuth2Client struct {
|
|
// The id of the OAuth 2.0 Client.
|
|
//
|
|
// in: path
|
|
// required: true
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
// swagger:route DELETE /admin/clients/{id} oAuth2 deleteOAuth2Client
|
|
//
|
|
// # Delete OAuth 2.0 Client
|
|
//
|
|
// Delete an existing OAuth 2.0 Client by its ID.
|
|
//
|
|
// OAuth 2.0 clients are used to perform OAuth 2.0 and OpenID Connect flows. Usually, OAuth 2.0 clients are
|
|
// generated for applications which want to consume your OAuth 2.0 or OpenID Connect capabilities.
|
|
//
|
|
// Make sure that this endpoint is well protected and only callable by first-party components.
|
|
//
|
|
// Consumes:
|
|
// - application/json
|
|
//
|
|
// Produces:
|
|
// - application/json
|
|
//
|
|
// Schemes: http, https
|
|
//
|
|
// Responses:
|
|
// 204: emptyResponse
|
|
// default: genericError
|
|
func (h *Handler) deleteOAuth2Client(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
var id = ps.ByName("id")
|
|
if err := h.r.ClientManager().DeleteClient(r.Context(), id); err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
// Set OAuth 2.0 Client Token Lifespans
|
|
//
|
|
// swagger:parameters setOAuth2ClientLifespans
|
|
//
|
|
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
|
|
type setOAuth2ClientLifespans struct {
|
|
// OAuth 2.0 Client ID
|
|
//
|
|
// in: path
|
|
// required: true
|
|
ID string `json:"id"`
|
|
|
|
// in: body
|
|
Body Lifespans
|
|
}
|
|
|
|
// swagger:route PUT /admin/clients/{id}/lifespans oAuth2 setOAuth2ClientLifespans
|
|
//
|
|
// # Set OAuth2 Client Token Lifespans
|
|
//
|
|
// Set lifespans of different token types issued for this OAuth 2.0 client. Does not modify other fields.
|
|
//
|
|
// Consumes:
|
|
// - application/json
|
|
//
|
|
// Schemes: http, https
|
|
//
|
|
// Responses:
|
|
// 200: oAuth2Client
|
|
// default: genericError
|
|
func (h *Handler) setOAuth2ClientLifespans(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
var id = ps.ByName("id")
|
|
c, err := h.r.ClientManager().GetConcreteClient(r.Context(), id)
|
|
if err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
var ls Lifespans
|
|
if err := json.NewDecoder(r.Body).Decode(&ls); err != nil {
|
|
h.r.Writer().WriteError(w, r, errorsx.WithStack(herodot.ErrBadRequest.WithReasonf("Unable to decode the request body: %s", err)))
|
|
return
|
|
}
|
|
|
|
c.Lifespans = ls
|
|
c.Secret = ""
|
|
|
|
if err := h.updateClient(r.Context(), c, h.r.ClientValidator().Validate); err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
h.r.Writer().Write(w, r, c)
|
|
}
|
|
|
|
// swagger:parameters deleteOidcDynamicClient
|
|
//
|
|
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
|
|
type dynamicClientRegistrationDeleteOAuth2Client struct {
|
|
// The id of the OAuth 2.0 Client.
|
|
//
|
|
// in: path
|
|
// required: true
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
// swagger:route DELETE /oauth2/register/{id} oidc deleteOidcDynamicClient
|
|
//
|
|
// # Delete OAuth 2.0 Client using the OpenID Dynamic Client Registration Management Protocol
|
|
//
|
|
// This endpoint behaves like the administrative counterpart (`deleteOAuth2Client`) but is capable of facing the
|
|
// public internet directly and can be used in self-service. It implements the OpenID Connect
|
|
// Dynamic Client Registration Protocol. This feature needs to be enabled in the configuration. This endpoint
|
|
// is disabled by default. It can be enabled by an administrator.
|
|
//
|
|
// To use this endpoint, you will need to present the client's authentication credentials. If the OAuth2 Client
|
|
// uses the Token Endpoint Authentication Method `client_secret_post`, you need to present the client secret in the URL query.
|
|
// If it uses `client_secret_basic`, present the Client ID and the Client Secret in the Authorization header.
|
|
//
|
|
// OAuth 2.0 clients are used to perform OAuth 2.0 and OpenID Connect flows. Usually, OAuth 2.0 clients are
|
|
// generated for applications which want to consume your OAuth 2.0 or OpenID Connect capabilities.
|
|
//
|
|
// Produces:
|
|
// - application/json
|
|
//
|
|
// Schemes: http, https
|
|
//
|
|
// Security:
|
|
// bearer:
|
|
//
|
|
// Responses:
|
|
// 204: emptyResponse
|
|
// default: genericError
|
|
func (h *Handler) deleteOidcDynamicClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
if err := h.requireDynamicAuth(r); err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
client, err := h.ValidDynamicAuth(r, ps)
|
|
if err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
if err := h.r.ClientManager().DeleteClient(r.Context(), client.GetID()); err != nil {
|
|
h.r.Writer().WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func (h *Handler) ValidDynamicAuth(r *http.Request, ps httprouter.Params) (fosite.Client, error) {
|
|
c, err := h.r.ClientManager().GetConcreteClient(r.Context(), ps.ByName("id"))
|
|
if err != nil {
|
|
return nil, herodot.ErrUnauthorized.
|
|
WithTrace(err).
|
|
WithReason("The requested OAuth 2.0 client does not exist or you provided incorrect credentials.").WithDebug(err.Error())
|
|
}
|
|
|
|
if len(c.RegistrationAccessTokenSignature) == 0 {
|
|
return nil, errors.WithStack(herodot.ErrUnauthorized.
|
|
WithReason("The requested OAuth 2.0 client does not exist or you provided incorrect credentials.").WithDebug("The OAuth2 Client does not have a registration access token."))
|
|
}
|
|
|
|
token := strings.TrimPrefix(fosite.AccessTokenFromRequest(r), "ory_at_")
|
|
if err := h.r.OAuth2HMACStrategy().ValidateAccessToken(
|
|
r.Context(),
|
|
// The strategy checks the expiry time of the token. Registration tokens don't expire (we don't have a way of
|
|
// rotating them) so we set the expiry time to a time in the future.
|
|
&fosite.Request{
|
|
Session: &fosite.DefaultSession{
|
|
ExpiresAt: map[fosite.TokenType]time.Time{
|
|
fosite.AccessToken: time.Now().Add(time.Hour),
|
|
},
|
|
},
|
|
RequestedAt: time.Now(),
|
|
},
|
|
token,
|
|
); err != nil {
|
|
return nil, herodot.ErrUnauthorized.
|
|
WithTrace(err).
|
|
WithReason("The requested OAuth 2.0 client does not exist or you provided incorrect credentials.").WithDebug(err.Error())
|
|
}
|
|
|
|
signature := h.r.OAuth2EnigmaStrategy().Signature(token)
|
|
if subtle.ConstantTimeCompare([]byte(c.RegistrationAccessTokenSignature), []byte(signature)) == 0 {
|
|
return nil, errors.WithStack(herodot.ErrUnauthorized.
|
|
WithReason("The requested OAuth 2.0 client does not exist or you provided incorrect credentials.").WithDebug("Registration access tokens do not match."))
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func (h *Handler) requireDynamicAuth(r *http.Request) *herodot.DefaultError {
|
|
if !h.r.Config().PublicAllowDynamicRegistration(r.Context()) {
|
|
return herodot.ErrNotFound.WithReason("Dynamic registration is not enabled.")
|
|
}
|
|
return nil
|
|
}
|