mirror of https://github.com/harness/drone.git
222 lines
5.9 KiB
Go
222 lines
5.9 KiB
Go
// Copyright 2023 Harness, Inc.
|
|
//
|
|
// 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 oci
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
apiauth "github.com/harness/gitness/app/api/auth"
|
|
"github.com/harness/gitness/app/api/render"
|
|
"github.com/harness/gitness/app/api/request"
|
|
"github.com/harness/gitness/app/auth"
|
|
"github.com/harness/gitness/app/jwt"
|
|
"github.com/harness/gitness/app/paths"
|
|
"github.com/harness/gitness/app/token"
|
|
"github.com/harness/gitness/types"
|
|
"github.com/harness/gitness/types/enum"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
type TokenResponseOCI struct {
|
|
Token string `json:"token"`
|
|
}
|
|
|
|
func (h *Handler) GetToken(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
session, ok := request.AuthSessionFrom(ctx)
|
|
if !ok || session.Principal == auth.AnonymousPrincipal {
|
|
returnForbiddenResponse(w, fmt.Errorf("no auth session found"))
|
|
return
|
|
}
|
|
|
|
if tokenMetadata, okt := session.Metadata.(*auth.TokenMetadata); okt &&
|
|
tokenMetadata.TokenType != enum.TokenTypePAT {
|
|
returnForbiddenResponse(w, fmt.Errorf("only personal access token allowed"))
|
|
return
|
|
}
|
|
|
|
user, err := h.UserCtrl.FindNoAuth(ctx, session.Principal.UID)
|
|
if err != nil {
|
|
returnForbiddenResponse(w, err)
|
|
return
|
|
}
|
|
|
|
requestedOciAccess := GetRequestedResourceActions(getScopes(r.URL))
|
|
var accessPermissionsList = []jwt.AccessPermissions{}
|
|
for _, ra := range requestedOciAccess {
|
|
space, err := h.getSpace(ctx, ra.Name)
|
|
if err != nil {
|
|
render.TranslatedUserError(ctx, w, err)
|
|
log.Ctx(ctx).Warn().Msgf("failed to find space by ref: %v", err)
|
|
continue
|
|
}
|
|
|
|
accessPermissionsList = h.getAccessPermissionList(ctx, space, ra, session, accessPermissionsList)
|
|
}
|
|
|
|
subClaimsAccessPermissions := &jwt.SubClaimsAccessPermissions{
|
|
Source: jwt.OciSource,
|
|
Permissions: accessPermissionsList,
|
|
}
|
|
|
|
jwtToken, err := h.getTokenDetails(user, subClaimsAccessPermissions)
|
|
if err != nil {
|
|
returnForbiddenResponse(w, err)
|
|
return
|
|
}
|
|
if jwtToken != "" {
|
|
w.WriteHeader(http.StatusOK)
|
|
enc := json.NewEncoder(w)
|
|
if err := enc.Encode(
|
|
TokenResponseOCI{
|
|
Token: jwtToken,
|
|
},
|
|
); err != nil {
|
|
log.Error().Msgf("failed to write token response: %v", err)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
func (h *Handler) getSpace(ctx context.Context, name string) (*types.Space, error) {
|
|
spaceRef, _, _ := paths.DisectRoot(name)
|
|
space, err := h.SpaceStore.FindByRef(ctx, spaceRef)
|
|
return space, err
|
|
}
|
|
|
|
func (h *Handler) getAccessPermissionList(
|
|
ctx context.Context, space *types.Space, ra *ResourceActions, session *auth.Session,
|
|
accessPermissionsList []jwt.AccessPermissions,
|
|
) []jwt.AccessPermissions {
|
|
accessPermissions := &jwt.AccessPermissions{SpaceID: space.ID, Permissions: []enum.Permission{}}
|
|
|
|
for _, a := range ra.Actions {
|
|
permission, err := getPermissionFromAction(ctx, a)
|
|
if err != nil {
|
|
log.Ctx(ctx).Warn().Msgf("failed to get permission from action: %v", err)
|
|
continue
|
|
}
|
|
scopeErr := apiauth.CheckSpaceScope(
|
|
ctx,
|
|
h.Authorizer,
|
|
session,
|
|
space,
|
|
enum.ResourceTypeRegistry,
|
|
permission,
|
|
)
|
|
if scopeErr != nil {
|
|
log.Ctx(ctx).Warn().Msgf("failed to check space scope: %v", scopeErr)
|
|
continue
|
|
}
|
|
accessPermissions.Permissions = append(accessPermissions.Permissions, permission)
|
|
}
|
|
accessPermissionsList = append(accessPermissionsList, *accessPermissions)
|
|
return accessPermissionsList
|
|
}
|
|
|
|
func getPermissionFromAction(ctx context.Context, action string) (enum.Permission, error) {
|
|
switch action {
|
|
case "pull":
|
|
return enum.PermissionArtifactsDownload, nil
|
|
case "push":
|
|
return enum.PermissionArtifactsUpload, nil
|
|
case "delete":
|
|
return enum.PermissionArtifactsDelete, nil
|
|
default:
|
|
err := fmt.Errorf("unknown action: %s", action)
|
|
log.Ctx(ctx).Err(err).Msgf("Failed to get permission from action: %v", err)
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
func returnForbiddenResponse(w http.ResponseWriter, err error) {
|
|
w.WriteHeader(http.StatusForbidden)
|
|
_, err2 := w.Write([]byte(fmt.Sprintf("requested access to the resource is denied: %v", err)))
|
|
if err2 != nil {
|
|
log.Error().Msgf("failed to write token response: %v", err2)
|
|
}
|
|
}
|
|
|
|
/*
|
|
* getTokenDetails attempts to get token details.
|
|
*/
|
|
func (h *Handler) getTokenDetails(
|
|
user *types.User,
|
|
accessPermissions *jwt.SubClaimsAccessPermissions,
|
|
) (string, error) {
|
|
return token.CreateUserWithAccessPermissions(user, accessPermissions)
|
|
}
|
|
|
|
// GetRequestedResourceActions ...
|
|
func GetRequestedResourceActions(scopes []string) []*ResourceActions {
|
|
var res []*ResourceActions
|
|
for _, s := range scopes {
|
|
if s == "" {
|
|
continue
|
|
}
|
|
items := strings.Split(s, ":")
|
|
length := len(items)
|
|
|
|
var resourceType string
|
|
var resourceName string
|
|
actions := make([]string, 0)
|
|
|
|
switch length {
|
|
case 1:
|
|
resourceType = items[0]
|
|
case 2:
|
|
resourceType = items[0]
|
|
resourceName = items[1]
|
|
default:
|
|
resourceType = items[0]
|
|
resourceName = strings.Join(items[1:length-1], ":")
|
|
if len(items[length-1]) > 0 {
|
|
actions = strings.Split(items[length-1], ",")
|
|
}
|
|
}
|
|
|
|
res = append(
|
|
res, &ResourceActions{
|
|
Type: resourceType,
|
|
Name: resourceName,
|
|
Actions: actions,
|
|
},
|
|
)
|
|
}
|
|
return res
|
|
}
|
|
|
|
func getScopes(u *url.URL) []string {
|
|
var sector string
|
|
var result []string
|
|
for _, sector = range u.Query()["scope"] {
|
|
result = append(result, strings.Split(sector, " ")...)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// ResourceActions stores allowed actions on a resource.
|
|
type ResourceActions struct {
|
|
Type string `json:"type"`
|
|
Name string `json:"name"`
|
|
Actions []string `json:"actions"`
|
|
}
|