This is an automated email from the ASF dual-hosted git repository.
abeizn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
The following commit(s) were added to refs/heads/main by this push:
new 2227ba5aa refactor: revert auth mechansim in favor of oauth2_proxy
(#5396)
2227ba5aa is described below
commit 2227ba5aa6589f640dea2a5b977aa6693c6a23a2
Author: Klesh Wong <[email protected]>
AuthorDate: Wed Jun 7 16:34:00 2023 +0800
refactor: revert auth mechansim in favor of oauth2_proxy (#5396)
---
backend/server/api/api.go | 11 --
backend/server/api/login/login.go | 107 -------------
backend/server/services/auth/auth.go | 110 -------------
backend/server/services/auth/cognito.go | 276 --------------------------------
backend/server/services/init.go | 3 -
env.example | 8 -
6 files changed, 515 deletions(-)
diff --git a/backend/server/api/api.go b/backend/server/api/api.go
index f187206e8..24a017129 100644
--- a/backend/server/api/api.go
+++ b/backend/server/api/api.go
@@ -35,12 +35,10 @@ import (
"github.com/apache/incubator-devlake/core/plugin"
"github.com/apache/incubator-devlake/impls/logruslog"
_ "github.com/apache/incubator-devlake/server/api/docs"
- "github.com/apache/incubator-devlake/server/api/login"
"github.com/apache/incubator-devlake/server/api/ping"
"github.com/apache/incubator-devlake/server/api/shared"
"github.com/apache/incubator-devlake/server/api/version"
"github.com/apache/incubator-devlake/server/services"
- "github.com/apache/incubator-devlake/server/services/auth"
)
const DB_MIGRATION_REQUIRED = `
@@ -70,15 +68,6 @@ func CreateApiService() {
router.GET("/ping", ping.Get)
router.GET("/version", version.Get)
- if auth.Enabled() {
- // Add login endpoint
- router.POST("/login", login.Login)
- router.POST("/login/newpassword", login.NewPassword)
- router.POST("/login/refreshtoken", login.RefreshToken)
- // Use AuthenticationMiddleware for protected routes
- router.Use(auth.Middleware)
- }
-
// Endpoint to proceed database migration
router.GET("/proceed-db-migration", func(ctx *gin.Context) {
// Check if migration requires confirmation
diff --git a/backend/server/api/login/login.go
b/backend/server/api/login/login.go
deleted file mode 100644
index 7ef7f6d45..000000000
--- a/backend/server/api/login/login.go
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one or more
-contributor license agreements. See the NOTICE file distributed with
-this work for additional information regarding copyright ownership.
-The ASF licenses this file to You 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 login
-
-import (
- "net/http"
-
- "github.com/apache/incubator-devlake/core/errors"
- "github.com/apache/incubator-devlake/server/api/shared"
- "github.com/apache/incubator-devlake/server/services/auth"
-
- "github.com/gin-gonic/gin"
-)
-
-// @Summary post login
-// @Description post login
-// @Tags framework/login
-// @Accept application/json
-// @Param login body auth.LoginRequest true "json"
-// @Success 200 {object} auth.LoginResponse
-// @Failure 400 {object} shared.ApiBody "Bad Request"
-// @Failure 500 {object} shared.ApiBody "Internal Error"
-// @Router /login [post]
-func Login(ctx *gin.Context) {
- loginReq := &auth.LoginRequest{}
- err := ctx.ShouldBind(loginReq)
- if err != nil {
- shared.ApiOutputError(ctx, errors.BadInput.Wrap(err,
shared.BadRequestBody))
- return
- }
- res, err := auth.Provider.SignIn(loginReq)
- if err != nil {
- shared.ApiOutputError(ctx, errors.Default.Wrap(err, "error
signing in"))
- return
- }
- if res.AuthenticationResult != nil &&
res.AuthenticationResult.AccessToken != nil {
- token, err :=
auth.Provider.CheckAuth(*res.AuthenticationResult.AccessToken)
- if err != nil {
- shared.ApiOutputAbort(ctx, err)
- }
- ctx.Set("token", token)
- }
- shared.ApiOutputSuccess(ctx, res, http.StatusOK)
-}
-
-// @Summary post NewPassword
-// @Description post NewPassword
-// @Tags framework/NewPassword
-// @Accept application/json
-// @Param newpassword body auth.NewPasswordRequest true "json"
-// @Success 200 {object} auth.LoginResponse
-// @Failure 400 {object} shared.ApiBody "Bad Request"
-// @Failure 500 {object} shared.ApiBody "Internal Error"
-// @Router /password [post]
-func NewPassword(ctx *gin.Context) {
- newPasswordReq := &auth.NewPasswordRequest{}
- err := ctx.ShouldBind(newPasswordReq)
- if err != nil {
- shared.ApiOutputError(ctx, errors.BadInput.Wrap(err,
shared.BadRequestBody))
- return
- }
- res, err := auth.Provider.NewPassword(newPasswordReq)
- if err != nil {
- shared.ApiOutputError(ctx, errors.BadInput.Wrap(err, "failed to
set new password"))
- return
- }
- shared.ApiOutputSuccess(ctx, res, http.StatusOK)
-}
-
-// @Summary post RefreshToken
-// @Description post RefreshToken
-// @Tags framework/RefreshToken
-// @Accept application/json
-// @Param refreshtoken body auth.RefreshTokenRequest true "json"
-// @Success 200 {object} auth.LoginResponse
-// @Failure 400 {object} shared.ApiBody "Bad Request"
-// @Failure 500 {object} shared.ApiBody "Internal Error"
-// @Router /password [post]
-func RefreshToken(ctx *gin.Context) {
- req := &auth.RefreshTokenRequest{}
- err := ctx.ShouldBind(req)
- if err != nil {
- shared.ApiOutputError(ctx, errors.BadInput.Wrap(err,
shared.BadRequestBody))
- return
- }
- res, err := auth.Provider.RefreshToken(req)
- if err != nil {
- shared.ApiOutputError(ctx, errors.BadInput.Wrap(err, "failed to
refresh token"))
- return
- }
- shared.ApiOutputSuccess(ctx, res, http.StatusOK)
-}
diff --git a/backend/server/services/auth/auth.go
b/backend/server/services/auth/auth.go
deleted file mode 100644
index fe839e2d3..000000000
--- a/backend/server/services/auth/auth.go
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one or more
-contributor license agreements. See the NOTICE file distributed with
-this work for additional information regarding copyright ownership.
-The ASF licenses this file to You 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 auth
-
-import (
- "strings"
-
- "github.com/apache/incubator-devlake/core/context"
- "github.com/apache/incubator-devlake/core/errors"
- "github.com/apache/incubator-devlake/server/api/shared"
- "github.com/dgrijalva/jwt-go"
- "github.com/gin-gonic/gin"
-)
-
-// data structures
-
-type LoginRequest struct {
- Username string `json:"username"`
- Password string `json:"password"`
-}
-
-type LoginResponse struct {
- AuthenticationResult *AuthenticationResult `json:"authenticationResult"`
- ChallengeName *string `json:"challengeName"`
- ChallengeParameters map[string]*string `json:"challengeParameters"`
- Session *string `json:"session"`
-}
-
-type AuthenticationResult struct {
- AccessToken *string `json:"accessToken" type:"string" sensitive:"true"`
- ExpiresIn *int64 `json:"expiresIn" type:"integer"`
- IdToken *string `json:"idToken" type:"string" sensitive:"true"`
- RefreshToken *string `json:"refreshToken" type:"string"
sensitive:"true"`
- TokenType *string `json:"tokenType" type:"string"`
-}
-
-type NewPasswordRequest struct {
- Username string `json:"username"`
- NewPassword string `json:"newPassword"`
- Session string `json:"session"`
-}
-
-type RefreshTokenRequest struct {
- RefreshToken string `json:"refreshToken"`
-}
-
-// auth provider interface
-type AuthProvider interface {
- SignIn(*LoginRequest) (*LoginResponse, errors.Error)
- NewPassword(*NewPasswordRequest) (*LoginResponse, errors.Error)
- RefreshToken(*RefreshTokenRequest) (*LoginResponse, errors.Error)
- // ChangePassword(ctx *gin.Context, oldPassword, newPassword string)
errors.Error
- CheckAuth(token string) (*jwt.Token, errors.Error)
-}
-
-var Provider AuthProvider
-
-// initialize auth provider
-func InitProvider(basicRes context.BasicRes) {
- v := basicRes.GetConfigReader()
- awsCognitoEnabled := v.GetBool("AWS_ENABLE_COGNITO")
- if awsCognitoEnabled {
- Provider = NewCognitoProvider(basicRes)
- }
-}
-
-func Middleware(ctx *gin.Context) {
- if Provider == nil {
- return
- }
- // Get the Auth header
- authHeader := ctx.GetHeader("Authorization")
- if authHeader == "" {
- shared.ApiOutputAbort(ctx,
errors.Unauthorized.New("Authorization header is missing"))
- return
- }
-
- // Split the header into "Bearer" and the actual token
- bearerToken := strings.Split(authHeader, " ")
- if len(bearerToken) != 2 {
- shared.ApiOutputAbort(ctx, errors.Unauthorized.New("Invalid
Authorization header"))
- return
- }
- token, err := Provider.CheckAuth(bearerToken[1])
- if err != nil {
- shared.ApiOutputAbort(ctx, err)
- return
- }
-
- ctx.Set("token", token)
-}
-
-func Enabled() bool {
- return Provider != nil
-}
diff --git a/backend/server/services/auth/cognito.go
b/backend/server/services/auth/cognito.go
deleted file mode 100644
index 37c13f6ae..000000000
--- a/backend/server/services/auth/cognito.go
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one or more
-contributor license agreements. See the NOTICE file distributed with
-this work for additional information regarding copyright ownership.
-The ASF licenses this file to You 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 auth
-
-import (
- "crypto/rsa"
- "encoding/base64"
- "encoding/json"
- "fmt"
- "io"
- "math/big"
- "net/http"
- "strings"
-
- "github.com/apache/incubator-devlake/core/config"
- "github.com/apache/incubator-devlake/core/context"
- "github.com/apache/incubator-devlake/core/errors"
- "github.com/apache/incubator-devlake/core/log"
- "github.com/aws/aws-sdk-go/aws"
- "github.com/aws/aws-sdk-go/aws/session"
- "github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
- "github.com/dgrijalva/jwt-go"
-)
-
-type AwsCognitoProvider struct {
- jwks Jwks
- logger log.Logger
- client *cognitoidentityprovider.CognitoIdentityProvider
- clientId *string
- expectClaims jwt.MapClaims
-}
-
-func NewCognitoProvider(basicRes context.BasicRes) *AwsCognitoProvider {
- // Get configuration
- v := config.GetConfig()
- // TODO: verify the configuration
- // Create an AWS session
- sess := session.Must(session.NewSession(&aws.Config{
- Region: aws.String(v.GetString("AWS_AUTH_REGION")),
- }))
- // Create a Cognito Identity Provider client
- client := cognitoidentityprovider.New(sess)
- cgt := &AwsCognitoProvider{
- client: client,
- clientId:
aws.String(v.GetString("AWS_AUTH_USER_POOL_WEB_CLIENT_ID")),
- logger: basicRes.GetLogger().Nested("cognito"),
- }
- // Fetch the JWKS from the Cognito User Pool
- jwksURL := fmt.Sprintf(
- "https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json",
- v.GetString("AWS_AUTH_REGION"),
- v.GetString("AWS_AUTH_USER_POOL_ID"),
- )
- err := cgt.fetchJWKS(jwksURL)
- if err != nil {
- panic(err)
- }
- // Optional expect claims
- expect_claims :=
strings.TrimSpace(v.GetString("AWS_AUTH_EXPECT_CLAIMS"))
- if expect_claims != "" {
- e := json.Unmarshal([]byte(expect_claims), &cgt.expectClaims)
- if e != nil {
- panic(e)
- }
- }
- return cgt
-}
-
-func (cgt *AwsCognitoProvider) fetchJWKS(jwksURL string) errors.Error {
- // Get the JWKS from the URL
- resp, err := http.Get(jwksURL)
- if err != nil {
- return errors.Default.Wrap(err, "Failed to fetch JWKS")
- }
- defer resp.Body.Close()
-
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return errors.Default.Wrap(err, "Failed to read JWKS")
- }
- // Unmarshal the response into a Jwks struct
- err = json.Unmarshal(body, &cgt.jwks)
- if err != nil {
- return errors.Default.Wrap(err, "Failed to unmarshall JWKS")
- }
- return nil
-}
-
-func (cgt *AwsCognitoProvider) SignIn(loginReq *LoginRequest) (*LoginResponse,
errors.Error) {
- // Create the input for InitiateAuth
- input := &cognitoidentityprovider.InitiateAuthInput{
- AuthFlow: aws.String("USER_PASSWORD_AUTH"),
- ClientId: cgt.clientId,
- AuthParameters: map[string]*string{
- "USERNAME": aws.String(loginReq.Username),
- "PASSWORD": aws.String(loginReq.Password),
- },
- }
-
- // Call Cognito to get auth tokens
- return cgt.initiateAuth(input)
-}
-
-func (cgt *AwsCognitoProvider) initiateAuth(input
*cognitoidentityprovider.InitiateAuthInput) (*LoginResponse, errors.Error) {
- response, err := cgt.client.InitiateAuth(input)
- if err != nil {
- return nil, errors.BadInput.New(err.Error())
- }
- loginRes := &LoginResponse{
- ChallengeName: response.ChallengeName,
- ChallengeParameters: response.ChallengeParameters,
- Session: response.Session,
- }
- if response.AuthenticationResult != nil {
- loginRes.AuthenticationResult = &AuthenticationResult{
- AccessToken: response.AuthenticationResult.AccessToken,
- ExpiresIn: response.AuthenticationResult.ExpiresIn,
- IdToken: response.AuthenticationResult.IdToken,
- RefreshToken:
response.AuthenticationResult.RefreshToken,
- TokenType: response.AuthenticationResult.TokenType,
- }
- }
- return loginRes, nil
-}
-
-func (cgt *AwsCognitoProvider) CheckAuth(tokenString string) (*jwt.Token,
errors.Error) {
- // Parse the JWT token
- token, err := jwt.Parse(tokenString, func(token *jwt.Token)
(interface{}, error) {
- // Check the signing method
- if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
- return nil,
errors.Unauthorized.New(fmt.Sprintf("Unexpected signing method: %v",
token.Header["alg"]))
- }
-
- // Get the key ID from the header
- kid := token.Header["kid"].(string)
-
- // Look for the key that matches the kid
- for _, key := range cgt.jwks.Keys {
- if key.Kid == kid {
- // Construct the RSA public key
- n := pemHeader(key.N)
- e := pemHeader(key.E)
- parsedKey := &rsa.PublicKey{
- N: new(big.Int).SetBytes(n),
- E:
int(new(big.Int).SetBytes(e).Int64()),
- }
- return parsedKey, nil
- }
- }
-
- return nil, fmt.Errorf("Public key not found")
- })
-
- if err != nil {
- if ve, ok := err.(*jwt.ValidationError); ok {
- if ve.Errors == jwt.ValidationErrorExpired {
- return nil, errors.Forbidden.New("Token
expired")
- }
- }
- }
-
- // Check if the token is valid
- if err != nil || !token.Valid {
- cgt.logger.Error(err, "Invalid token")
- return nil, errors.Unauthorized.New("Invalid token")
- }
-
- // verify claims
- if len(cgt.expectClaims) > 0 {
- if actualClaims, ok := token.Claims.(jwt.MapClaims); ok {
- for key, expected := range cgt.expectClaims {
- if expected != actualClaims[key] {
- return nil,
errors.Unauthorized.New("Invalid token: expected claims do not match")
- }
- }
- } else {
- return nil, errors.Unauthorized.New("Invalid token:
expected claims do not match")
- }
- }
-
- return token, nil
-}
-
-func pemHeader(encodedKey string) []byte {
- // Decode the base64 encoded key
- key, err := base64.RawURLEncoding.DecodeString(encodedKey)
- if err != nil {
- return nil
- }
- return key
-}
-
-// Jwks represents the JSON web key set
-type Jwks struct {
- Keys []struct {
- Kid string `json:"kid"`
- N string `json:"n"`
- E string `json:"e"`
- } `json:"keys"`
-}
-
-func (cgt *AwsCognitoProvider) NewPassword(newPasswordReq *NewPasswordRequest)
(*LoginResponse, errors.Error) {
- input := &cognitoidentityprovider.RespondToAuthChallengeInput{
- ChallengeName: aws.String("NEW_PASSWORD_REQUIRED"),
- ChallengeResponses: map[string]*string{
- "USERNAME": aws.String(newPasswordReq.Username),
- "NEW_PASSWORD": aws.String(newPasswordReq.NewPassword),
- },
- Session: aws.String(newPasswordReq.Session),
- ClientId: cgt.clientId,
- }
- response, err := cgt.client.RespondToAuthChallenge(input)
- if err != nil {
- return nil, errors.BadInput.Wrap(err, "Error setting up new
password: "+err.Error())
- }
- // yes , it is identical to the login response, and yet they are 2
different structs
- loginRes := &LoginResponse{
- ChallengeName: response.ChallengeName,
- ChallengeParameters: response.ChallengeParameters,
- Session: response.Session,
- }
- if response.AuthenticationResult != nil {
- loginRes.AuthenticationResult = &AuthenticationResult{
- AccessToken: response.AuthenticationResult.AccessToken,
- ExpiresIn: response.AuthenticationResult.ExpiresIn,
- IdToken: response.AuthenticationResult.IdToken,
- RefreshToken:
response.AuthenticationResult.RefreshToken,
- TokenType: response.AuthenticationResult.TokenType,
- }
- }
- return loginRes, nil
-}
-
-func (cgt *AwsCognitoProvider) RefreshToken(req *RefreshTokenRequest)
(*LoginResponse, errors.Error) {
- // Create the input for InitiateAuth
- input := &cognitoidentityprovider.InitiateAuthInput{
- AuthFlow:
aws.String(cognitoidentityprovider.AuthFlowTypeRefreshTokenAuth),
- ClientId: cgt.clientId,
- AuthParameters: map[string]*string{
- "REFRESH_TOKEN": aws.String(req.RefreshToken),
- },
- }
- return cgt.initiateAuth(input)
-}
-
-// func (cgt *AwsCognitorProvider) ChangePassword(ctx *gin.Context,
oldPassword, newPassword string) errors.Error {
-// token := ctx.GetString(("token"))
-// if token == "" {
-// return errors.Unauthorized.New("Token is missing")
-// }
-// input := &cognitoidentityprovider.ChangePasswordInput{
-// AccessToken: &token,
-// PreviousPassword: &oldPassword,
-// ProposedPassword: &newPassword,
-// }
-// _, err := cgt.client.ChangePassword(input)
-// if err != nil {
-// return errors.BadInput.Wrap(err, "Error changing password")
-// }
-// return nil
-// }
diff --git a/backend/server/services/init.go b/backend/server/services/init.go
index e4f9c065b..f3f4e4e72 100644
--- a/backend/server/services/init.go
+++ b/backend/server/services/init.go
@@ -32,7 +32,6 @@ import (
"github.com/apache/incubator-devlake/helpers/pluginhelper/services"
"github.com/apache/incubator-devlake/impls/dalgorm"
"github.com/apache/incubator-devlake/impls/logruslog"
- "github.com/apache/incubator-devlake/server/services/auth"
"github.com/go-playground/validator/v10"
"github.com/robfig/cron/v3"
)
@@ -84,8 +83,6 @@ func GetMigrator() plugin.Migrator {
func Init() {
InitResources()
- auth.InitProvider(basicRes)
-
// lock the database to avoid multiple devlake instances from sharing
the same one
lockDb()
diff --git a/env.example b/env.example
index 917126c1a..718be78cc 100644
--- a/env.example
+++ b/env.example
@@ -46,11 +46,3 @@ ENCRYPTION_SECRET=
# Set if skip verify and connect with out trusted certificate when use https
##########################
IN_SECURE_SKIP_VERIFY=
-
-
-# aws cognito
-AWS_ENABLE_COGNITO=
-AWS_AUTH_REGION=
-AWS_AUTH_USER_POOL_ID=
-AWS_AUTH_USER_POOL_WEB_CLIENT_ID=
-AWS_AUTH_COOKIE_STORAGE_DOMAIN=