From 88658254d473a3f6e281e7b7c33ed04740f9bdde Mon Sep 17 00:00:00 2001 From: Simon Murray Date: Fri, 5 Apr 2024 12:45:47 +0100 Subject: [PATCH] Add Roles API (#30) To drive the UI. --- charts/identity/Chart.yaml | 4 +- charts/identity/values.yaml | 1 - openapi/server.spec.yaml | 31 ++++ pkg/generated/client.go | 133 +++++++++++++++++ pkg/generated/router.go | 34 +++++ pkg/generated/schema.go | 276 ++++++++++++++++++------------------ pkg/generated/types.go | 3 + pkg/handler/handler.go | 17 +++ pkg/handler/roles/client.go | 65 +++++++++ 9 files changed, 424 insertions(+), 140 deletions(-) create mode 100644 pkg/handler/roles/client.go diff --git a/charts/identity/Chart.yaml b/charts/identity/Chart.yaml index 7374bff2..f5b816a6 100644 --- a/charts/identity/Chart.yaml +++ b/charts/identity/Chart.yaml @@ -4,7 +4,7 @@ description: A Helm chart for deploying Unikorn's IdP type: application -version: v0.1.23 -appVersion: v0.1.23 +version: v0.1.24 +appVersion: v0.1.24 icon: https://raw.githubusercontent.com/unikorn-cloud/assets/main/images/logos/dark-on-light/icon.png diff --git a/charts/identity/values.yaml b/charts/identity/values.yaml index 64339bcd..33f4bac8 100644 --- a/charts/identity/values.yaml +++ b/charts/identity/values.yaml @@ -61,7 +61,6 @@ roles: default: isDefault: true scopes: - roles: [read] organizations: [read] reader: scopes: diff --git a/openapi/server.spec.yaml b/openapi/server.spec.yaml index d9ec0ce7..4d119be0 100644 --- a/openapi/server.spec.yaml +++ b/openapi/server.spec.yaml @@ -192,6 +192,26 @@ paths: $ref: '#/components/responses/unauthorizedResponse' '500': $ref: '#/components/responses/internalServerErrorResponse' + /api/v1/organizations/{organization}/roles: + description: |- + Allows management of roles that define access control permissions for + users that are linked to them via a group. + parameters: + - $ref: '#/components/parameters/organizationParameter' + get: + description: |- + Returns roles that can be used by the organization. + security: + - oauth2Authentication: [] + responses: + '200': + $ref: '#/components/responses/rolesResponse' + '401': + $ref: '#/components/responses/unauthorizedResponse' + '403': + $ref: '#/components/responses/forbiddenResponse' + '500': + $ref: '#/components/responses/internalServerErrorResponse' /api/v1/organizations/{organization}/oauth2/providers: description: |- Allows management of oauth2 providers. The identity service is typically @@ -927,6 +947,17 @@ components: scopes: organizations: - read + rolesResponse: + description: |- + A set of roles within the organization. + content: + application/json: + schema: + $ref: '#/components/schemas/roleList' + example: + - admin + - user + - reader groupResponse: description: |- A group in the organization. diff --git a/pkg/generated/client.go b/pkg/generated/client.go index 0042d4f2..75b0d877 100644 --- a/pkg/generated/client.go +++ b/pkg/generated/client.go @@ -130,6 +130,9 @@ type ClientInterface interface { // GetApiV1OrganizationsOrganizationOauth2Providers request GetApiV1OrganizationsOrganizationOauth2Providers(ctx context.Context, organization OrganizationParameter, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetApiV1OrganizationsOrganizationRoles request + GetApiV1OrganizationsOrganizationRoles(ctx context.Context, organization OrganizationParameter, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetOauth2V2Authorization request GetOauth2V2Authorization(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -333,6 +336,18 @@ func (c *Client) GetApiV1OrganizationsOrganizationOauth2Providers(ctx context.Co return c.Client.Do(req) } +func (c *Client) GetApiV1OrganizationsOrganizationRoles(ctx context.Context, organization OrganizationParameter, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetApiV1OrganizationsOrganizationRolesRequest(c.Server, organization) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) GetOauth2V2Authorization(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetOauth2V2AuthorizationRequest(c.Server) if err != nil { @@ -855,6 +870,40 @@ func NewGetApiV1OrganizationsOrganizationOauth2ProvidersRequest(server string, o return req, nil } +// NewGetApiV1OrganizationsOrganizationRolesRequest generates requests for GetApiV1OrganizationsOrganizationRoles +func NewGetApiV1OrganizationsOrganizationRolesRequest(server string, organization OrganizationParameter) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "organization", runtime.ParamLocationPath, organization) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/v1/organizations/%s/roles", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewGetOauth2V2AuthorizationRequest generates requests for GetOauth2V2Authorization func NewGetOauth2V2AuthorizationRequest(server string) (*http.Request, error) { var err error @@ -1127,6 +1176,9 @@ type ClientWithResponsesInterface interface { // GetApiV1OrganizationsOrganizationOauth2Providers request GetApiV1OrganizationsOrganizationOauth2ProvidersWithResponse(ctx context.Context, organization OrganizationParameter, reqEditors ...RequestEditorFn) (*GetApiV1OrganizationsOrganizationOauth2ProvidersResponse, error) + // GetApiV1OrganizationsOrganizationRoles request + GetApiV1OrganizationsOrganizationRolesWithResponse(ctx context.Context, organization OrganizationParameter, reqEditors ...RequestEditorFn) (*GetApiV1OrganizationsOrganizationRolesResponse, error) + // GetOauth2V2Authorization request GetOauth2V2AuthorizationWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetOauth2V2AuthorizationResponse, error) @@ -1417,6 +1469,31 @@ func (r GetApiV1OrganizationsOrganizationOauth2ProvidersResponse) StatusCode() i return 0 } +type GetApiV1OrganizationsOrganizationRolesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *RoleList + JSON401 *Oauth2Error + JSON403 *Oauth2Error + JSON500 *Oauth2Error +} + +// Status returns HTTPResponse.Status +func (r GetApiV1OrganizationsOrganizationRolesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetApiV1OrganizationsOrganizationRolesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type GetOauth2V2AuthorizationResponse struct { Body []byte HTTPResponse *http.Response @@ -1682,6 +1759,15 @@ func (c *ClientWithResponses) GetApiV1OrganizationsOrganizationOauth2ProvidersWi return ParseGetApiV1OrganizationsOrganizationOauth2ProvidersResponse(rsp) } +// GetApiV1OrganizationsOrganizationRolesWithResponse request returning *GetApiV1OrganizationsOrganizationRolesResponse +func (c *ClientWithResponses) GetApiV1OrganizationsOrganizationRolesWithResponse(ctx context.Context, organization OrganizationParameter, reqEditors ...RequestEditorFn) (*GetApiV1OrganizationsOrganizationRolesResponse, error) { + rsp, err := c.GetApiV1OrganizationsOrganizationRoles(ctx, organization, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetApiV1OrganizationsOrganizationRolesResponse(rsp) +} + // GetOauth2V2AuthorizationWithResponse request returning *GetOauth2V2AuthorizationResponse func (c *ClientWithResponses) GetOauth2V2AuthorizationWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetOauth2V2AuthorizationResponse, error) { rsp, err := c.GetOauth2V2Authorization(ctx, reqEditors...) @@ -2213,6 +2299,53 @@ func ParseGetApiV1OrganizationsOrganizationOauth2ProvidersResponse(rsp *http.Res return response, nil } +// ParseGetApiV1OrganizationsOrganizationRolesResponse parses an HTTP response from a GetApiV1OrganizationsOrganizationRolesWithResponse call +func ParseGetApiV1OrganizationsOrganizationRolesResponse(rsp *http.Response) (*GetApiV1OrganizationsOrganizationRolesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetApiV1OrganizationsOrganizationRolesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest RoleList + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest Oauth2Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest Oauth2Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Oauth2Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseGetOauth2V2AuthorizationResponse parses an HTTP response from a GetOauth2V2AuthorizationWithResponse call func ParseGetOauth2V2AuthorizationResponse(rsp *http.Response) (*GetOauth2V2AuthorizationResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/pkg/generated/router.go b/pkg/generated/router.go index f4becf02..923c95fd 100644 --- a/pkg/generated/router.go +++ b/pkg/generated/router.go @@ -48,6 +48,9 @@ type ServerInterface interface { // (GET /api/v1/organizations/{organization}/oauth2/providers) GetApiV1OrganizationsOrganizationOauth2Providers(w http.ResponseWriter, r *http.Request, organization OrganizationParameter) + // (GET /api/v1/organizations/{organization}/roles) + GetApiV1OrganizationsOrganizationRoles(w http.ResponseWriter, r *http.Request, organization OrganizationParameter) + // (GET /oauth2/v2/authorization) GetOauth2V2Authorization(w http.ResponseWriter, r *http.Request) @@ -376,6 +379,34 @@ func (siw *ServerInterfaceWrapper) GetApiV1OrganizationsOrganizationOauth2Provid handler.ServeHTTP(w, r.WithContext(ctx)) } +// GetApiV1OrganizationsOrganizationRoles operation middleware +func (siw *ServerInterfaceWrapper) GetApiV1OrganizationsOrganizationRoles(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "organization" ------------- + var organization OrganizationParameter + + err = runtime.BindStyledParameterWithLocation("simple", false, "organization", runtime.ParamLocationPath, chi.URLParam(r, "organization"), &organization) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "organization", Err: err}) + return + } + + ctx = context.WithValue(ctx, Oauth2AuthenticationScopes, []string{""}) + + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetApiV1OrganizationsOrganizationRoles(w, r, organization) + }) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // GetOauth2V2Authorization operation middleware func (siw *ServerInterfaceWrapper) GetOauth2V2Authorization(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -614,6 +645,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/api/v1/organizations/{organization}/oauth2/providers", wrapper.GetApiV1OrganizationsOrganizationOauth2Providers) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/v1/organizations/{organization}/roles", wrapper.GetApiV1OrganizationsOrganizationRoles) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/oauth2/v2/authorization", wrapper.GetOauth2V2Authorization) }) diff --git a/pkg/generated/schema.go b/pkg/generated/schema.go index 215c6a1e..338b51e1 100644 --- a/pkg/generated/schema.go +++ b/pkg/generated/schema.go @@ -18,143 +18,145 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "k/hlZP3+/n8CAAD//7L+R/bwfgAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/pkg/generated/types.go b/pkg/generated/types.go index 28e94895..30ac9d71 100644 --- a/pkg/generated/types.go +++ b/pkg/generated/types.go @@ -363,6 +363,9 @@ type OpenidConfigurationResponse = OpenidConfiguration // OrganizationsResponse A list of organizations. type OrganizationsResponse = Organizations +// RolesResponse A list of roles. +type RolesResponse = RoleList + // TokenResponse Oauth2 token result. type TokenResponse = Token diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go index 30391051..e61065e1 100644 --- a/pkg/handler/handler.go +++ b/pkg/handler/handler.go @@ -31,6 +31,7 @@ import ( "github.com/unikorn-cloud/identity/pkg/handler/groups" "github.com/unikorn-cloud/identity/pkg/handler/oauth2providers" "github.com/unikorn-cloud/identity/pkg/handler/organizations" + "github.com/unikorn-cloud/identity/pkg/handler/roles" "github.com/unikorn-cloud/identity/pkg/util" "sigs.k8s.io/controller-runtime/pkg/client" @@ -181,6 +182,22 @@ func (h *Handler) GetApiV1OrganizationsOrganizationAcl(w http.ResponseWriter, r util.WriteJSONResponse(w, r, http.StatusOK, result) } +func (h *Handler) GetApiV1OrganizationsOrganizationRoles(w http.ResponseWriter, r *http.Request, organization generated.OrganizationParameter) { + if err := h.checkRBAC(r.Context(), organization, "roles", constants.Read); err != nil { + errors.HandleError(w, r, err) + return + } + + result, err := roles.New(h.client, h.namespace).List(r.Context()) + if err != nil { + errors.HandleError(w, r, err) + return + } + + h.setUncacheable(w) + util.WriteJSONResponse(w, r, http.StatusOK, result) +} + func (h *Handler) GetApiV1OrganizationsOrganizationOauth2Providers(w http.ResponseWriter, r *http.Request, organization generated.OrganizationParameter) { // TODO: separate into different scopes as per unikornv1 if err := h.checkRBAC(r.Context(), organization, "oauth2providers-public", constants.Read); err != nil { diff --git a/pkg/handler/roles/client.go b/pkg/handler/roles/client.go new file mode 100644 index 00000000..42b390e0 --- /dev/null +++ b/pkg/handler/roles/client.go @@ -0,0 +1,65 @@ +/* +Copyright 2024 the Unikorn Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package roles + +import ( + "context" + "slices" + + unikornv1 "github.com/unikorn-cloud/identity/pkg/apis/unikorn/v1alpha1" + "github.com/unikorn-cloud/identity/pkg/generated" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type Client struct { + client client.Client + namespace string +} + +func New(client client.Client, namespace string) *Client { + return &Client{ + client: client, + namespace: namespace, + } +} + +func convertList(in unikornv1.RoleList) generated.RoleList { + var out generated.RoleList + + for _, role := range in.Items { + if role.Spec.IsDefault { + continue + } + + out = append(out, role.Name) + } + + slices.Sort(out) + + return out +} + +func (c *Client) List(ctx context.Context) (generated.RoleList, error) { + var result unikornv1.RoleList + + if err := c.client.List(ctx, &result, &client.ListOptions{Namespace: c.namespace}); err != nil { + return nil, err + } + + return convertList(result), nil +}