agola/internal/services/gateway/action/user.go
Simone Gotti abab40c6f5 gateway/cmd: generate CacheGroup for user direct runs
Since the user direct runs all belong to the same run group (the user id) all
the user direct runs will share the same caches. To distinguish between the
different caches we need to use something in addition to the user id. In this
case we are usin the local repo uuid generated by the direct run start command.
2019-07-03 15:19:52 +02:00

867 lines
27 KiB
Go

// Copyright 2019 Sorint.lab
//
// 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 action
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
gitsource "agola.io/agola/internal/gitsources"
"agola.io/agola/internal/gitsources/agolagit"
"agola.io/agola/internal/services/common"
csapi "agola.io/agola/internal/services/configstore/api"
"agola.io/agola/internal/services/types"
"agola.io/agola/internal/util"
jwt "github.com/dgrijalva/jwt-go"
errors "golang.org/x/xerrors"
)
const (
expireTimeRange time.Duration = 5 * time.Minute
)
func isAccessTokenExpired(expiresAt time.Time) bool {
if expiresAt.IsZero() {
return false
}
return expiresAt.Add(-expireTimeRange).Before(time.Now())
}
func (h *ActionHandler) GetUser(ctx context.Context, userRef string) (*types.User, error) {
if !h.IsUserLoggedOrAdmin(ctx) {
return nil, errors.Errorf("user not logged in")
}
user, resp, err := h.configstoreClient.GetUser(ctx, userRef)
if err != nil {
return nil, ErrFromRemote(resp, err)
}
return user, nil
}
type GetUsersRequest struct {
Start string
Limit int
Asc bool
}
func (h *ActionHandler) GetUsers(ctx context.Context, req *GetUsersRequest) ([]*types.User, error) {
if !h.IsUserAdmin(ctx) {
return nil, errors.Errorf("user not logged in")
}
users, resp, err := h.configstoreClient.GetUsers(ctx, req.Start, req.Limit, req.Asc)
if err != nil {
return nil, ErrFromRemote(resp, err)
}
return users, nil
}
type CreateUserRequest struct {
UserName string
}
func (h *ActionHandler) CreateUser(ctx context.Context, req *CreateUserRequest) (*types.User, error) {
if !h.IsUserAdmin(ctx) {
return nil, errors.Errorf("user not admin")
}
if req.UserName == "" {
return nil, util.NewErrBadRequest(errors.Errorf("user name required"))
}
if !util.ValidateName(req.UserName) {
return nil, util.NewErrBadRequest(errors.Errorf("invalid user name %q", req.UserName))
}
creq := &csapi.CreateUserRequest{
UserName: req.UserName,
}
h.log.Infof("creating user")
u, resp, err := h.configstoreClient.CreateUser(ctx, creq)
if err != nil {
return nil, errors.Errorf("failed to create user: %w", ErrFromRemote(resp, err))
}
h.log.Infof("user %s created, ID: %s", u.Name, u.ID)
return u, nil
}
type CreateUserTokenRequest struct {
UserRef string
TokenName string
}
func (h *ActionHandler) CreateUserToken(ctx context.Context, req *CreateUserTokenRequest) (string, error) {
var userID string
userIDVal := ctx.Value("userid")
if userIDVal != nil {
userID = userIDVal.(string)
}
isAdmin := false
isAdminVal := ctx.Value("admin")
if isAdminVal != nil {
isAdmin = isAdminVal.(bool)
}
userRef := req.UserRef
user, resp, err := h.configstoreClient.GetUser(ctx, userRef)
if err != nil {
return "", errors.Errorf("failed to get user: %w", ErrFromRemote(resp, err))
}
// only admin or the same logged user can create a token
if !isAdmin && user.ID != userID {
return "", util.NewErrBadRequest(errors.Errorf("logged in user cannot create token for another user"))
}
if _, ok := user.Tokens[req.TokenName]; ok {
return "", util.NewErrBadRequest(errors.Errorf("user %q already have a token with name %q", userRef, req.TokenName))
}
h.log.Infof("creating user token")
creq := &csapi.CreateUserTokenRequest{
TokenName: req.TokenName,
}
res, resp, err := h.configstoreClient.CreateUserToken(ctx, userRef, creq)
if err != nil {
return "", errors.Errorf("failed to create user token: %w", ErrFromRemote(resp, err))
}
h.log.Infof("token %q for user %q created", req.TokenName, userRef)
return res.Token, nil
}
type CreateUserLARequest struct {
UserRef string
RemoteSourceName string
UserAccessToken string
Oauth2AccessToken string
Oauth2RefreshToken string
Oauth2AccessTokenExpiresAt time.Time
}
func (h *ActionHandler) CreateUserLA(ctx context.Context, req *CreateUserLARequest) (*types.LinkedAccount, error) {
userRef := req.UserRef
user, resp, err := h.configstoreClient.GetUser(ctx, userRef)
if err != nil {
return nil, errors.Errorf("failed to get user %q: %w", userRef, ErrFromRemote(resp, err))
}
rs, resp, err := h.configstoreClient.GetRemoteSource(ctx, req.RemoteSourceName)
if err != nil {
return nil, errors.Errorf("failed to get remote source %q: %w", req.RemoteSourceName, ErrFromRemote(resp, err))
}
var la *types.LinkedAccount
for _, v := range user.LinkedAccounts {
if v.RemoteSourceID == rs.ID {
la = v
break
}
}
if la != nil {
return nil, util.NewErrBadRequest(errors.Errorf("user %q already have a linked account for remote source %q", userRef, rs.Name))
}
accessToken, err := common.GetAccessToken(rs, req.UserAccessToken, req.Oauth2AccessToken)
if err != nil {
return nil, err
}
userSource, err := common.GetUserSource(rs, accessToken)
if err != nil {
return nil, err
}
remoteUserInfo, err := userSource.GetUserInfo()
if err != nil {
return nil, errors.Errorf("failed to retrieve remote user info for remote source %q: %w", rs.ID, err)
}
if remoteUserInfo.ID == "" {
return nil, errors.Errorf("empty remote user id for remote source %q", rs.ID)
}
creq := &csapi.CreateUserLARequest{
RemoteSourceName: req.RemoteSourceName,
RemoteUserID: remoteUserInfo.ID,
RemoteUserName: remoteUserInfo.LoginName,
UserAccessToken: req.UserAccessToken,
Oauth2AccessToken: req.Oauth2AccessToken,
Oauth2RefreshToken: req.Oauth2RefreshToken,
Oauth2AccessTokenExpiresAt: req.Oauth2AccessTokenExpiresAt,
}
h.log.Infof("creating linked account")
la, resp, err = h.configstoreClient.CreateUserLA(ctx, userRef, creq)
if err != nil {
return nil, errors.Errorf("failed to create linked account: %w", ErrFromRemote(resp, err))
}
h.log.Infof("linked account %q for user %q created", la.ID, userRef)
return la, nil
}
func (h *ActionHandler) UpdateUserLA(ctx context.Context, userRef string, la *types.LinkedAccount) error {
user, resp, err := h.configstoreClient.GetUser(ctx, userRef)
if err != nil {
return errors.Errorf("failed to get user %q: %w", userRef, ErrFromRemote(resp, err))
}
laFound := false
for _, ula := range user.LinkedAccounts {
if ula.ID == la.ID {
laFound = true
break
}
}
if !laFound {
return util.NewErrBadRequest(errors.Errorf("user %q doesn't have a linked account with id %q", userRef, la.ID))
}
creq := &csapi.UpdateUserLARequest{
RemoteUserID: la.RemoteUserID,
RemoteUserName: la.RemoteUserName,
UserAccessToken: la.UserAccessToken,
Oauth2AccessToken: la.Oauth2AccessToken,
Oauth2RefreshToken: la.Oauth2RefreshToken,
Oauth2AccessTokenExpiresAt: la.Oauth2AccessTokenExpiresAt,
}
h.log.Infof("updating user %q linked account", userRef)
la, resp, err = h.configstoreClient.UpdateUserLA(ctx, userRef, la.ID, creq)
if err != nil {
return errors.Errorf("failed to update user: %w", ErrFromRemote(resp, err))
}
h.log.Infof("linked account %q for user %q updated", la.ID, userRef)
return nil
}
// RefreshLinkedAccount refreshed the linked account oauth2 access token and update linked account in the configstore
func (h *ActionHandler) RefreshLinkedAccount(ctx context.Context, rs *types.RemoteSource, userName string, la *types.LinkedAccount) (*types.LinkedAccount, error) {
switch rs.AuthType {
case types.RemoteSourceAuthTypeOauth2:
// refresh access token if expired
if isAccessTokenExpired(la.Oauth2AccessTokenExpiresAt) {
userSource, err := common.GetOauth2Source(rs, "")
if err != nil {
return nil, err
}
token, err := userSource.RefreshOauth2Token(la.Oauth2RefreshToken)
if err != nil {
return nil, err
}
if la.Oauth2AccessToken != token.AccessToken {
la.Oauth2AccessToken = token.AccessToken
la.Oauth2RefreshToken = token.RefreshToken
la.Oauth2AccessTokenExpiresAt = token.Expiry
if err := h.UpdateUserLA(ctx, userName, la); err != nil {
return nil, errors.Errorf("failed to update linked account: %w", err)
}
}
}
}
return la, nil
}
// GetGitSource is a wrapper around common.GetGitSource that will also refresh
// the oauth2 access token and update the linked account when needed
func (h *ActionHandler) GetGitSource(ctx context.Context, rs *types.RemoteSource, userName string, la *types.LinkedAccount) (gitsource.GitSource, error) {
la, err := h.RefreshLinkedAccount(ctx, rs, userName, la)
if err != nil {
return nil, err
}
return common.GetGitSource(rs, la)
}
type RegisterUserRequest struct {
UserName string
RemoteSourceName string
UserAccessToken string
Oauth2AccessToken string
Oauth2RefreshToken string
Oauth2AccessTokenExpiresAt time.Time
}
func (h *ActionHandler) RegisterUser(ctx context.Context, req *RegisterUserRequest) (*types.User, error) {
if req.UserName == "" {
return nil, util.NewErrBadRequest(errors.Errorf("user name required"))
}
if !util.ValidateName(req.UserName) {
return nil, util.NewErrBadRequest(errors.Errorf("invalid user name %q", req.UserName))
}
rs, resp, err := h.configstoreClient.GetRemoteSource(ctx, req.RemoteSourceName)
if err != nil {
return nil, errors.Errorf("failed to get remote source %q: %w", req.RemoteSourceName, ErrFromRemote(resp, err))
}
accessToken, err := common.GetAccessToken(rs, req.UserAccessToken, req.Oauth2AccessToken)
if err != nil {
return nil, err
}
userSource, err := common.GetUserSource(rs, accessToken)
if err != nil {
return nil, err
}
remoteUserInfo, err := userSource.GetUserInfo()
if err != nil {
return nil, errors.Errorf("failed to retrieve remote user info for remote source %q: %w", rs.ID, err)
}
if remoteUserInfo.ID == "" {
return nil, errors.Errorf("empty remote user id for remote source %q", rs.ID)
}
creq := &csapi.CreateUserRequest{
UserName: req.UserName,
CreateUserLARequest: &csapi.CreateUserLARequest{
RemoteSourceName: req.RemoteSourceName,
RemoteUserID: remoteUserInfo.ID,
RemoteUserName: remoteUserInfo.LoginName,
UserAccessToken: req.UserAccessToken,
Oauth2AccessToken: req.Oauth2AccessToken,
Oauth2RefreshToken: req.Oauth2RefreshToken,
Oauth2AccessTokenExpiresAt: req.Oauth2AccessTokenExpiresAt,
},
}
h.log.Infof("creating user account")
u, resp, err := h.configstoreClient.CreateUser(ctx, creq)
if err != nil {
return nil, errors.Errorf("failed to create linked account: %w", ErrFromRemote(resp, err))
}
h.log.Infof("user %q created", req.UserName)
return u, nil
}
type LoginUserRequest struct {
RemoteSourceName string
UserAccessToken string
Oauth2AccessToken string
Oauth2RefreshToken string
Oauth2AccessTokenExpiresAt time.Time
}
type LoginUserResponse struct {
Token string
User *types.User
}
func (h *ActionHandler) LoginUser(ctx context.Context, req *LoginUserRequest) (*LoginUserResponse, error) {
rs, resp, err := h.configstoreClient.GetRemoteSource(ctx, req.RemoteSourceName)
if err != nil {
return nil, errors.Errorf("failed to get remote source %q: %w", req.RemoteSourceName, ErrFromRemote(resp, err))
}
accessToken, err := common.GetAccessToken(rs, req.UserAccessToken, req.Oauth2AccessToken)
if err != nil {
return nil, err
}
userSource, err := common.GetUserSource(rs, accessToken)
if err != nil {
return nil, err
}
remoteUserInfo, err := userSource.GetUserInfo()
if err != nil {
return nil, errors.Errorf("failed to retrieve remote user info for remote source %q: %w", rs.ID, err)
}
if remoteUserInfo.ID == "" {
return nil, errors.Errorf("empty remote user id for remote source %q", rs.ID)
}
user, resp, err := h.configstoreClient.GetUserByLinkedAccountRemoteUserAndSource(ctx, remoteUserInfo.ID, rs.ID)
if err != nil {
return nil, errors.Errorf("failed to get user for remote user id %q and remote source %q: %w", remoteUserInfo.ID, rs.ID, ErrFromRemote(resp, err))
}
var la *types.LinkedAccount
for _, v := range user.LinkedAccounts {
if v.RemoteSourceID == rs.ID {
la = v
break
}
}
if la == nil {
return nil, errors.Errorf("linked account for user %q for remote source %q doesn't exist", user.Name, rs.Name)
}
// Update oauth tokens if they have changed since the getuserinfo request may have updated them
if la.Oauth2AccessToken != req.Oauth2AccessToken ||
la.Oauth2RefreshToken != req.Oauth2RefreshToken ||
la.UserAccessToken != req.UserAccessToken {
la.Oauth2AccessToken = req.Oauth2AccessToken
la.Oauth2RefreshToken = req.Oauth2RefreshToken
la.UserAccessToken = req.UserAccessToken
creq := &csapi.UpdateUserLARequest{
RemoteUserID: la.RemoteUserID,
RemoteUserName: la.RemoteUserName,
UserAccessToken: la.UserAccessToken,
Oauth2AccessToken: la.Oauth2AccessToken,
Oauth2RefreshToken: la.Oauth2RefreshToken,
Oauth2AccessTokenExpiresAt: la.Oauth2AccessTokenExpiresAt,
}
h.log.Infof("updating user %q linked account", user.Name)
la, resp, err = h.configstoreClient.UpdateUserLA(ctx, user.Name, la.ID, creq)
if err != nil {
return nil, errors.Errorf("failed to update user: %w", ErrFromRemote(resp, err))
}
h.log.Infof("linked account %q for user %q updated", la.ID, user.Name)
}
// generate jwt token
token, err := common.GenerateLoginJWTToken(h.sd, user.ID)
if err != nil {
return nil, err
}
return &LoginUserResponse{
Token: token,
User: user,
}, nil
}
type AuthorizeRequest struct {
RemoteSourceName string
UserAccessToken string
Oauth2AccessToken string
Oauth2RefreshToken string
Oauth2AccessTokenExpiresAt time.Time
}
type AuthorizeResponse struct {
RemoteUserInfo *gitsource.UserInfo
RemoteSourceName string
}
func (h *ActionHandler) Authorize(ctx context.Context, req *AuthorizeRequest) (*AuthorizeResponse, error) {
rs, resp, err := h.configstoreClient.GetRemoteSource(ctx, req.RemoteSourceName)
if err != nil {
return nil, errors.Errorf("failed to get remote source %q: %w", req.RemoteSourceName, ErrFromRemote(resp, err))
}
accessToken, err := common.GetAccessToken(rs, req.UserAccessToken, req.Oauth2AccessToken)
if err != nil {
return nil, err
}
userSource, err := common.GetUserSource(rs, accessToken)
if err != nil {
return nil, err
}
remoteUserInfo, err := userSource.GetUserInfo()
if err != nil {
return nil, errors.Errorf("failed to retrieve remote user info for remote source %q: %w", rs.ID, err)
}
if remoteUserInfo.ID == "" {
return nil, errors.Errorf("empty remote user id for remote source %q", rs.ID)
}
return &AuthorizeResponse{
RemoteUserInfo: remoteUserInfo,
RemoteSourceName: req.RemoteSourceName,
}, nil
}
type RemoteSourceAuthResponse struct {
Oauth2Redirect string
Response interface{}
}
func (h *ActionHandler) HandleRemoteSourceAuth(ctx context.Context, remoteSourceName, loginName, loginPassword string, requestType RemoteSourceRequestType, req interface{}) (*RemoteSourceAuthResponse, error) {
rs, resp, err := h.configstoreClient.GetRemoteSource(ctx, remoteSourceName)
if err != nil {
return nil, errors.Errorf("failed to get remote source %q: %w", remoteSourceName, ErrFromRemote(resp, err))
}
switch requestType {
case RemoteSourceRequestTypeCreateUserLA:
req := req.(*CreateUserLARequest)
user, resp, err := h.configstoreClient.GetUser(ctx, req.UserRef)
if err != nil {
return nil, errors.Errorf("failed to get user %q: %w", req.UserRef, ErrFromRemote(resp, err))
}
curUserID := h.CurrentUserID(ctx)
// user must be already logged in the create a linked account and can create a
// linked account only on itself.
if user.ID != curUserID {
return nil, util.NewErrBadRequest(errors.Errorf("logged in user cannot create linked account for another user"))
}
var la *types.LinkedAccount
for _, v := range user.LinkedAccounts {
if v.RemoteSourceID == rs.ID {
la = v
break
}
}
if la != nil {
return nil, util.NewErrBadRequest(errors.Errorf("user %q already have a linked account for remote source %q", req.UserRef, rs.Name))
}
case RemoteSourceRequestTypeLoginUser:
case RemoteSourceRequestTypeAuthorize:
case RemoteSourceRequestTypeRegisterUser:
default:
return nil, errors.Errorf("unknown request type: %q", requestType)
}
switch rs.AuthType {
case types.RemoteSourceAuthTypeOauth2:
oauth2Source, err := common.GetOauth2Source(rs, "")
if err != nil {
return nil, errors.Errorf("failed to create git source: %w", err)
}
token, err := common.GenerateOauth2JWTToken(h.sd, rs.Name, string(requestType), req)
if err != nil {
return nil, err
}
redirect, err := oauth2Source.GetOauth2AuthorizationURL(h.webExposedURL+"/oauth2/callback", token)
if err != nil {
return nil, err
}
return &RemoteSourceAuthResponse{
Oauth2Redirect: redirect,
}, nil
case types.RemoteSourceAuthTypePassword:
passwordSource, err := common.GetPasswordSource(rs, "")
if err != nil {
return nil, errors.Errorf("failed to create git source: %w", err)
}
tokenName := "agola-" + h.agolaID
accessToken, err := passwordSource.LoginPassword(loginName, loginPassword, tokenName)
if err != nil {
if err == gitsource.ErrUnauthorized {
return nil, util.NewErrUnauthorized(errors.Errorf("failed to login to remotesource %q: %w", remoteSourceName, err))
}
return nil, errors.Errorf("failed to login to remote source %q with login name %q: %w", rs.Name, loginName, err)
}
requestj, err := json.Marshal(req)
if err != nil {
return nil, err
}
cres, err := h.HandleRemoteSourceAuthRequest(ctx, requestType, string(requestj), accessToken, "", "", time.Time{})
if err != nil {
return nil, err
}
return &RemoteSourceAuthResponse{
Response: cres.Response,
}, nil
default:
return nil, errors.Errorf("unknown remote source authentication type: %q", rs.AuthType)
}
}
type RemoteSourceRequestType string
const (
RemoteSourceRequestTypeCreateUserLA RemoteSourceRequestType = "createuserla"
RemoteSourceRequestTypeLoginUser RemoteSourceRequestType = "loginuser"
RemoteSourceRequestTypeAuthorize RemoteSourceRequestType = "authorize"
RemoteSourceRequestTypeRegisterUser RemoteSourceRequestType = "registeruser"
)
type RemoteSourceAuthResult struct {
RequestType RemoteSourceRequestType
Response interface{}
}
type CreateUserLAResponse struct {
LinkedAccount *types.LinkedAccount
}
func (h *ActionHandler) HandleRemoteSourceAuthRequest(ctx context.Context, requestType RemoteSourceRequestType, requestString string, userAccessToken, oauth2AccessToken, oauth2RefreshToken string, oauth2AccessTokenExpiresAt time.Time) (*RemoteSourceAuthResult, error) {
switch requestType {
case RemoteSourceRequestTypeCreateUserLA:
var req *CreateUserLARequest
if err := json.Unmarshal([]byte(requestString), &req); err != nil {
return nil, errors.Errorf("failed to unmarshal request")
}
creq := &CreateUserLARequest{
UserRef: req.UserRef,
RemoteSourceName: req.RemoteSourceName,
UserAccessToken: userAccessToken,
Oauth2AccessToken: oauth2AccessToken,
Oauth2RefreshToken: oauth2RefreshToken,
Oauth2AccessTokenExpiresAt: oauth2AccessTokenExpiresAt,
}
la, err := h.CreateUserLA(ctx, creq)
if err != nil {
return nil, err
}
return &RemoteSourceAuthResult{
RequestType: requestType,
Response: &CreateUserLAResponse{
LinkedAccount: la,
},
}, nil
case RemoteSourceRequestTypeRegisterUser:
var req *RegisterUserRequest
if err := json.Unmarshal([]byte(requestString), &req); err != nil {
return nil, errors.Errorf("failed to unmarshal request")
}
creq := &RegisterUserRequest{
UserName: req.UserName,
RemoteSourceName: req.RemoteSourceName,
UserAccessToken: userAccessToken,
Oauth2AccessToken: oauth2AccessToken,
Oauth2RefreshToken: oauth2RefreshToken,
Oauth2AccessTokenExpiresAt: oauth2AccessTokenExpiresAt,
}
cresp, err := h.RegisterUser(ctx, creq)
if err != nil {
return nil, err
}
return &RemoteSourceAuthResult{
RequestType: requestType,
Response: cresp,
}, nil
case RemoteSourceRequestTypeLoginUser:
var req *LoginUserRequest
if err := json.Unmarshal([]byte(requestString), &req); err != nil {
return nil, errors.Errorf("failed to unmarshal request")
}
creq := &LoginUserRequest{
RemoteSourceName: req.RemoteSourceName,
UserAccessToken: userAccessToken,
Oauth2AccessToken: oauth2AccessToken,
Oauth2RefreshToken: oauth2RefreshToken,
Oauth2AccessTokenExpiresAt: oauth2AccessTokenExpiresAt,
}
cresp, err := h.LoginUser(ctx, creq)
if err != nil {
return nil, err
}
return &RemoteSourceAuthResult{
RequestType: requestType,
Response: cresp,
}, nil
case RemoteSourceRequestTypeAuthorize:
var req *AuthorizeRequest
if err := json.Unmarshal([]byte(requestString), &req); err != nil {
return nil, errors.Errorf("failed to unmarshal request")
}
creq := &AuthorizeRequest{
RemoteSourceName: req.RemoteSourceName,
UserAccessToken: userAccessToken,
Oauth2AccessToken: oauth2AccessToken,
Oauth2RefreshToken: oauth2RefreshToken,
Oauth2AccessTokenExpiresAt: oauth2AccessTokenExpiresAt,
}
cresp, err := h.Authorize(ctx, creq)
if err != nil {
return nil, err
}
return &RemoteSourceAuthResult{
RequestType: requestType,
Response: cresp,
}, nil
default:
return nil, errors.Errorf("unknown request")
}
}
func (h *ActionHandler) HandleOauth2Callback(ctx context.Context, code, state string) (*RemoteSourceAuthResult, error) {
token, err := jwt.Parse(state, func(token *jwt.Token) (interface{}, error) {
if token.Method != h.sd.Method {
return nil, errors.Errorf("unexpected signing method: %v", token.Header["alg"])
}
var key interface{}
switch h.sd.Method {
case jwt.SigningMethodRS256:
key = h.sd.PrivateKey
case jwt.SigningMethodHS256:
key = h.sd.Key
default:
return nil, errors.Errorf("unsupported signing method %q", h.sd.Method.Alg())
}
return key, nil
})
if err != nil {
return nil, errors.Errorf("failed to parse jwt: %w", err)
}
if !token.Valid {
return nil, errors.Errorf("invalid token")
}
claims := token.Claims.(jwt.MapClaims)
remoteSourceName := claims["remote_source_name"].(string)
requestType := RemoteSourceRequestType(claims["request_type"].(string))
requestString := claims["request"].(string)
rs, resp, err := h.configstoreClient.GetRemoteSource(ctx, remoteSourceName)
if err != nil {
return nil, errors.Errorf("failed to get remote source %q: %w", remoteSourceName, ErrFromRemote(resp, err))
}
oauth2Source, err := common.GetOauth2Source(rs, "")
if err != nil {
return nil, errors.Errorf("failed to create oauth2 source: %w", err)
}
oauth2Token, err := oauth2Source.RequestOauth2Token(h.webExposedURL+"/oauth2/callback", code)
if err != nil {
return nil, err
}
return h.HandleRemoteSourceAuthRequest(ctx, requestType, requestString, "", oauth2Token.AccessToken, oauth2Token.RefreshToken, oauth2Token.Expiry)
}
func (h *ActionHandler) DeleteUser(ctx context.Context, userRef string) error {
if !h.IsUserAdmin(ctx) {
return errors.Errorf("user not logged in")
}
resp, err := h.configstoreClient.DeleteUser(ctx, userRef)
if err != nil {
return errors.Errorf("failed to delete user: %w", ErrFromRemote(resp, err))
}
return nil
}
func (h *ActionHandler) DeleteUserLA(ctx context.Context, userRef, laID string) error {
if !h.IsUserLoggedOrAdmin(ctx) {
return errors.Errorf("user not logged in")
}
isAdmin := !h.IsUserAdmin(ctx)
curUserID := h.CurrentUserID(ctx)
user, resp, err := h.configstoreClient.GetUser(ctx, userRef)
if err != nil {
return errors.Errorf("failed to get user %q: %w", userRef, ErrFromRemote(resp, err))
}
// only admin or the same logged user can create a token
if !isAdmin && user.ID != curUserID {
return util.NewErrBadRequest(errors.Errorf("logged in user cannot create token for another user"))
}
resp, err = h.configstoreClient.DeleteUserLA(ctx, userRef, laID)
if err != nil {
return errors.Errorf("failed to delete user linked account: %w", ErrFromRemote(resp, err))
}
return nil
}
func (h *ActionHandler) DeleteUserToken(ctx context.Context, userRef, tokenName string) error {
if !h.IsUserLoggedOrAdmin(ctx) {
return errors.Errorf("user not logged in")
}
isAdmin := !h.IsUserAdmin(ctx)
curUserID := h.CurrentUserID(ctx)
user, resp, err := h.configstoreClient.GetUser(ctx, userRef)
if err != nil {
return errors.Errorf("failed to get user %q: %w", userRef, ErrFromRemote(resp, err))
}
// only admin or the same logged user can create a token
if !isAdmin && user.ID != curUserID {
return util.NewErrBadRequest(errors.Errorf("logged in user cannot delete token for another user"))
}
resp, err = h.configstoreClient.DeleteUserToken(ctx, userRef, tokenName)
if err != nil {
return errors.Errorf("failed to delete user token: %w", ErrFromRemote(resp, err))
}
return nil
}
type UserCreateRunRequest struct {
RepoUUID string
RepoPath string
Branch string
CommitSHA string
Message string
}
func (h *ActionHandler) UserCreateRun(ctx context.Context, req *UserCreateRunRequest) error {
curUserID := h.CurrentUserID(ctx)
user, resp, err := h.configstoreClient.GetUser(ctx, curUserID)
if err != nil {
return errors.Errorf("failed to get user %q: %w", curUserID, ErrFromRemote(resp, err))
}
// Verify that the repo is owned by the user
repoParts := strings.Split(req.RepoPath, "/")
if req.RepoUUID == "" {
return util.NewErrBadRequest(errors.Errorf("empty repo uuid"))
}
if len(repoParts) != 2 {
return util.NewErrBadRequest(errors.Errorf("wrong repo path: %q", req.RepoPath))
}
if repoParts[0] != user.ID {
return util.NewErrUnauthorized(errors.Errorf("repo %q not owned", req.RepoPath))
}
gitSource := agolagit.New(h.apiExposedURL + "/repos")
cloneURL := fmt.Sprintf("%s/%s.git", h.apiExposedURL+"/repos", req.RepoPath)
creq := &CreateRunRequest{
RunType: types.RunTypeUser,
RefType: types.RunRefTypeBranch,
RunCreationTrigger: types.RunCreationTriggerTypeManual,
Project: nil,
User: user,
RepoPath: req.RepoPath,
GitSource: gitSource,
CommitSHA: req.CommitSHA,
Message: req.Message,
Branch: req.Branch,
Tag: "",
PullRequestID: "",
Ref: gitSource.BranchRef(req.Branch),
CloneURL: cloneURL,
CommitLink: "",
BranchLink: "",
TagLink: "",
PullRequestLink: "",
UserRunRepoUUID: req.RepoUUID,
}
return h.CreateRuns(ctx, creq)
}