diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 00000000..ba322e69 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,111 @@ +run: + timeout: 5m + issues-exit-code: 2 + tests: true + build-tags: [] + skip-dirs: [] + skip-dirs-use-default: false + skip-files: [] + allow-parallel-runners: true +output: + format: colored-line-number + print-issued-lines: true + print-linter-name: true + uniq-by-line: true + sort-results: true +linters-settings: + godot: + scope: all + gofmt: + simplify: false + staticcheck: + checks: + - all + - "-SA9004" # + errcheck: + check-type-assertions: true + check-blank: false + + govet: + check-shadowing: false + settings: + printf: + funcs: + - (github.com/xxx/xxx/pkg/log).Infof + - (github.com/xxx/xxx/pkg/log).Warnf + - (github.com/xxx/xxx/pkg/log).Errorf + - (github.com/xxx/xxx/pkg/log).Fatalf + - (github.com/xxx/xxx/pkg/log).Panicf + - (github.com/xxx/xxx/pkg/log).Debugf + - (github.com/xxx/xxx/pkg/log).Info + - (github.com/xxx/xxx/pkg/log).Warn + - (github.com/xxx/xxx/pkg/log).Error + - (github.com/xxx/xxx/pkg/log).Fatal + - (github.com/xxx/xxx/pkg/log).Panic + - (github.com/xxx/xxx/pkg/log).Debug + - (github.com/xxx/xxx/pkg/log).InfoDepth + - (github.com/xxx/xxx/pkg/log).WarnDepth + - (github.com/xxx/xxx/pkg/log).ErrorDepth + - (github.com/xxx/xxx/pkg/log).FatalDepth + - (github.com/xxx/xxx/pkg/log).PanicDepth + - (github.com/xxx/xxx/pkg/log).DebugDepth + - (github.com/xxx/xxx/pkg/log).InfofDepth + - (github.com/xxx/xxx/pkg/log).WarnfDepth + - (github.com/xxx/xxx/pkg/log).ErrorfDepth + - (github.com/xxx/xxx/pkg/log).FatalfDepth + - (github.com/xxx/xxx/pkg/log).PanicfDepth + - (github.com/xxx/xxx/pkg/log).DebugfDepth + - (github.com/xxx/xxx/pkg/log).InfoWithFields + - (github.com/xxx/xxx/pkg/log).WarnWithFields + - (github.com/xxx/xxx/pkg/log).ErrorWithFields + - (github.com/xxx/xxx/pkg/log).FatalWithFields + - (github.com/xxx/xxx/pkg/log).PanicWithFields + - (github.com/xxx/xxx/pkg/log).DebugWithFields + - (github.com/xxx/xxx/pkg/log).InfofWithFields + +linters: + enable: + - errcheck # checks unchecked errors + - gosimple # simplify code + - govet # examines Go source code and reports suspicious constructs + - ineffassign # detect unused assign + - staticcheck # cover Go vet edge cases + - typecheck # type-checks Go code + - unused # checks Go code for unused constants, variables, functions and types + - gosimple # specializes in simplifying a code + - errcheck # checks unchecked errors + - misspell # finds commonly misspelled English words in comments + + disable: + - deadcode # enabled-default. but duplicated & deprecated someday + - structcheck # enabled-default. but duplicated & deprecated someday + - varcheck # enabled-default. but duplicated & deprecated someday + - bidichk # checks for dangerous unicode character sequences + - bodyclose # checks whether HTTP response body is closed successfully + - contextcheck # check the function whether use a non-inherited context + - dupl # code clone detection + - durationcheck # check for two durations multiplied together + - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error + - errorlint # find code that will cause problems with the error wrapping scheme + - exportloopref # checks for pointers to enclosing loop variables + - goconst # finds repeated strings that could be replaced by a constant + - gocritic # provides diagnostics that check for bugs, performance and style issues + - godot # check if comments end in a period + - gofmt # checks whether code was gofmt-ed + - goimports # fix imports, formats your code in the same style as gofmt + - ifshort # checks that your code uses short syntax for if-statements whenever possible +# - misspell # finds commonly misspelled English words in comments + - noctx # finds sending http request without context.Context + - predeclared # find code that shadows one of Go's predeclared identifiers + - revive # replacement of golint + - rowserrcheck # checks whether Err of rows is checked successfully + - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed. + - unconvert # remove unnecessary type conversions + - wastedassign # finds wasted assignment statements. + - whitespace # checks for unnecessary whitespace + - wrapcheck # check that errors from external packages are wrapped during return to help identify the error source. + +issues: + exclude: [] +severity: + default-severity: warning \ No newline at end of file diff --git a/api/swagger/docs.go b/api/swagger/docs.go index 08aa189c..c97d28d1 100644 --- a/api/swagger/docs.go +++ b/api/swagger/docs.go @@ -1,5 +1,4 @@ -// Package swagger GENERATED BY SWAG; DO NOT EDIT -// This file was generated by swaggo/swag +// Code generated by swaggo/swag. DO NOT EDIT package swagger import "github.com/swaggo/swag" @@ -2055,7 +2054,7 @@ const docTemplate = `{ "type": "object", "properties": { "appGroupType": { - "type": "integer" + "$ref": "#/definitions/domain.AppGroupType" }, "clusterId": { "type": "string" @@ -2076,7 +2075,7 @@ const docTemplate = `{ "type": "string" }, "status": { - "type": "integer" + "$ref": "#/definitions/domain.AppGroupStatus" }, "statusDescription": { "type": "string" @@ -2092,6 +2091,38 @@ const docTemplate = `{ } } }, + "domain.AppGroupStatus": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5 + ], + "x-enum-varnames": [ + "AppGroupStatus_PENDING", + "AppGroupStatus_INSTALLING", + "AppGroupStatus_RUNNING", + "AppGroupStatus_DELETING", + "AppGroupStatus_DELETED", + "AppGroupStatus_ERROR" + ] + }, + "domain.AppGroupType": { + "type": "integer", + "enum": [ + 0, + 1, + 2 + ], + "x-enum-varnames": [ + "AppGroupType_UNSPECIFIED", + "AppGroupType_LMA", + "AppGroupType_SERVICE_MESH" + ] + }, "domain.AppServeApp": { "type": "object", "properties": { @@ -2248,7 +2279,7 @@ const docTemplate = `{ "type": "string" }, "applicationType": { - "type": "integer" + "$ref": "#/definitions/domain.ApplicationType" }, "createdAt": { "type": "string" @@ -2267,6 +2298,35 @@ const docTemplate = `{ } } }, + "domain.ApplicationType": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "x-enum-varnames": [ + "ApplicationType_UNSPECIFIED", + "ApplicationType_THANOS", + "ApplicationType_PROMETHEUS", + "ApplicationType_GRAFANA", + "ApplicationType_KIALI", + "ApplicationType_KIBANA", + "ApplicationType_ELASTICSERCH", + "ApplicationType_CLOUD_CONSOLE", + "ApplicationType_HORIZON", + "ApplicationType_JAEGER", + "ApplicationType_KUBERNETES_DASHBOARD" + ] + }, "domain.ChartData": { "type": "object", "properties": { @@ -2428,7 +2488,7 @@ const docTemplate = `{ "type": "string" }, "status": { - "type": "integer" + "$ref": "#/definitions/domain.ClusterStatus" }, "statusDesc": { "type": "string" @@ -2540,6 +2600,25 @@ const docTemplate = `{ } } }, + "domain.ClusterStatus": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5 + ], + "x-enum-varnames": [ + "ClusterStatus_PENDING", + "ClusterStatus_INSTALLING", + "ClusterStatus_RUNNING", + "ClusterStatus_DELETING", + "ClusterStatus_DELETED", + "ClusterStatus_ERROR" + ] + }, "domain.CreateAppGroupRequest": { "type": "object", "required": [ @@ -3079,7 +3158,7 @@ const docTemplate = `{ "type": "string" }, "status": { - "type": "integer" + "type": "string" }, "statusDescription": { "type": "string" @@ -3198,6 +3277,9 @@ const docTemplate = `{ "domain.ListOrganizationBody": { "type": "object", "properties": { + "createdAt": { + "type": "string" + }, "description": { "type": "string" }, @@ -3214,7 +3296,10 @@ const docTemplate = `{ "type": "string" }, "status": { - "type": "integer" + "$ref": "#/definitions/domain.OrganizationStatus" + }, + "updatedAt": { + "type": "string" } } }, @@ -3325,7 +3410,7 @@ const docTemplate = `{ "type": "string" }, "status": { - "type": "integer" + "$ref": "#/definitions/domain.OrganizationStatus" }, "statusDescription": { "type": "string" @@ -3335,6 +3420,29 @@ const docTemplate = `{ } } }, + "domain.OrganizationStatus": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ], + "x-enum-varnames": [ + "OrganizationStatus_PENDING", + "OrganizationStatus_CREATE", + "OrganizationStatus_CREATING", + "OrganizationStatus_CREATED", + "OrganizationStatus_DELETE", + "OrganizationStatus_DELETING", + "OrganizationStatus_DELETED", + "OrganizationStatus_ERROR" + ] + }, "domain.Role": { "type": "object", "properties": { @@ -3842,6 +3950,9 @@ const docTemplate = `{ "accountId": { "type": "string" }, + "createdAt": { + "type": "string" + }, "department": { "type": "string" }, @@ -3862,6 +3973,9 @@ const docTemplate = `{ }, "role": { "$ref": "#/definitions/domain.Role" + }, + "updatedAt": { + "type": "string" } } } diff --git a/api/swagger/swagger.json b/api/swagger/swagger.json index 810181b7..a6756030 100644 --- a/api/swagger/swagger.json +++ b/api/swagger/swagger.json @@ -2048,7 +2048,7 @@ "type": "object", "properties": { "appGroupType": { - "type": "integer" + "$ref": "#/definitions/domain.AppGroupType" }, "clusterId": { "type": "string" @@ -2069,7 +2069,7 @@ "type": "string" }, "status": { - "type": "integer" + "$ref": "#/definitions/domain.AppGroupStatus" }, "statusDescription": { "type": "string" @@ -2085,6 +2085,38 @@ } } }, + "domain.AppGroupStatus": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5 + ], + "x-enum-varnames": [ + "AppGroupStatus_PENDING", + "AppGroupStatus_INSTALLING", + "AppGroupStatus_RUNNING", + "AppGroupStatus_DELETING", + "AppGroupStatus_DELETED", + "AppGroupStatus_ERROR" + ] + }, + "domain.AppGroupType": { + "type": "integer", + "enum": [ + 0, + 1, + 2 + ], + "x-enum-varnames": [ + "AppGroupType_UNSPECIFIED", + "AppGroupType_LMA", + "AppGroupType_SERVICE_MESH" + ] + }, "domain.AppServeApp": { "type": "object", "properties": { @@ -2241,7 +2273,7 @@ "type": "string" }, "applicationType": { - "type": "integer" + "$ref": "#/definitions/domain.ApplicationType" }, "createdAt": { "type": "string" @@ -2260,6 +2292,35 @@ } } }, + "domain.ApplicationType": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "x-enum-varnames": [ + "ApplicationType_UNSPECIFIED", + "ApplicationType_THANOS", + "ApplicationType_PROMETHEUS", + "ApplicationType_GRAFANA", + "ApplicationType_KIALI", + "ApplicationType_KIBANA", + "ApplicationType_ELASTICSERCH", + "ApplicationType_CLOUD_CONSOLE", + "ApplicationType_HORIZON", + "ApplicationType_JAEGER", + "ApplicationType_KUBERNETES_DASHBOARD" + ] + }, "domain.ChartData": { "type": "object", "properties": { @@ -2421,7 +2482,7 @@ "type": "string" }, "status": { - "type": "integer" + "$ref": "#/definitions/domain.ClusterStatus" }, "statusDesc": { "type": "string" @@ -2533,6 +2594,25 @@ } } }, + "domain.ClusterStatus": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5 + ], + "x-enum-varnames": [ + "ClusterStatus_PENDING", + "ClusterStatus_INSTALLING", + "ClusterStatus_RUNNING", + "ClusterStatus_DELETING", + "ClusterStatus_DELETED", + "ClusterStatus_ERROR" + ] + }, "domain.CreateAppGroupRequest": { "type": "object", "required": [ @@ -3072,7 +3152,7 @@ "type": "string" }, "status": { - "type": "integer" + "type": "string" }, "statusDescription": { "type": "string" @@ -3191,6 +3271,9 @@ "domain.ListOrganizationBody": { "type": "object", "properties": { + "createdAt": { + "type": "string" + }, "description": { "type": "string" }, @@ -3207,7 +3290,10 @@ "type": "string" }, "status": { - "type": "integer" + "$ref": "#/definitions/domain.OrganizationStatus" + }, + "updatedAt": { + "type": "string" } } }, @@ -3318,7 +3404,7 @@ "type": "string" }, "status": { - "type": "integer" + "$ref": "#/definitions/domain.OrganizationStatus" }, "statusDescription": { "type": "string" @@ -3328,6 +3414,29 @@ } } }, + "domain.OrganizationStatus": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ], + "x-enum-varnames": [ + "OrganizationStatus_PENDING", + "OrganizationStatus_CREATE", + "OrganizationStatus_CREATING", + "OrganizationStatus_CREATED", + "OrganizationStatus_DELETE", + "OrganizationStatus_DELETING", + "OrganizationStatus_DELETED", + "OrganizationStatus_ERROR" + ] + }, "domain.Role": { "type": "object", "properties": { @@ -3835,6 +3944,9 @@ "accountId": { "type": "string" }, + "createdAt": { + "type": "string" + }, "department": { "type": "string" }, @@ -3855,6 +3967,9 @@ }, "role": { "$ref": "#/definitions/domain.Role" + }, + "updatedAt": { + "type": "string" } } } diff --git a/api/swagger/swagger.yaml b/api/swagger/swagger.yaml index 411a96ef..84c0a704 100644 --- a/api/swagger/swagger.yaml +++ b/api/swagger/swagger.yaml @@ -3,7 +3,7 @@ definitions: domain.AppGroupResponse: properties: appGroupType: - type: integer + $ref: '#/definitions/domain.AppGroupType' clusterId: type: string createdAt: @@ -17,7 +17,7 @@ definitions: name: type: string status: - type: integer + $ref: '#/definitions/domain.AppGroupStatus' statusDescription: type: string updatedAt: @@ -27,6 +27,32 @@ definitions: workflowId: type: string type: object + domain.AppGroupStatus: + enum: + - 0 + - 1 + - 2 + - 3 + - 4 + - 5 + type: integer + x-enum-varnames: + - AppGroupStatus_PENDING + - AppGroupStatus_INSTALLING + - AppGroupStatus_RUNNING + - AppGroupStatus_DELETING + - AppGroupStatus_DELETED + - AppGroupStatus_ERROR + domain.AppGroupType: + enum: + - 0 + - 1 + - 2 + type: integer + x-enum-varnames: + - AppGroupType_UNSPECIFIED + - AppGroupType_LMA + - AppGroupType_SERVICE_MESH domain.AppServeApp: properties: app_serve_app_tasks: @@ -139,7 +165,7 @@ definitions: appGroupId: type: string applicationType: - type: integer + $ref: '#/definitions/domain.ApplicationType' createdAt: type: string endpoint: @@ -151,6 +177,32 @@ definitions: updatedAt: type: string type: object + domain.ApplicationType: + enum: + - 0 + - 1 + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + - 8 + - 9 + - 10 + type: integer + x-enum-varnames: + - ApplicationType_UNSPECIFIED + - ApplicationType_THANOS + - ApplicationType_PROMETHEUS + - ApplicationType_GRAFANA + - ApplicationType_KIALI + - ApplicationType_KIBANA + - ApplicationType_ELASTICSERCH + - ApplicationType_CLOUD_CONSOLE + - ApplicationType_HORIZON + - ApplicationType_JAEGER + - ApplicationType_KUBERNETES_DASHBOARD domain.ChartData: properties: series: @@ -257,7 +309,7 @@ definitions: stackTemplateId: type: string status: - type: integer + $ref: '#/definitions/domain.ClusterStatus' statusDesc: type: string updatedAt: @@ -330,6 +382,22 @@ definitions: updator: $ref: '#/definitions/domain.SimpleUserResponse' type: object + domain.ClusterStatus: + enum: + - 0 + - 1 + - 2 + - 3 + - 4 + - 5 + type: integer + x-enum-varnames: + - ClusterStatus_PENDING + - ClusterStatus_INSTALLING + - ClusterStatus_RUNNING + - ClusterStatus_DELETING + - ClusterStatus_DELETED + - ClusterStatus_ERROR domain.CreateAppGroupRequest: properties: appGroupType: @@ -696,7 +764,7 @@ definitions: primaryClusterId: type: string status: - type: integer + type: string statusDescription: type: string updatedAt: @@ -772,6 +840,8 @@ definitions: type: object domain.ListOrganizationBody: properties: + createdAt: + type: string description: type: string id: @@ -783,7 +853,9 @@ definitions: primaryClusterId: type: string status: - type: integer + $ref: '#/definitions/domain.OrganizationStatus' + updatedAt: + type: string type: object domain.ListUserBody: properties: @@ -856,12 +928,32 @@ definitions: primaryClusterId: type: string status: - type: integer + $ref: '#/definitions/domain.OrganizationStatus' statusDescription: type: string updatedAt: type: string type: object + domain.OrganizationStatus: + enum: + - 0 + - 1 + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + type: integer + x-enum-varnames: + - OrganizationStatus_PENDING + - OrganizationStatus_CREATE + - OrganizationStatus_CREATING + - OrganizationStatus_CREATED + - OrganizationStatus_DELETE + - OrganizationStatus_DELETING + - OrganizationStatus_DELETED + - OrganizationStatus_ERROR domain.Role: properties: createdAt: @@ -1199,6 +1291,8 @@ definitions: properties: accountId: type: string + createdAt: + type: string department: type: string description: @@ -1213,6 +1307,8 @@ definitions: $ref: '#/definitions/domain.Organization' role: $ref: '#/definitions/domain.Role' + updatedAt: + type: string type: object type: object domain.User: diff --git a/internal/auth/authenticator/interface.go b/internal/auth/authenticator/interface.go deleted file mode 100644 index e32da109..00000000 --- a/internal/auth/authenticator/interface.go +++ /dev/null @@ -1,19 +0,0 @@ -package authenticator - -import ( - "context" - "github.com/openinfradev/tks-api/internal/auth/user" - "net/http" -) - -type Token interface { - AuthenticateToken(ctx context.Context, token string) (*Response, bool, error) -} - -type Request interface { - AuthenticateRequest(req *http.Request) (*Response, bool, error) -} - -type Response struct { - User user.Info -} diff --git a/internal/auth/authenticator/keycloak/keycloak.go b/internal/auth/authenticator/keycloak/keycloak.go deleted file mode 100644 index cf172a0e..00000000 --- a/internal/auth/authenticator/keycloak/keycloak.go +++ /dev/null @@ -1 +0,0 @@ -package keycloak diff --git a/internal/delivery/http/app-serve-app.go b/internal/delivery/http/app-serve-app.go index 1f2e26e8..521f4245 100644 --- a/internal/delivery/http/app-serve-app.go +++ b/internal/delivery/http/app-serve-app.go @@ -139,7 +139,6 @@ func (h *AppServeAppHandler) GetAppServeApps(w http.ResponseWriter, r *http.Requ out.AppServeApps = apps ResponseJSON(w, http.StatusOK, out) - } // GetAppServeApp godoc diff --git a/internal/delivery/http/auth.go b/internal/delivery/http/auth.go index cde40bce..3a1b13ee 100644 --- a/internal/delivery/http/auth.go +++ b/internal/delivery/http/auth.go @@ -59,7 +59,6 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { } ResponseJSON(w, http.StatusOK, out) - } // Logout godoc diff --git a/internal/delivery/http/cloud-account.go b/internal/delivery/http/cloud-account.go index 9b555123..80399224 100644 --- a/internal/delivery/http/cloud-account.go +++ b/internal/delivery/http/cloud-account.go @@ -6,7 +6,7 @@ import ( "github.com/google/uuid" "github.com/gorilla/mux" - "github.com/openinfradev/tks-api/internal/auth/request" + "github.com/openinfradev/tks-api/internal/middleware/auth/request" "github.com/openinfradev/tks-api/internal/usecase" "github.com/openinfradev/tks-api/pkg/domain" "github.com/openinfradev/tks-api/pkg/httpErrors" diff --git a/internal/delivery/http/organization.go b/internal/delivery/http/organization.go index 40e9bc5a..95d71542 100644 --- a/internal/delivery/http/organization.go +++ b/internal/delivery/http/organization.go @@ -4,12 +4,11 @@ import ( "fmt" "net/http" - "github.com/openinfradev/tks-api/pkg/httpErrors" - "github.com/gorilla/mux" - "github.com/openinfradev/tks-api/internal/auth/request" + "github.com/openinfradev/tks-api/internal/middleware/auth/request" "github.com/openinfradev/tks-api/internal/usecase" "github.com/openinfradev/tks-api/pkg/domain" + "github.com/openinfradev/tks-api/pkg/httpErrors" "github.com/openinfradev/tks-api/pkg/log" ) @@ -36,6 +35,7 @@ func NewOrganizationHandler(o usecase.IOrganizationUsecase, u usecase.IUserUseca // @Router /organizations [post] // @Security JWT func (h *OrganizationHandler) CreateOrganization(w http.ResponseWriter, r *http.Request) { + log.Info("called Create") input := domain.CreateOrganizationRequest{} err := UnmarshalRequestInput(r, &input) diff --git a/internal/delivery/http/stack-template.go b/internal/delivery/http/stack-template.go index fbb66952..4eee3b2c 100644 --- a/internal/delivery/http/stack-template.go +++ b/internal/delivery/http/stack-template.go @@ -35,7 +35,7 @@ func NewStackTemplateHandler(h usecase.IStackTemplateUsecase) *StackTemplateHand // @Router /stack-templates [post] // @Security JWT func (h *StackTemplateHandler) CreateStackTemplate(w http.ResponseWriter, r *http.Request) { - ErrorJSON(w, fmt.Errorf("Need implentation")) + ErrorJSON(w, fmt.Errorf("need implementation")) } // GetStackTemplate godoc @@ -84,7 +84,7 @@ func (h *StackTemplateHandler) GetStackTemplate(w http.ResponseWriter, r *http.R vars := mux.Vars(r) strId, ok := vars["stackTemplateId"] if !ok { - ErrorJSON(w, httpErrors.NewBadRequestError(fmt.Errorf("Invalid stackTemplateId"))) + ErrorJSON(w, httpErrors.NewBadRequestError(fmt.Errorf("invalid stackTemplateId"))) return } @@ -111,7 +111,6 @@ func (h *StackTemplateHandler) GetStackTemplate(w http.ResponseWriter, r *http.R } ResponseJSON(w, http.StatusOK, out) - } // UpdateStackTemplate godoc @@ -152,7 +151,7 @@ func (h *StackTemplateHandler) UpdateStackTemplate(w http.ResponseWriter, r *htt } */ - ErrorJSON(w, fmt.Errorf("Need implentation")) + ErrorJSON(w, fmt.Errorf("need implementation")) } // DeleteStackTemplate godoc @@ -169,9 +168,9 @@ func (h *StackTemplateHandler) DeleteStackTemplate(w http.ResponseWriter, r *htt vars := mux.Vars(r) _, ok := vars["stackTemplateId"] if !ok { - ErrorJSON(w, httpErrors.NewBadRequestError(fmt.Errorf("Invalid stackTemplateId"))) + ErrorJSON(w, httpErrors.NewBadRequestError(fmt.Errorf("invalid stackTemplateId"))) return } - ErrorJSON(w, fmt.Errorf("Need implentation")) + ErrorJSON(w, fmt.Errorf("need implementation")) } diff --git a/internal/delivery/http/stack.go b/internal/delivery/http/stack.go index d3669890..3662d1de 100644 --- a/internal/delivery/http/stack.go +++ b/internal/delivery/http/stack.go @@ -153,7 +153,7 @@ func (h *StackHandler) GetStack(w http.ResponseWriter, r *http.Request) { // @Router /organizations/{organizationId}/stacks/{stackId} [put] // @Security JWT func (h *StackHandler) UpdateStack(w http.ResponseWriter, r *http.Request) { - ErrorJSON(w, httpErrors.NewInternalServerError(fmt.Errorf("Need implementaion"))) + ErrorJSON(w, httpErrors.NewInternalServerError(fmt.Errorf("need implementation"))) } // DeleteStack godoc diff --git a/internal/delivery/http/user.go b/internal/delivery/http/user.go index 7a351d35..6dd6b189 100644 --- a/internal/delivery/http/user.go +++ b/internal/delivery/http/user.go @@ -4,12 +4,11 @@ import ( "fmt" "net/http" - "github.com/openinfradev/tks-api/pkg/log" - "github.com/gorilla/mux" "github.com/openinfradev/tks-api/internal/usecase" "github.com/openinfradev/tks-api/pkg/domain" "github.com/openinfradev/tks-api/pkg/httpErrors" + "github.com/openinfradev/tks-api/pkg/log" ) type IUserHandler interface { diff --git a/internal/helper/jwt.go b/internal/helper/jwt.go index 8bfcd316..68170a55 100644 --- a/internal/helper/jwt.go +++ b/internal/helper/jwt.go @@ -11,7 +11,10 @@ func CreateJWT(accountId string, uId string, organizationId string) (string, err signingKey := []byte(viper.GetString("jwt-secret")) aToken := jwt.New(jwt.SigningMethodHS256) - claims := aToken.Claims.(jwt.MapClaims) + claims, ok := aToken.Claims.(jwt.MapClaims) + if !ok { + return "", nil + } claims["AccountId"] = accountId claims["ID"] = uId claims["OrganizationId"] = organizationId diff --git a/internal/keycloak/config.go b/internal/keycloak/config.go index 30802ba0..bc0243d5 100644 --- a/internal/keycloak/config.go +++ b/internal/keycloak/config.go @@ -11,4 +11,6 @@ const ( DefaultMasterRealm = "master" DefaultClientID = "tks" DefaultClientSecret = "secret" + AdminCliClientID = "admin-cli" + accessTokenLifespan = 60 * 60 * 24 ) diff --git a/internal/keycloak/keycloak.go b/internal/keycloak/keycloak.go index 69ed3374..db139a47 100644 --- a/internal/keycloak/keycloak.go +++ b/internal/keycloak/keycloak.go @@ -4,12 +4,9 @@ import ( "context" "crypto/tls" "fmt" - "time" - "github.com/openinfradev/tks-api/pkg/httpErrors" "github.com/Nerzal/gocloak/v13" - "github.com/golang-jwt/jwt/v4" "github.com/openinfradev/tks-api/pkg/domain" "github.com/openinfradev/tks-api/pkg/log" ) @@ -17,37 +14,49 @@ import ( type IKeycloak interface { InitializeKeycloak() error - LoginAdmin() (string, error) + LoginAdmin(accountId string, password string) (*domain.User, error) + Login(accountId string, password string, organizationId string) (*domain.User, error) - CreateRealm(organizationName string, organizationConfig domain.Organization, token string) (string, error) - GetRealm(organizationName string, token string) (*domain.Organization, error) - GetRealms(token string) ([]*domain.Organization, error) - DeleteRealm(organizationName string, token string) error - UpdateRealm(organizationName string, organizationConfig domain.Organization, token string) error + CreateRealm(organizationName string) (string, error) + GetRealm(organizationName string) (*domain.Organization, error) + GetRealms() ([]*domain.Organization, error) + DeleteRealm(organizationName string) error + UpdateRealm(organizationName string, organizationConfig domain.Organization) error - CreateUser(organizationName string, user *gocloak.User, token string) error - GetUser(organizationName string, userAccountId string, token string) (*gocloak.User, error) - GetUsers(organizationName string, token string) ([]*gocloak.User, error) - DeleteUser(organizationName string, userAccountId string, token string) error - UpdateUser(organizationName string, user *gocloak.User, accessToken string) error + CreateUser(organizationName string, user *gocloak.User) error + GetUser(organizationName string, userAccountId string) (*gocloak.User, error) + GetUsers(organizationName string) ([]*gocloak.User, error) + DeleteUser(organizationName string, userAccountId string) error + UpdateUser(organizationName string, user *gocloak.User) error - GetAccessTokenByIdPassword(accountId string, password string, organizationName string) (*domain.User, error) VerifyAccessToken(token string, organizationName string) error - ParseAccessToken(token string, organization string) (*jwt.Token, *jwt.MapClaims, error) } - type Keycloak struct { config *Config client *gocloak.GoCloak } -func (c *Keycloak) LoginAdmin() (string, error) { - token, err := c.client.LoginAdmin(context.Background(), c.config.AdminId, c.config.AdminPassword, DefaultMasterRealm) +func (k *Keycloak) LoginAdmin(accountId string, password string) (*domain.User, error) { + ctx := context.Background() + log.Info("LoginAdmin called") + //JWTToken, err := k.client.Login(ctx, "admin-cli", "", DefaultMasterRealm, accountId, password) + JWTToken, err := k.client.LoginAdmin(ctx, accountId, password, DefaultMasterRealm) if err != nil { - return "", err + log.Error(err) + return nil, err } - return token.AccessToken, nil + return &domain.User{Token: JWTToken.AccessToken}, nil +} + +func (k *Keycloak) Login(accountId string, password string, organizationId string) (*domain.User, error) { + ctx := context.Background() + JWTToken, err := k.client.Login(ctx, DefaultClientID, DefaultClientSecret, organizationId, accountId, password) + if err != nil { + log.Error(err) + return nil, err + } + return &domain.User{Token: JWTToken.AccessToken}, nil } func New(config *Config) IKeycloak { @@ -55,56 +64,69 @@ func New(config *Config) IKeycloak { config: config, } } -func (c *Keycloak) InitializeKeycloak() error { - c.client = gocloak.NewClient(c.config.Address) +func (k *Keycloak) InitializeKeycloak() error { + k.client = gocloak.NewClient(k.config.Address) ctx := context.Background() - restyClient := c.client.RestyClient() - //for debugging - - //if os.Getenv("LOG_LEVEL") == "DEBUG" { - // restyClient.SetDebug(true) - //} - + restyClient := k.client.RestyClient() restyClient.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) - token, err := c.loginAdmin(ctx) + token, err := k.loginAdmin(ctx) if err != nil { log.Fatal(err) return err } - group, err := c.ensureGroupByName(ctx, token, DefaultMasterRealm, "tks-admin@master") + group, err := k.ensureGroupByName(ctx, token, DefaultMasterRealm, "tks-admin@master") if err != nil { log.Fatal(err) return err } - user, err := c.ensureUserByName(ctx, token, DefaultMasterRealm, c.config.AdminId, c.config.AdminPassword) + user, err := k.ensureUserByName(ctx, token, DefaultMasterRealm, k.config.AdminId, k.config.AdminPassword) if err != nil { log.Fatal(err) return err } - if err := c.addUserToGroup(ctx, token, DefaultMasterRealm, *user.ID, *group.ID); err != nil { + if err := k.addUserToGroup(ctx, token, DefaultMasterRealm, *user.ID, *group.ID); err != nil { log.Fatal(err) return err } - keycloakClient, err := c.ensureClient(ctx, token, DefaultMasterRealm, DefaultClientID, DefaultClientSecret) + keycloakClient, err := k.ensureClient(ctx, token, DefaultMasterRealm, DefaultClientID, DefaultClientSecret) if err != nil { log.Fatal(err) return err } for _, defaultMapper := range defaultProtocolTksMapper { - if err := c.ensureClientProtocolMappers(ctx, token, DefaultMasterRealm, *keycloakClient.ClientID, "openid", defaultMapper); err != nil { + if err := k.ensureClientProtocolMappers(ctx, token, DefaultMasterRealm, *keycloakClient.ClientID, "openid", defaultMapper); err != nil { log.Fatal(err) return err } } - if _, err := c.client.Login(ctx, DefaultClientID, DefaultClientSecret, DefaultMasterRealm, - c.config.AdminId, c.config.AdminPassword); err != nil { + if _, err := k.client.Login(ctx, DefaultClientID, DefaultClientSecret, DefaultMasterRealm, + k.config.AdminId, k.config.AdminPassword); err != nil { + log.Fatal(err) + return err + } + + adminCliClient, err := k.ensureClient(ctx, token, DefaultMasterRealm, AdminCliClientID, DefaultClientSecret) + if err != nil { + log.Fatal(err) + return err + } + + for _, defaultMapper := range defaultProtocolTksMapper { + if err := k.ensureClientProtocolMappers(ctx, token, DefaultMasterRealm, *adminCliClient.ClientID, "openid", defaultMapper); err != nil { + log.Fatal(err) + return err + } + } + + if _, err := k.client.Login(ctx, AdminCliClientID, DefaultClientSecret, DefaultMasterRealm, + k.config.AdminId, k.config.AdminPassword); err != nil { log.Fatal(err) return err } @@ -112,31 +134,36 @@ func (c *Keycloak) InitializeKeycloak() error { return nil } -func (k *Keycloak) CreateRealm(organizationName string, organizationConfig domain.Organization, accessToken string) (string, error) { +func (k *Keycloak) CreateRealm(organizationName string) (string, error) { //TODO implement me ctx := context.Background() + token, err := k.loginAdmin(ctx) + if err != nil { + return "", err + } + accessToken := token.AccessToken realmConfig := gocloak.RealmRepresentation{ Realm: &organizationName, Enabled: gocloak.BoolP(true), - AccessTokenLifespan: gocloak.IntP(60 * 60 * 24), + AccessTokenLifespan: gocloak.IntP(accessTokenLifespan), } realmUUID, err := k.client.CreateRealm(ctx, accessToken, realmConfig) if err != nil { - return realmUUID, err + return "", err } + // After Create Realm, accesstoken got changed so that old token doesn't work properly. - token, err := k.loginAdmin(ctx) + token, err = k.loginAdmin(ctx) if err != nil { - return realmUUID, err + return "", err } accessToken = token.AccessToken - time.Sleep(time.Second * 3) clientUUID, err := k.createDefaultClient(context.Background(), accessToken, organizationName, DefaultClientID, DefaultClientSecret) if err != nil { log.Error(err, "createDefaultClient") - return realmUUID, err + return "", err } token, err = k.loginAdmin(ctx) @@ -157,7 +184,7 @@ func (k *Keycloak) CreateRealm(organizationName string, organizationConfig domai } } if _, err := k.createClientProtocolMapper(ctx, accessToken, organizationName, clientUUID, defaultMapper); err != nil { - return realmUUID, err + return "", err } } adminGroupUuid, err := k.createGroup(ctx, accessToken, organizationName, "admin@"+organizationName) @@ -192,18 +219,18 @@ func (k *Keycloak) CreateRealm(organizationName string, organizationConfig domai }) if err != nil { - return realmUUID, err + return "", err } userGroupUuid, err := k.createGroup(ctx, accessToken, organizationName, "user@"+organizationName) if err != nil { - return realmUUID, err + return "", err } //adminGroup, err := k.ensureGroup(ctx, accessToken, organizationName, "admin@"+organizationName) viewUserRole, err := k.getClientRole(ctx, accessToken, organizationName, realmManagementClientUuid, "view-users") if err != nil { - return realmUUID, err + return "", err } err = k.addClientRoleToGroup(ctx, accessToken, organizationName, realmManagementClientUuid, userGroupUuid, @@ -213,7 +240,7 @@ func (k *Keycloak) CreateRealm(organizationName string, organizationConfig domai }) if err != nil { - return realmUUID, err + return "", err } // TODO: implement leader, member, viewer @@ -224,18 +251,26 @@ func (k *Keycloak) CreateRealm(organizationName string, organizationConfig domai return realmUUID, nil } -func (k *Keycloak) GetRealm(organizationName string, accessToken string) (*domain.Organization, error) { +func (k *Keycloak) GetRealm(organizationName string) (*domain.Organization, error) { ctx := context.Background() - realm, err := k.client.GetRealm(ctx, accessToken, organizationName) + token, err := k.loginAdmin(ctx) + if err != nil { + return nil, err + } + realm, err := k.client.GetRealm(ctx, token.AccessToken, organizationName) if err != nil { return nil, err } return k.reflectOrganization(*realm), nil } -func (k *Keycloak) GetRealms(accessToken string) ([]*domain.Organization, error) { +func (k *Keycloak) GetRealms() ([]*domain.Organization, error) { ctx := context.Background() - realms, err := k.client.GetRealms(ctx, accessToken) + token, err := k.loginAdmin(ctx) + if err != nil { + return nil, err + } + realms, err := k.client.GetRealms(ctx, token.AccessToken) if err != nil { return nil, err } @@ -246,39 +281,56 @@ func (k *Keycloak) GetRealms(accessToken string) ([]*domain.Organization, error) return organization, nil } -func (k *Keycloak) UpdateRealm(organizationName string, organizationConfig domain.Organization, accessToken string) error { +func (k *Keycloak) UpdateRealm(organizationName string, organizationConfig domain.Organization) error { ctx := context.Background() + token, err := k.loginAdmin(ctx) + if err != nil { + return err + } realm := k.reflectRealmRepresentation(organizationConfig) - err := k.client.UpdateRealm(ctx, accessToken, *realm) + err = k.client.UpdateRealm(ctx, token.AccessToken, *realm) if err != nil { return err } return nil } -func (k *Keycloak) DeleteRealm(organizationName string, accessToken string) error { +func (k *Keycloak) DeleteRealm(organizationName string) error { ctx := context.Background() - err := k.client.DeleteRealm(ctx, accessToken, organizationName) + token, err := k.loginAdmin(ctx) + if err != nil { + return err + } + err = k.client.DeleteRealm(ctx, token.AccessToken, organizationName) if err != nil { return err } return nil } -func (k *Keycloak) CreateUser(organizationName string, user *gocloak.User, accessToken string) error { +func (k *Keycloak) CreateUser(organizationName string, user *gocloak.User) error { ctx := context.Background() + token, err := k.loginAdmin(ctx) + if err != nil { + return err + } user.Enabled = gocloak.BoolP(true) - _, err := k.client.CreateUser(ctx, accessToken, organizationName, *user) + _, err = k.client.CreateUser(ctx, token.AccessToken, organizationName, *user) if err != nil { return err } return nil } -func (k *Keycloak) GetUser(organizationName string, accountId string, accessToken string) (*gocloak.User, error) { +func (k *Keycloak) GetUser(organizationName string, accountId string) (*gocloak.User, error) { ctx := context.Background() + token, err := k.loginAdmin(ctx) + if err != nil { + return nil, err + } + //TODO: this is rely on the fact that username is the same as userAccountId and unique - users, err := k.client.GetUsers(ctx, accessToken, organizationName, gocloak.GetUsersParams{ + users, err := k.client.GetUsers(ctx, token.AccessToken, organizationName, gocloak.GetUsersParams{ Username: gocloak.StringP(accountId), }) if err != nil { @@ -290,10 +342,14 @@ func (k *Keycloak) GetUser(organizationName string, accountId string, accessToke return users[0], nil } -func (k *Keycloak) GetUsers(organizationName string, accessToken string) ([]*gocloak.User, error) { +func (k *Keycloak) GetUsers(organizationName string) ([]*gocloak.User, error) { ctx := context.Background() + token, err := k.loginAdmin(ctx) + if err != nil { + return nil, err + } //TODO: this is rely on the fact that username is the same as userAccountId and unique - users, err := k.client.GetUsers(ctx, accessToken, organizationName, gocloak.GetUsersParams{}) + users, err := k.client.GetUsers(ctx, token.AccessToken, organizationName, gocloak.GetUsersParams{}) if err != nil { return nil, err } @@ -304,47 +360,40 @@ func (k *Keycloak) GetUsers(organizationName string, accessToken string) ([]*goc return users, nil } -func (k *Keycloak) UpdateUser(organizationName string, user *gocloak.User, accessToken string) error { +func (k *Keycloak) UpdateUser(organizationName string, user *gocloak.User) error { ctx := context.Background() + token, err := k.loginAdmin(ctx) + if err != nil { + return err + } user.Enabled = gocloak.BoolP(true) - err := k.client.UpdateUser(ctx, accessToken, organizationName, *user) + err = k.client.UpdateUser(ctx, token.AccessToken, organizationName, *user) if err != nil { return err } return nil } -func (k *Keycloak) DeleteUser(organizationName string, userAccountId string, accessToken string) error { +func (k *Keycloak) DeleteUser(organizationName string, userAccountId string) error { ctx := context.Background() - u, err := k.GetUser(organizationName, userAccountId, accessToken) + token, err := k.loginAdmin(ctx) + if err != nil { + return err + } + u, err := k.GetUser(organizationName, userAccountId) if err != nil { log.Errorf("error is :%s(%T)", err.Error(), err) return httpErrors.NewNotFoundError(err) } - err = k.client.DeleteUser(ctx, accessToken, organizationName, *u.ID) + err = k.client.DeleteUser(ctx, token.AccessToken, organizationName, *u.ID) if err != nil { return err } return nil } -func (k *Keycloak) GetAccessTokenByIdPassword(accountId string, password string, organizationName string) (*domain.User, error) { - ctx := context.Background() - JWTToken, err := k.client.Login(ctx, DefaultClientID, DefaultClientSecret, organizationName, accountId, password) - if err != nil { - log.Error(err) - return nil, err - } - return &domain.User{ - Token: JWTToken.AccessToken, - }, nil -} - func (k *Keycloak) VerifyAccessToken(token string, organizationName string) error { - - //TODO implement me ctx := context.Background() - //log.Info(token) rptResult, err := k.client.RetrospectToken(ctx, token, DefaultClientID, DefaultClientSecret, organizationName) if err != nil { return err @@ -356,18 +405,8 @@ func (k *Keycloak) VerifyAccessToken(token string, organizationName string) erro return nil } -func (k *Keycloak) ParseAccessToken(token string, organization string) (*jwt.Token, *jwt.MapClaims, error) { - ctx := context.Background() - jwtToken, mapClaim, err := k.client.DecodeAccessToken(ctx, token, organization) - if err != nil { - fmt.Println(err) - } - - return jwtToken, mapClaim, err -} - -func (c *Keycloak) loginAdmin(ctx context.Context) (*gocloak.JWT, error) { - token, err := c.client.LoginAdmin(ctx, c.config.AdminId, c.config.AdminPassword, DefaultMasterRealm) +func (k *Keycloak) loginAdmin(ctx context.Context) (*gocloak.JWT, error) { + token, err := k.client.LoginAdmin(ctx, k.config.AdminId, k.config.AdminPassword, DefaultMasterRealm) if err != nil { log.Error("Login to keycloak as Admin is failed", err) } @@ -375,10 +414,10 @@ func (c *Keycloak) loginAdmin(ctx context.Context) (*gocloak.JWT, error) { return token, err } -func (c *Keycloak) ensureClientProtocolMappers(ctx context.Context, token *gocloak.JWT, realm string, clientId string, +func (k *Keycloak) ensureClientProtocolMappers(ctx context.Context, token *gocloak.JWT, realm string, clientId string, scope string, mapper gocloak.ProtocolMapperRepresentation) error { //TODO: Check current logic(if exist, do nothing) is fine - clients, err := c.client.GetClients(ctx, token.AccessToken, realm, gocloak.GetClientsParams{ + clients, err := k.client.GetClients(ctx, token.AccessToken, realm, gocloak.GetClientsParams{ ClientID: &clientId, }) if err != nil { @@ -394,15 +433,15 @@ func (c *Keycloak) ensureClientProtocolMappers(ctx context.Context, token *goclo } } - if _, err := c.client.CreateClientProtocolMapper(ctx, token.AccessToken, realm, *clients[0].ID, mapper); err != nil { + if _, err := k.client.CreateClientProtocolMapper(ctx, token.AccessToken, realm, *clients[0].ID, mapper); err != nil { log.Error("Creating Client Protocol Mapper is failed", err) return err } return nil } -func (c *Keycloak) ensureClient(ctx context.Context, token *gocloak.JWT, realm string, clientId string, secret string) (*gocloak.Client, error) { - keycloakClient, err := c.client.GetClients(ctx, token.AccessToken, realm, gocloak.GetClientsParams{ +func (k *Keycloak) ensureClient(ctx context.Context, token *gocloak.JWT, realm string, clientId string, secret string) (*gocloak.Client, error) { + keycloakClient, err := k.client.GetClients(ctx, token.AccessToken, realm, gocloak.GetClientsParams{ ClientID: &clientId, }) if err != nil { @@ -410,7 +449,7 @@ func (c *Keycloak) ensureClient(ctx context.Context, token *gocloak.JWT, realm s } if len(keycloakClient) == 0 { - _, err = c.client.CreateClient(ctx, token.AccessToken, realm, gocloak.Client{ + _, err = k.client.CreateClient(ctx, token.AccessToken, realm, gocloak.Client{ ClientID: gocloak.StringP(clientId), Enabled: gocloak.BoolP(true), DirectAccessGrantsEnabled: gocloak.BoolP(true), @@ -418,26 +457,26 @@ func (c *Keycloak) ensureClient(ctx context.Context, token *gocloak.JWT, realm s if err != nil { log.Error("Creating Client is failed", err) } - keycloakClient, err = c.client.GetClients(ctx, token.AccessToken, realm, gocloak.GetClientsParams{ + keycloakClient, err = k.client.GetClients(ctx, token.AccessToken, realm, gocloak.GetClientsParams{ ClientID: &clientId, }) if err != nil { log.Error("Getting Client is failed", err) } } - if *keycloakClient[0].Secret != secret { + if keycloakClient[0].Secret == nil || *keycloakClient[0].Secret != secret { log.Warn("Client secret is not matched. Overwrite it") keycloakClient[0].Secret = gocloak.StringP(secret) - if err := c.client.UpdateClient(ctx, token.AccessToken, realm, *keycloakClient[0]); err != nil { + if err := k.client.UpdateClient(ctx, token.AccessToken, realm, *keycloakClient[0]); err != nil { log.Error("Updating Client is failed", err) } } - return keycloakClient[0], err + return keycloakClient[0], nil } -func (c *Keycloak) addUserToGroup(ctx context.Context, token *gocloak.JWT, realm string, userID string, groupID string) error { - groups, err := c.client.GetUserGroups(ctx, token.AccessToken, realm, userID, gocloak.GetGroupsParams{}) +func (k *Keycloak) addUserToGroup(ctx context.Context, token *gocloak.JWT, realm string, userID string, groupID string) error { + groups, err := k.client.GetUserGroups(ctx, token.AccessToken, realm, userID, gocloak.GetGroupsParams{}) if err != nil { log.Error("Getting User Groups is failed") } @@ -447,23 +486,23 @@ func (c *Keycloak) addUserToGroup(ctx context.Context, token *gocloak.JWT, realm } } - err = c.client.AddUserToGroup(ctx, token.AccessToken, realm, userID, groupID) + err = k.client.AddUserToGroup(ctx, token.AccessToken, realm, userID, groupID) if err != nil { log.Error("Assigning User to Group is failed", err) } return err } -func (c *Keycloak) ensureUserByName(ctx context.Context, token *gocloak.JWT, realm string, userName string, password string) (*gocloak.User, error) { - user, err := c.ensureUser(ctx, token, realm, userName, password) +func (k *Keycloak) ensureUserByName(ctx context.Context, token *gocloak.JWT, realm string, userName string, password string) (*gocloak.User, error) { + user, err := k.ensureUser(ctx, token, realm, userName, password) return user, err } -func (c *Keycloak) ensureUser(ctx context.Context, token *gocloak.JWT, realm string, userName string, password string) (*gocloak.User, error) { +func (k *Keycloak) ensureUser(ctx context.Context, token *gocloak.JWT, realm string, userName string, password string) (*gocloak.User, error) { searchParam := gocloak.GetUsersParams{ Search: gocloak.StringP(userName), } - users, err := c.client.GetUsers(ctx, token.AccessToken, realm, searchParam) + users, err := k.client.GetUsers(ctx, token.AccessToken, realm, searchParam) if err != nil { log.Error("Getting User is failed", err) } @@ -479,12 +518,12 @@ func (c *Keycloak) ensureUser(ctx context.Context, token *gocloak.JWT, realm str }, }, } - _, err = c.client.CreateUser(ctx, token.AccessToken, realm, user) + _, err = k.client.CreateUser(ctx, token.AccessToken, realm, user) if err != nil { log.Error("Creating User is failed", err) } - users, err = c.client.GetUsers(ctx, token.AccessToken, realm, searchParam) + users, err = k.client.GetUsers(ctx, token.AccessToken, realm, searchParam) if err != nil { log.Error("Getting User is failed", err) } @@ -493,12 +532,12 @@ func (c *Keycloak) ensureUser(ctx context.Context, token *gocloak.JWT, realm str return users[0], err } -func (c *Keycloak) ensureGroupByName(ctx context.Context, token *gocloak.JWT, realm string, groupName string, groupParam ...gocloak.Group) (*gocloak.Group, error) { - group, err := c.ensureGroup(ctx, token, realm, groupName) +func (k *Keycloak) ensureGroupByName(ctx context.Context, token *gocloak.JWT, realm string, groupName string, groupParam ...gocloak.Group) (*gocloak.Group, error) { + group, err := k.ensureGroup(ctx, token, realm, groupName) return group, err } -func (c *Keycloak) ensureGroup(ctx context.Context, token *gocloak.JWT, realm string, groupName string) (*gocloak.Group, error) { +func (k *Keycloak) ensureGroup(ctx context.Context, token *gocloak.JWT, realm string, groupName string) (*gocloak.Group, error) { searchParam := gocloak.GetGroupsParams{ Search: gocloak.StringP(groupName), } @@ -506,16 +545,16 @@ func (c *Keycloak) ensureGroup(ctx context.Context, token *gocloak.JWT, realm st Name: gocloak.StringP(groupName), } - groups, err := c.client.GetGroups(ctx, token.AccessToken, realm, searchParam) + groups, err := k.client.GetGroups(ctx, token.AccessToken, realm, searchParam) if err != nil { log.Error("Getting Group is failed", err) } if len(groups) == 0 { - _, err = c.client.CreateGroup(ctx, token.AccessToken, realm, groupParam) + _, err = k.client.CreateGroup(ctx, token.AccessToken, realm, groupParam) if err != nil { log.Error("Creating Group is failed", err) } - groups, err = c.client.GetGroups(ctx, token.AccessToken, realm, searchParam) + groups, err = k.client.GetGroups(ctx, token.AccessToken, realm, searchParam) if err != nil { log.Error("Getting Group is failed", err) } diff --git a/internal/middleware/auth/authenticator/authenticator.go b/internal/middleware/auth/authenticator/authenticator.go new file mode 100644 index 00000000..892b3834 --- /dev/null +++ b/internal/middleware/auth/authenticator/authenticator.go @@ -0,0 +1,42 @@ +package authenticator + +import ( + internalHttp "github.com/openinfradev/tks-api/internal/delivery/http" + "github.com/openinfradev/tks-api/internal/middleware/auth/request" + "github.com/openinfradev/tks-api/internal/middleware/auth/user" + "github.com/openinfradev/tks-api/pkg/httpErrors" + "net/http" +) + +type Interface interface { + WithAuthentication(handler http.Handler) http.Handler +} +type Request interface { + AuthenticateRequest(req *http.Request) (*Response, bool, error) +} + +type defaultAuthenticator struct { + auth Request +} + +func NewAuthenticator(kc Request) *defaultAuthenticator { + return &defaultAuthenticator{ + auth: kc, + } +} + +func (a *defaultAuthenticator) WithAuthentication(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + resp, ok, err := a.auth.AuthenticateRequest(r) + if !ok { + internalHttp.ErrorJSON(w, httpErrors.NewUnauthorizedError(err)) + return + } + r = r.WithContext(request.WithUser(r.Context(), resp.User)) + handler.ServeHTTP(w, r) + }) +} + +type Response struct { + User user.Info +} diff --git a/internal/middleware/auth/authenticator/keycloak/keycloak.go b/internal/middleware/auth/authenticator/keycloak/keycloak.go new file mode 100644 index 00000000..cc902d1a --- /dev/null +++ b/internal/middleware/auth/authenticator/keycloak/keycloak.go @@ -0,0 +1,94 @@ +package keycloak + +import ( + "fmt" + jwtWithouKey "github.com/dgrijalva/jwt-go" + "github.com/google/uuid" + "github.com/openinfradev/tks-api/internal/keycloak" + "github.com/openinfradev/tks-api/internal/middleware/auth/authenticator" + "github.com/openinfradev/tks-api/internal/middleware/auth/request" + "github.com/openinfradev/tks-api/internal/middleware/auth/user" + "github.com/openinfradev/tks-api/pkg/log" + "net/http" + "strings" +) + +type keycloakAuthenticator struct { + kc keycloak.IKeycloak +} + +func NewKeycloakAuthenticator(kc keycloak.IKeycloak) *keycloakAuthenticator { + return &keycloakAuthenticator{ + kc: kc, + } +} + +func (a *keycloakAuthenticator) AuthenticateRequest(r *http.Request) (*authenticator.Response, bool, error) { + authHeader := strings.TrimSpace(r.Header.Get("Authorization")) + if authHeader == "" { + return nil, false, fmt.Errorf("authorizer header is invalid") + } + parts := strings.SplitN(authHeader, " ", 3) + if len(parts) < 2 || strings.ToLower(parts[0]) != "bearer" { + return nil, false, fmt.Errorf("authorizer header is invalid") + } + + token := parts[1] + + if len(token) == 0 { + // The space before the token case + if len(parts) == 3 { + log.Warn("the provided Authorization header contains extra space before the bearer token, and is ignored") + } + return nil, false, fmt.Errorf("token is empty") + } + + return a.AuthenticateToken(r, token) +} + +func (a *keycloakAuthenticator) AuthenticateToken(r *http.Request, token string) (*authenticator.Response, bool, error) { + parsedToken, _, err := new(jwtWithouKey.Parser).ParseUnverified(token, jwtWithouKey.MapClaims{}) + if err != nil { + return nil, false, err + } + + if parsedToken.Method.Alg() != "RS256" { + return nil, false, fmt.Errorf("invalid token") + } + + if parsedToken.Claims.Valid() != nil { + return nil, false, fmt.Errorf("invalid token") + } + + organizationId, ok := parsedToken.Claims.(jwtWithouKey.MapClaims)["organization"].(string) + if !ok { + return nil, false, fmt.Errorf("organization is not found in token") + } + if err := a.kc.VerifyAccessToken(token, organizationId); err != nil { + log.Errorf("failed to verify access token: %v", err) + return nil, false, err + } + + roleProjectMapping := make(map[string]string) + for _, role := range parsedToken.Claims.(jwtWithouKey.MapClaims)["tks-role"].([]interface{}) { + slice := strings.Split(role.(string), "@") + if len(slice) != 2 { + return nil, false, fmt.Errorf("invalid tks-role format") + } + // key is projectName and value is roleName + roleProjectMapping[slice[1]] = slice[0] + } + userAccountId, err := uuid.Parse(parsedToken.Claims.(jwtWithouKey.MapClaims)["sub"].(string)) + if err != nil { + return nil, false, err + } + + userInfo := &user.DefaultInfo{ + UserId: userAccountId, + OrganizationId: organizationId, + RoleProjectMapping: roleProjectMapping, + } + *r = *(r.WithContext(request.WithToken(r.Context(), token))) + + return &authenticator.Response{User: userInfo}, true, nil +} diff --git a/internal/middleware/auth/authorizer/authorizer.go b/internal/middleware/auth/authorizer/authorizer.go new file mode 100644 index 00000000..df237fce --- /dev/null +++ b/internal/middleware/auth/authorizer/authorizer.go @@ -0,0 +1,25 @@ +package authorizer + +import ( + "github.com/openinfradev/tks-api/pkg/log" + "net/http" +) + +type Interface interface { + WithAuthorization(handler http.Handler) http.Handler +} + +type defaultAuthorization struct { +} + +func NewDefaultAuthorization() *defaultAuthorization { + return &defaultAuthorization{} +} + +func (a *defaultAuthorization) WithAuthorization(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Info("called Authorization") + + handler.ServeHTTP(w, r) + }) +} diff --git a/internal/middleware/auth/middleware.go b/internal/middleware/auth/middleware.go new file mode 100644 index 00000000..f9e6e977 --- /dev/null +++ b/internal/middleware/auth/middleware.go @@ -0,0 +1,27 @@ +package auth + +import ( + "github.com/openinfradev/tks-api/internal/middleware/auth/authenticator" + "github.com/openinfradev/tks-api/internal/middleware/auth/authorizer" + "net/http" +) + +type authMiddleware struct { + authenticator authenticator.Interface + authorizer authorizer.Interface +} + +func NewAuthMiddleware(authenticator authenticator.Interface, + authorizer authorizer.Interface) *authMiddleware { + ret := &authMiddleware{ + authenticator: authenticator, + authorizer: authorizer, + } + return ret +} + +func (m *authMiddleware) Handle(handle http.Handler) http.Handler { + handler := m.authorizer.WithAuthorization(handle) + handler = m.authenticator.WithAuthentication(handler) + return handler +} diff --git a/internal/auth/request/context.go b/internal/middleware/auth/request/context.go similarity index 92% rename from internal/auth/request/context.go rename to internal/middleware/auth/request/context.go index 4ef49334..75658148 100644 --- a/internal/auth/request/context.go +++ b/internal/middleware/auth/request/context.go @@ -2,7 +2,7 @@ package request import ( "context" - "github.com/openinfradev/tks-api/internal/auth/user" + "github.com/openinfradev/tks-api/internal/middleware/auth/user" ) type key int diff --git a/internal/auth/user/user.go b/internal/middleware/auth/user/user.go similarity index 100% rename from internal/auth/user/user.go rename to internal/middleware/auth/user/user.go diff --git a/internal/repository/app-serve-app.go b/internal/repository/app-serve-app.go index 046ba614..8af3a6a7 100644 --- a/internal/repository/app-serve-app.go +++ b/internal/repository/app-serve-app.go @@ -27,8 +27,7 @@ func NewAppServeAppRepository(db *gorm.DB) IAppServeAppRepository { } } -func (r *AppServeAppRepository) CreateAppServeApp( - app *domain.AppServeApp) (appId string, taskId string, err error) { +func (r *AppServeAppRepository) CreateAppServeApp(app *domain.AppServeApp) (appId string, taskId string, err error) { res := r.db.Create(&app) if res.Error != nil { @@ -41,7 +40,6 @@ func (r *AppServeAppRepository) CreateAppServeApp( // Update creates new appServeApp Task for existing appServeApp. func (r *AppServeAppRepository) CreateTask( task *domain.AppServeAppTask) (string, error) { - res := r.db.Create(task) if res.Error != nil { return "", res.Error diff --git a/internal/repository/cluster.go b/internal/repository/cluster.go index 54609444..1556fea8 100644 --- a/internal/repository/cluster.go +++ b/internal/repository/cluster.go @@ -199,7 +199,6 @@ func (r *ClusterRepository) InitWorkflow(clusterId domain.ClusterId, workflowId } return nil - } func reflectCluster(cluster Cluster) domain.Cluster { diff --git a/internal/repository/user.go b/internal/repository/user.go index cdf68926..4b08c2f5 100644 --- a/internal/repository/user.go +++ b/internal/repository/user.go @@ -11,9 +11,9 @@ import ( // Interface type IUserRepository interface { - Create(accountId string, organizationId string, paasword string, name string) (domain.User, error) + Create(accountId string, organizationId string, password string, name string) (domain.User, error) CreateWithUuid(uuid uuid.UUID, accountId string, name string, password string, email string, - department string, description string, orgainzationId string, roleId uuid.UUID) (domain.User, error) + department string, description string, organizationId string, roleId uuid.UUID) (domain.User, error) List(...FilterFunc) (out *[]domain.User, err error) Get(accountId string, organizationId string) (domain.User, error) GetByUuid(userId uuid.UUID) (domain.User, error) diff --git a/internal/route/route.go b/internal/route/route.go index 4b8e3a45..f56d4a1d 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -6,27 +6,21 @@ import ( "io" "io/ioutil" "net/http" - "strings" - "github.com/google/uuid" - "github.com/openinfradev/tks-api/internal/auth/request" - user "github.com/openinfradev/tks-api/internal/auth/user" - "github.com/openinfradev/tks-api/internal/keycloak" - - jwtWithouKey "github.com/dgrijalva/jwt-go" - "github.com/golang-jwt/jwt/v4" "github.com/gorilla/handlers" "github.com/gorilla/mux" - "gorm.io/gorm" - - "github.com/swaggo/http-swagger" - delivery "github.com/openinfradev/tks-api/internal/delivery/http" - "github.com/openinfradev/tks-api/internal/helper" + "github.com/openinfradev/tks-api/internal/keycloak" + "github.com/openinfradev/tks-api/internal/middleware/auth" + "github.com/openinfradev/tks-api/internal/middleware/auth/authenticator" + authKeycloak "github.com/openinfradev/tks-api/internal/middleware/auth/authenticator/keycloak" + "github.com/openinfradev/tks-api/internal/middleware/auth/authorizer" "github.com/openinfradev/tks-api/internal/repository" "github.com/openinfradev/tks-api/internal/usecase" argowf "github.com/openinfradev/tks-api/pkg/argo-client" "github.com/openinfradev/tks-api/pkg/log" + "github.com/swaggo/http-swagger" + "gorm.io/gorm" ) const ( @@ -46,13 +40,9 @@ func (r *StatusRecorder) WriteHeader(status int) { func SetupRouter(db *gorm.DB, argoClient argowf.ArgoClient, asset http.Handler, kc keycloak.IKeycloak) http.Handler { r := mux.NewRouter() - - r.Use(loggingMiddleware) - - // [TODO] Transaction - //r.Use(transactionMiddleware(db)) - - //r.PathPrefix("/swagger").Handler(httpSwagger.WrapHandler) + authMiddleware := auth.NewAuthMiddleware( + authenticator.NewAuthenticator(authKeycloak.NewKeycloakAuthenticator(kc)), + authorizer.NewDefaultAuthorization()) repoFactory := repository.Repository{ User: repository.NewUserRepository(db), @@ -64,78 +54,82 @@ func SetupRouter(db *gorm.DB, argoClient argowf.ArgoClient, asset http.Handler, StackTemplate: repository.NewStackTemplateRepository(db), History: repository.NewHistoryRepository(db), } - authHandler := delivery.NewAuthHandler(usecase.NewAuthUsecase(repoFactory, kc)) + + r.Use(loggingMiddleware) + // [TODO] Transaction + //r.Use(transactionMiddleware(db)) + r.HandleFunc(API_PREFIX+API_VERSION+"/auth/login", authHandler.Login).Methods(http.MethodPost) r.HandleFunc(API_PREFIX+API_VERSION+"/auth/logout", authHandler.Logout).Methods(http.MethodPost) r.HandleFunc(API_PREFIX+API_VERSION+"/auth/find-id", authHandler.FindId).Methods(http.MethodPost) r.HandleFunc(API_PREFIX+API_VERSION+"/auth/find-password", authHandler.FindPassword).Methods(http.MethodPost) userHandler := delivery.NewUserHandler(usecase.NewUserUsecase(repoFactory, kc)) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users", authMiddleware(http.HandlerFunc(userHandler.Create), kc)).Methods(http.MethodPost) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users", authMiddleware(http.HandlerFunc(userHandler.List), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}", authMiddleware(http.HandlerFunc(userHandler.Get), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}", authMiddleware(http.HandlerFunc(userHandler.Delete), kc)).Methods(http.MethodDelete) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}", authMiddleware(http.HandlerFunc(userHandler.Update), kc)).Methods(http.MethodPut) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}/password", authMiddleware(http.HandlerFunc(userHandler.UpdatePassword), kc)).Methods(http.MethodPut) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}/existence", authMiddleware(http.HandlerFunc(userHandler.CheckId), kc)).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users", authMiddleware.Handle(http.HandlerFunc(userHandler.Create))).Methods(http.MethodPost) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users", authMiddleware.Handle(http.HandlerFunc(userHandler.List))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}", authMiddleware.Handle(http.HandlerFunc(userHandler.Get))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}", authMiddleware.Handle(http.HandlerFunc(userHandler.Delete))).Methods(http.MethodDelete) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}", authMiddleware.Handle(http.HandlerFunc(userHandler.Update))).Methods(http.MethodPut) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}/password", authMiddleware.Handle(http.HandlerFunc(userHandler.UpdatePassword))).Methods(http.MethodPut) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}/existence", authMiddleware.Handle(http.HandlerFunc(userHandler.CheckId))).Methods(http.MethodGet) organizationHandler := delivery.NewOrganizationHandler(usecase.NewOrganizationUsecase(repoFactory, argoClient, kc), usecase.NewUserUsecase(repoFactory, kc)) - r.Handle(API_PREFIX+API_VERSION+"/organizations", authMiddleware(http.HandlerFunc(organizationHandler.CreateOrganization), kc)).Methods(http.MethodPost) - r.Handle(API_PREFIX+API_VERSION+"/organizations", authMiddleware(http.HandlerFunc(organizationHandler.GetOrganizations), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}", authMiddleware(http.HandlerFunc(organizationHandler.GetOrganization), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}", authMiddleware(http.HandlerFunc(organizationHandler.DeleteOrganization), kc)).Methods(http.MethodDelete) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}", authMiddleware(http.HandlerFunc(organizationHandler.UpdateOrganization), kc)).Methods(http.MethodPut) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/primary-cluster", authMiddleware(http.HandlerFunc(organizationHandler.UpdatePrimaryCluster), kc)).Methods(http.MethodPatch) + r.Handle(API_PREFIX+API_VERSION+"/organizations", authMiddleware.Handle(http.HandlerFunc(organizationHandler.CreateOrganization))).Methods(http.MethodPost) + r.Handle(API_PREFIX+API_VERSION+"/organizations", authMiddleware.Handle(http.HandlerFunc(organizationHandler.GetOrganizations))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}", authMiddleware.Handle(http.HandlerFunc(organizationHandler.GetOrganization))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}", authMiddleware.Handle(http.HandlerFunc(organizationHandler.DeleteOrganization))).Methods(http.MethodDelete) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}", authMiddleware.Handle(http.HandlerFunc(organizationHandler.UpdateOrganization))).Methods(http.MethodPut) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/primary-cluster", authMiddleware.Handle(http.HandlerFunc(organizationHandler.UpdatePrimaryCluster))).Methods(http.MethodPatch) clusterHandler := delivery.NewClusterHandler(usecase.NewClusterUsecase(repoFactory, argoClient)) - r.Handle(API_PREFIX+API_VERSION+"/clusters", authMiddleware(http.HandlerFunc(clusterHandler.CreateCluster), kc)).Methods(http.MethodPost) - r.Handle(API_PREFIX+API_VERSION+"/clusters", authMiddleware(http.HandlerFunc(clusterHandler.GetClusters), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/clusters/{clusterId}", authMiddleware(http.HandlerFunc(clusterHandler.GetCluster), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/clusters/{clusterId}", authMiddleware(http.HandlerFunc(clusterHandler.DeleteCluster), kc)).Methods(http.MethodDelete) + r.Handle(API_PREFIX+API_VERSION+"/clusters", authMiddleware.Handle(http.HandlerFunc(clusterHandler.CreateCluster))).Methods(http.MethodPost) + r.Handle(API_PREFIX+API_VERSION+"/clusters", authMiddleware.Handle(http.HandlerFunc(clusterHandler.GetClusters))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/clusters/{clusterId}", authMiddleware.Handle(http.HandlerFunc(clusterHandler.GetCluster))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/clusters/{clusterId}", authMiddleware.Handle(http.HandlerFunc(clusterHandler.DeleteCluster))).Methods(http.MethodDelete) appGroupHandler := delivery.NewAppGroupHandler(usecase.NewAppGroupUsecase(repoFactory, argoClient)) - r.Handle(API_PREFIX+API_VERSION+"/app-groups", authMiddleware(http.HandlerFunc(appGroupHandler.CreateAppGroup), kc)).Methods(http.MethodPost) - r.Handle(API_PREFIX+API_VERSION+"/app-groups", authMiddleware(http.HandlerFunc(appGroupHandler.GetAppGroups), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/app-groups/{appGroupId}", authMiddleware(http.HandlerFunc(appGroupHandler.GetAppGroup), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/app-groups/{appGroupId}", authMiddleware(http.HandlerFunc(appGroupHandler.DeleteAppGroup), kc)).Methods(http.MethodDelete) - r.Handle(API_PREFIX+API_VERSION+"/app-groups/{appGroupId}/applications", authMiddleware(http.HandlerFunc(appGroupHandler.GetApplications), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/app-groups/{appGroupId}/applications", authMiddleware(http.HandlerFunc(appGroupHandler.UpdateApplication), kc)).Methods(http.MethodPost) + r.Handle(API_PREFIX+API_VERSION+"/app-groups", authMiddleware.Handle(http.HandlerFunc(appGroupHandler.CreateAppGroup))).Methods(http.MethodPost) + r.Handle(API_PREFIX+API_VERSION+"/app-groups", authMiddleware.Handle(http.HandlerFunc(appGroupHandler.GetAppGroups))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/app-groups/{appGroupId}", authMiddleware.Handle(http.HandlerFunc(appGroupHandler.GetAppGroup))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/app-groups/{appGroupId}", authMiddleware.Handle(http.HandlerFunc(appGroupHandler.DeleteAppGroup))).Methods(http.MethodDelete) + r.Handle(API_PREFIX+API_VERSION+"/app-groups/{appGroupId}/applications", authMiddleware.Handle(http.HandlerFunc(appGroupHandler.GetApplications))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/app-groups/{appGroupId}/applications", authMiddleware.Handle(http.HandlerFunc(appGroupHandler.UpdateApplication))).Methods(http.MethodPost) appServeAppHandler := delivery.NewAppServeAppHandler(usecase.NewAppServeAppUsecase(repoFactory, argoClient)) - r.Handle(API_PREFIX+API_VERSION+"/app-serve-apps", authMiddleware(http.HandlerFunc(appServeAppHandler.CreateAppServeApp), kc)).Methods(http.MethodPost) - r.Handle(API_PREFIX+API_VERSION+"/app-serve-apps", authMiddleware(http.HandlerFunc(appServeAppHandler.GetAppServeApps), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/app-serve-apps/{appId}", authMiddleware(http.HandlerFunc(appServeAppHandler.GetAppServeApp), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/app-serve-apps/{appId}", authMiddleware(http.HandlerFunc(appServeAppHandler.DeleteAppServeApp), kc)).Methods(http.MethodDelete) - r.Handle(API_PREFIX+API_VERSION+"/app-serve-apps/{appId}", authMiddleware(http.HandlerFunc(appServeAppHandler.UpdateAppServeApp), kc)).Methods(http.MethodPut) - r.Handle(API_PREFIX+API_VERSION+"/app-serve-apps/{appId}/status", authMiddleware(http.HandlerFunc(appServeAppHandler.UpdateAppServeAppStatus), kc)).Methods(http.MethodPatch) - r.Handle(API_PREFIX+API_VERSION+"/app-serve-apps/{appId}/endpoint", authMiddleware(http.HandlerFunc(appServeAppHandler.UpdateAppServeAppEndpoint), kc)).Methods(http.MethodPatch) + r.Handle(API_PREFIX+API_VERSION+"/app-serve-apps", authMiddleware.Handle(http.HandlerFunc(appServeAppHandler.CreateAppServeApp))).Methods(http.MethodPost) + r.Handle(API_PREFIX+API_VERSION+"/app-serve-apps", authMiddleware.Handle(http.HandlerFunc(appServeAppHandler.GetAppServeApps))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/app-serve-apps/{appId}", authMiddleware.Handle(http.HandlerFunc(appServeAppHandler.GetAppServeApp))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/app-serve-apps/{appId}", authMiddleware.Handle(http.HandlerFunc(appServeAppHandler.DeleteAppServeApp))).Methods(http.MethodDelete) + r.Handle(API_PREFIX+API_VERSION+"/app-serve-apps/{appId}", authMiddleware.Handle(http.HandlerFunc(appServeAppHandler.UpdateAppServeApp))).Methods(http.MethodPut) + r.Handle(API_PREFIX+API_VERSION+"/app-serve-apps/{appId}/status", authMiddleware.Handle(http.HandlerFunc(appServeAppHandler.UpdateAppServeAppStatus))).Methods(http.MethodPatch) + r.Handle(API_PREFIX+API_VERSION+"/app-serve-apps/{appId}/endpoint", authMiddleware.Handle(http.HandlerFunc(appServeAppHandler.UpdateAppServeAppEndpoint))).Methods(http.MethodPatch) cloudAccountHandler := delivery.NewCloudAccountHandler(usecase.NewCloudAccountUsecase(repoFactory, argoClient)) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/cloud-accounts", authMiddleware(http.HandlerFunc(cloudAccountHandler.GetCloudAccounts), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/cloud-accounts", authMiddleware(http.HandlerFunc(cloudAccountHandler.CreateCloudAccount), kc)).Methods(http.MethodPost) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/cloud-accounts/name/{name}/existence", authMiddleware(http.HandlerFunc(cloudAccountHandler.CheckCloudAccountName), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/cloud-accounts/{cloudAccountId}", authMiddleware(http.HandlerFunc(cloudAccountHandler.GetCloudAccount), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/cloud-accounts/{cloudAccountId}", authMiddleware(http.HandlerFunc(cloudAccountHandler.UpdateCloudAccount), kc)).Methods(http.MethodPut) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/cloud-accounts/{cloudAccountId}", authMiddleware(http.HandlerFunc(cloudAccountHandler.DeleteCloudAccount), kc)).Methods(http.MethodDelete) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/cloud-accounts", authMiddleware.Handle(http.HandlerFunc(cloudAccountHandler.GetCloudAccounts))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/cloud-accounts", authMiddleware.Handle(http.HandlerFunc(cloudAccountHandler.CreateCloudAccount))).Methods(http.MethodPost) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/cloud-accounts/name/{name}/existence", authMiddleware.Handle(http.HandlerFunc(cloudAccountHandler.CheckCloudAccountName))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/cloud-accounts/{cloudAccountId}", authMiddleware.Handle(http.HandlerFunc(cloudAccountHandler.GetCloudAccount))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/cloud-accounts/{cloudAccountId}", authMiddleware.Handle(http.HandlerFunc(cloudAccountHandler.UpdateCloudAccount))).Methods(http.MethodPut) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/cloud-accounts/{cloudAccountId}", authMiddleware.Handle(http.HandlerFunc(cloudAccountHandler.DeleteCloudAccount))).Methods(http.MethodDelete) stackTemplateHandler := delivery.NewStackTemplateHandler(usecase.NewStackTemplateUsecase(repoFactory)) - r.Handle(API_PREFIX+API_VERSION+"/stack-templates", authMiddleware(http.HandlerFunc(stackTemplateHandler.GetStackTemplates), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/stack-templates", authMiddleware(http.HandlerFunc(stackTemplateHandler.CreateStackTemplate), kc)).Methods(http.MethodPost) - r.Handle(API_PREFIX+API_VERSION+"/stack-templates/{stackTemplateId}", authMiddleware(http.HandlerFunc(stackTemplateHandler.GetStackTemplate), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/stack-templates/{stackTemplateId}", authMiddleware(http.HandlerFunc(stackTemplateHandler.UpdateStackTemplate), kc)).Methods(http.MethodPut) - r.Handle(API_PREFIX+API_VERSION+"/stack-templates/{stackTemplateId}", authMiddleware(http.HandlerFunc(stackTemplateHandler.DeleteStackTemplate), kc)).Methods(http.MethodDelete) + r.Handle(API_PREFIX+API_VERSION+"/stack-templates", authMiddleware.Handle(http.HandlerFunc(stackTemplateHandler.GetStackTemplates))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/stack-templates", authMiddleware.Handle(http.HandlerFunc(stackTemplateHandler.CreateStackTemplate))).Methods(http.MethodPost) + r.Handle(API_PREFIX+API_VERSION+"/stack-templates/{stackTemplateId}", authMiddleware.Handle(http.HandlerFunc(stackTemplateHandler.GetStackTemplate))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/stack-templates/{stackTemplateId}", authMiddleware.Handle(http.HandlerFunc(stackTemplateHandler.UpdateStackTemplate))).Methods(http.MethodPut) + r.Handle(API_PREFIX+API_VERSION+"/stack-templates/{stackTemplateId}", authMiddleware.Handle(http.HandlerFunc(stackTemplateHandler.DeleteStackTemplate))).Methods(http.MethodDelete) stackHandler := delivery.NewStackHandler(usecase.NewStackUsecase(repoFactory, argoClient)) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/stacks", authMiddleware(http.HandlerFunc(stackHandler.GetStacks), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/stacks", authMiddleware(http.HandlerFunc(stackHandler.CreateStack), kc)).Methods(http.MethodPost) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/stacks/name/{name}/existence", authMiddleware(http.HandlerFunc(stackHandler.CheckStackName), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/stacks/{stackId}", authMiddleware(http.HandlerFunc(stackHandler.GetStack), kc)).Methods(http.MethodGet) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/stacks/{stackId}", authMiddleware(http.HandlerFunc(stackHandler.UpdateStack), kc)).Methods(http.MethodPut) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/stacks/{stackId}", authMiddleware(http.HandlerFunc(stackHandler.DeleteStack), kc)).Methods(http.MethodDelete) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/stacks", authMiddleware.Handle(http.HandlerFunc(stackHandler.GetStacks))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/stacks", authMiddleware.Handle(http.HandlerFunc(stackHandler.CreateStack))).Methods(http.MethodPost) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/stacks/name/{name}/existence", authMiddleware.Handle(http.HandlerFunc(stackHandler.CheckStackName))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/stacks/{stackId}", authMiddleware.Handle(http.HandlerFunc(stackHandler.GetStack))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/stacks/{stackId}", authMiddleware.Handle(http.HandlerFunc(stackHandler.UpdateStack))).Methods(http.MethodPut) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/stacks/{stackId}", authMiddleware.Handle(http.HandlerFunc(stackHandler.DeleteStack))).Methods(http.MethodDelete) dashboardHandler := delivery.NewDashboardHandler(usecase.NewDashboardUsecase(repoFactory)) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/dashboard/charts", authMiddleware(http.HandlerFunc(dashboardHandler.GetCharts), kc)).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/dashboard/charts", authMiddleware.Handle(http.HandlerFunc(dashboardHandler.GetCharts))).Methods(http.MethodGet) // assets r.PathPrefix("/api/").HandlerFunc(http.NotFound) @@ -151,142 +145,6 @@ func SetupRouter(db *gorm.DB, argoClient argowf.ArgoClient, asset http.Handler, return handlers.CORS(credentials, headersOk, originsOk, methodsOk)(r) } -func authMiddleware(next http.Handler, kc keycloak.IKeycloak) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Possible values : "basic", "keycloak" - authType := r.Header.Get("Authorization-Type") - - switch authType { - case "basic": - tokenString := r.Header.Get("Authorization") - if len(tokenString) == 0 { - w.WriteHeader(http.StatusUnauthorized) - if _, err := w.Write([]byte("Missing Authorization Header")); err != nil { - log.Error(err) - } - - return - } - tokenString = strings.Replace(tokenString, "Bearer ", "", 1) - token, err := helper.VerifyToken(tokenString) - if err != nil { - w.WriteHeader(http.StatusUnauthorized) - if _, err := w.Write([]byte("Error verifying JWT token: " + err.Error())); err != nil { - log.Error(err) - } - return - } - - accountId := token.Claims.(jwt.MapClaims)["AccountId"] - organizationId := token.Claims.(jwt.MapClaims)["OrganizationId"] - id := token.Claims.(jwt.MapClaims)["ID"] - - log.Debug("[authMiddleware] accountId : ", accountId) - log.Debug("[authMiddleware] Id : ", id) - log.Debug("[authMiddleware] organizationId : ", organizationId) - - r.Header.Set("OrganizationId", fmt.Sprint(organizationId)) - r.Header.Set("AccountId", fmt.Sprint(accountId)) - r.Header.Set("ID", fmt.Sprint(id)) - - next.ServeHTTP(w, r) - return - - case "keycloak": - default: - auth := strings.TrimSpace(r.Header.Get("Authorization")) - if auth == "" { - w.WriteHeader(http.StatusUnauthorized) - return - } - parts := strings.SplitN(auth, " ", 3) - if len(parts) < 2 || strings.ToLower(parts[0]) != "bearer" { - w.WriteHeader(http.StatusUnauthorized) - return - } - - token := parts[1] - - // Empty bearer tokens aren't valid - if len(token) == 0 { - // The space before the token case - if len(parts) == 3 { - log.Warn("the provided Authorization header contains extra space before the bearer token, and is ignored") - } - w.WriteHeader(http.StatusUnauthorized) - return - } - - parsedToken, _, err := new(jwtWithouKey.Parser).ParseUnverified(token, jwtWithouKey.MapClaims{}) - - if err != nil { - log.Error("failed to parse access token: ", err) - w.WriteHeader(http.StatusUnauthorized) - if _, err := w.Write([]byte(err.Error())); err != nil { - log.Error(err) - } - return - } - organization := parsedToken.Claims.(jwtWithouKey.MapClaims)["organization"].(string) - if err := kc.VerifyAccessToken(token, organization); err != nil { - log.Error("failed to verify access token: ", err) - w.WriteHeader(http.StatusUnauthorized) - if _, err := w.Write([]byte("failed to verify access token: " + err.Error())); err != nil { - log.Error(err) - } - return - } - jwtToken, mapClaims, err := kc.ParseAccessToken(token, organization) - if err != nil { - log.Error("failed to parse access token: ", err) - w.WriteHeader(http.StatusUnauthorized) - if _, err := w.Write([]byte(err.Error())); err != nil { - log.Error(err) - } - return - } - - if jwtToken == nil || mapClaims == nil || mapClaims.Valid() != nil { - w.WriteHeader(http.StatusUnauthorized) - if _, err := w.Write([]byte("Error message TODO")); err != nil { - log.Error(err) - } - return - } - roleProjectMapping := make(map[string]string) - for _, role := range jwtToken.Claims.(jwt.MapClaims)["tks-role"].([]interface{}) { - slice := strings.Split(role.(string), "@") - if len(slice) != 2 { - log.Error("invalid role format: ", role) - w.WriteHeader(http.StatusUnauthorized) - if _, err := w.Write([]byte(fmt.Sprintf("invalid role format: %s", role))); err != nil { - log.Error(err) - } - return - } - // key is projectName and value is roleName - roleProjectMapping[slice[1]] = slice[0] - } - userId, err := uuid.Parse(jwtToken.Claims.(jwt.MapClaims)["sub"].(string)) - if err != nil { - userId = uuid.Nil - } - - userInfo := &user.DefaultInfo{ - OrganizationId: jwtToken.Claims.(jwt.MapClaims)["organization"].(string), - UserId: userId, - RoleProjectMapping: roleProjectMapping, - } - - r = r.WithContext(request.WithToken(r.Context(), token)) - r = r.WithContext(request.WithUser(r.Context(), userInfo)) - - next.ServeHTTP(w, r) - } - - }) -} - func loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Info(fmt.Sprintf("***** START [%s %s] ***** ", r.Method, r.RequestURI)) diff --git a/internal/usecase/app-group.go b/internal/usecase/app-group.go index 71dfcb1e..111e917d 100644 --- a/internal/usecase/app-group.go +++ b/internal/usecase/app-group.go @@ -3,8 +3,8 @@ package usecase import ( "context" "fmt" + "github.com/openinfradev/tks-api/internal/middleware/auth/request" - "github.com/openinfradev/tks-api/internal/auth/request" "github.com/openinfradev/tks-api/internal/repository" argowf "github.com/openinfradev/tks-api/pkg/argo-client" "github.com/openinfradev/tks-api/pkg/domain" diff --git a/internal/usecase/app-serve-app.go b/internal/usecase/app-serve-app.go index 22c7a02b..195ccbb6 100644 --- a/internal/usecase/app-serve-app.go +++ b/internal/usecase/app-serve-app.go @@ -367,7 +367,6 @@ func (u *AppServeAppUsecase) PromoteAppServeApp(appId string) (ret string, err e return fmt.Sprintf("The app '%s' is being promoted. "+ "Confirm result by checking the app status after a while.", app.Name), nil - } func (u *AppServeAppUsecase) AbortAppServeApp(appId string) (ret string, err error) { diff --git a/internal/usecase/auth.go b/internal/usecase/auth.go index 0d18cb1c..a1b9a30f 100644 --- a/internal/usecase/auth.go +++ b/internal/usecase/auth.go @@ -32,14 +32,18 @@ func (r *AuthUsecase) Login(accountId string, password string, organizationId st // Authentication with DB user, err := r.repo.Get(accountId, organizationId) if err != nil { - return domain.User{}, err + return domain.User{}, httpErrors.NewUnauthorizedError(err) } if !helper.CheckPasswordHash(user.Password, password) { - return domain.User{}, httpErrors.NewUnauthorizedError(fmt.Errorf("password is not correct")) + return domain.User{}, httpErrors.NewUnauthorizedError(fmt.Errorf("")) } - + var accountToken *domain.User // Authentication with Keycloak - accountToken, err := r.kc.GetAccessTokenByIdPassword(accountId, password, organizationId) + if organizationId == "master" && accountId == "admin" { + accountToken, err = r.kc.LoginAdmin(accountId, password) + } else { + accountToken, err = r.kc.Login(accountId, password, organizationId) + } if err != nil { //TODO: implement not found handling return domain.User{}, httpErrors.NewUnauthorizedError(err) diff --git a/internal/usecase/cloud-account.go b/internal/usecase/cloud-account.go index 9adf84c3..604f8872 100644 --- a/internal/usecase/cloud-account.go +++ b/internal/usecase/cloud-account.go @@ -3,9 +3,9 @@ package usecase import ( "context" "fmt" + "github.com/openinfradev/tks-api/internal/middleware/auth/request" "github.com/google/uuid" - "github.com/openinfradev/tks-api/internal/auth/request" "github.com/openinfradev/tks-api/internal/repository" argowf "github.com/openinfradev/tks-api/pkg/argo-client" "github.com/openinfradev/tks-api/pkg/domain" diff --git a/internal/usecase/cluster.go b/internal/usecase/cluster.go index b9e25d92..a130ea9b 100644 --- a/internal/usecase/cluster.go +++ b/internal/usecase/cluster.go @@ -3,10 +3,10 @@ package usecase import ( "context" "fmt" + "github.com/openinfradev/tks-api/internal/middleware/auth/request" "strings" "github.com/google/uuid" - "github.com/openinfradev/tks-api/internal/auth/request" "github.com/openinfradev/tks-api/internal/repository" argowf "github.com/openinfradev/tks-api/pkg/argo-client" "github.com/openinfradev/tks-api/pkg/domain" diff --git a/internal/usecase/organization.go b/internal/usecase/organization.go index 382adec0..f421a210 100644 --- a/internal/usecase/organization.go +++ b/internal/usecase/organization.go @@ -3,8 +3,6 @@ package usecase import ( "context" "fmt" - - "github.com/openinfradev/tks-api/internal/auth/request" "github.com/openinfradev/tks-api/internal/helper" "github.com/openinfradev/tks-api/internal/keycloak" "github.com/openinfradev/tks-api/pkg/httpErrors" @@ -48,13 +46,9 @@ func (u *OrganizationUsecase) Create(ctx context.Context, in *domain.Organizatio return "", err } } - token, ok := request.TokenFrom(ctx) - if !ok { - return "", fmt.Errorf("token in the context is empty") - } // Create realm in keycloak - if organizationId, err = u.kc.CreateRealm(helper.GenerateOrganizationId(), domain.Organization{}, token); err != nil { + if organizationId, err = u.kc.CreateRealm(helper.GenerateOrganizationId()); err != nil { return "", err } @@ -103,7 +97,7 @@ func (u *OrganizationUsecase) Delete(organizationId string, accessToken string) } // Delete realm in keycloak - if err := u.kc.DeleteRealm(organizationId, accessToken); err != nil { + if err := u.kc.DeleteRealm(organizationId); err != nil { return err } diff --git a/internal/usecase/stack.go b/internal/usecase/stack.go index 4ecb9cce..4d4188ba 100644 --- a/internal/usecase/stack.go +++ b/internal/usecase/stack.go @@ -3,9 +3,9 @@ package usecase import ( "context" "fmt" + "github.com/openinfradev/tks-api/internal/middleware/auth/request" "time" - "github.com/openinfradev/tks-api/internal/auth/request" "github.com/openinfradev/tks-api/internal/repository" argowf "github.com/openinfradev/tks-api/pkg/argo-client" "github.com/openinfradev/tks-api/pkg/domain" diff --git a/internal/usecase/user.go b/internal/usecase/user.go index dbb251b4..6f3b5f01 100644 --- a/internal/usecase/user.go +++ b/internal/usecase/user.go @@ -3,11 +3,11 @@ package usecase import ( "context" "fmt" + "github.com/openinfradev/tks-api/internal/middleware/auth/request" "net/http" "github.com/Nerzal/gocloak/v13" "github.com/google/uuid" - "github.com/openinfradev/tks-api/internal/auth/request" "github.com/openinfradev/tks-api/internal/helper" "github.com/openinfradev/tks-api/internal/keycloak" "github.com/openinfradev/tks-api/internal/repository" @@ -47,17 +47,12 @@ func (u *UserUsecase) DeleteAll(ctx context.Context, organizationId string) erro } func (u *UserUsecase) DeleteAdmin(organizationId string) error { - token, err := u.kc.LoginAdmin() - if err != nil { - return errors.Wrap(err, "login admin failed") - } - - user, err := u.kc.GetUser(organizationId, "admin", token) + user, err := u.kc.GetUser(organizationId, "admin") if err != nil { return errors.Wrap(err, "get user failed") } - err = u.kc.DeleteUser(organizationId, "admin", token) + err = u.kc.DeleteUser(organizationId, "admin") if err != nil { return errors.Wrap(err, "delete user failed") } @@ -76,10 +71,6 @@ func (u *UserUsecase) DeleteAdmin(organizationId string) error { } func (u *UserUsecase) CreateAdmin(orgainzationId string) (*domain.User, error) { - token, err := u.kc.LoginAdmin() - if err != nil { - return nil, errors.Wrap(err, "login admin failed") - } user := domain.User{ AccountId: "admin", Password: "admin", @@ -94,7 +85,7 @@ func (u *UserUsecase) CreateAdmin(orgainzationId string) (*domain.User, error) { // Create user in keycloak groups := []string{fmt.Sprintf("%s@%s", user.Role.Name, orgainzationId)} - err = u.kc.CreateUser(orgainzationId, &gocloak.User{ + err := u.kc.CreateUser(orgainzationId, &gocloak.User{ Username: gocloak.StringP(user.AccountId), Credentials: &[]gocloak.CredentialRepresentation{ { @@ -104,11 +95,11 @@ func (u *UserUsecase) CreateAdmin(orgainzationId string) (*domain.User, error) { }, }, Groups: &groups, - }, token) + }) if err != nil { return nil, errors.Wrap(err, "creating user in keycloak failed") } - keycloakUser, err := u.kc.GetUser(user.Organization.ID, user.AccountId, token) + keycloakUser, err := u.kc.GetUser(user.Organization.ID, user.AccountId) if err != nil { return nil, errors.Wrap(err, "getting user from keycloak failed") } @@ -147,13 +138,7 @@ func (u *UserUsecase) CreateAdmin(orgainzationId string) (*domain.User, error) { func (u *UserUsecase) UpdatePasswordByAccountId(ctx context.Context, accountId string, newPassword string, organizationId string) error { - - token, ok := request.TokenFrom(ctx) - if !ok { - return fmt.Errorf("token in the context is empty") - } - - originUser, err := u.kc.GetUser(organizationId, accountId, token) + originUser, err := u.kc.GetUser(organizationId, accountId) if err != nil { return err } @@ -166,7 +151,7 @@ func (u *UserUsecase) UpdatePasswordByAccountId(ctx context.Context, accountId s }, } - err = u.kc.UpdateUser(organizationId, originUser, token) + err = u.kc.UpdateUser(organizationId, originUser) if err != nil { return errors.Wrap(err, "updating user in keycloak failed") } @@ -233,10 +218,6 @@ func (u *UserUsecase) UpdateByAccountId(ctx context.Context, accountId string, u if !ok { return nil, fmt.Errorf("user in the context is empty") } - token, ok := request.TokenFrom(ctx) - if !ok { - return nil, fmt.Errorf("token in the context is empty") - } users, err := u.repo.List(u.repo.OrganizationFilter(userInfo.GetOrganizationId()), u.repo.AccountIdFilter(accountId)) @@ -257,7 +238,7 @@ func (u *UserUsecase) UpdateByAccountId(ctx context.Context, accountId string, u if err := u.kc.UpdateUser(userInfo.GetOrganizationId(), &gocloak.User{ ID: &(*users)[0].ID, Groups: &groups, - }, token); err != nil { + }); err != nil { log.Errorf("updating user in keycloak failed: %v", err) return nil, httpErrors.NewInternalServerError(err) } @@ -309,11 +290,7 @@ func (u *UserUsecase) DeleteByAccountId(ctx context.Context, accountId string, o } // Delete user in keycloak - token, ok := request.TokenFrom(ctx) - if !ok { - return fmt.Errorf("token in the context is empty") - } - err = u.kc.DeleteUser(organizationId, accountId, token) + err = u.kc.DeleteUser(organizationId, accountId) if err != nil { return err } @@ -322,13 +299,6 @@ func (u *UserUsecase) DeleteByAccountId(ctx context.Context, accountId string, o } func (u *UserUsecase) Create(ctx context.Context, user *domain.User) (*domain.User, error) { - // Validation check - - token, ok := request.TokenFrom(ctx) - if !ok { - return nil, fmt.Errorf("token in the context is empty") - } - // Create user in keycloak groups := []string{fmt.Sprintf("%s@%s", user.Role.Name, user.Organization.ID)} err := u.kc.CreateUser(user.Organization.ID, &gocloak.User{ @@ -341,15 +311,15 @@ func (u *UserUsecase) Create(ctx context.Context, user *domain.User) (*domain.Us }, }, Groups: &groups, - }, token) + }) if err != nil { - if _, err := u.kc.GetUser(user.Organization.ID, user.AccountId, token); err == nil { + if _, err := u.kc.GetUser(user.Organization.ID, user.AccountId); err == nil { return nil, httpErrors.NewConflictError(errors.New("user already exists")) } return nil, errors.Wrap(err, "creating user in keycloak failed") } - keycloakUser, err := u.kc.GetUser(user.Organization.ID, user.AccountId, token) + keycloakUser, err := u.kc.GetUser(user.Organization.ID, user.AccountId) if err != nil { return nil, errors.Wrap(err, "getting user from keycloak failed") } diff --git a/pkg/api-client/api-client.go b/pkg/api-client/api-client.go index 0ae6cf6c..fb94d413 100644 --- a/pkg/api-client/api-client.go +++ b/pkg/api-client/api-client.go @@ -135,5 +135,4 @@ func (c *ApiClientImpl) callWithBody(method string, path string, input interface } return resJson, nil - } diff --git a/pkg/domain/mapper.go b/pkg/domain/mapper.go index 20927d44..cbd61531 100644 --- a/pkg/domain/mapper.go +++ b/pkg/domain/mapper.go @@ -77,10 +77,10 @@ func Map(src interface{}, dst interface{}) error { return val, nil }, {srcType: reflect.TypeOf((*OrganizationStatus)(nil)).Elem(), dstType: reflect.TypeOf("")}: func(i interface{}) (interface{}, error) { - return string(i.(OrganizationStatus)), nil + return i.(OrganizationStatus).String(), nil }, {srcType: reflect.TypeOf(""), dstType: reflect.TypeOf((*OrganizationStatus)(nil)).Elem()}: func(i interface{}) (interface{}, error) { - return i.(OrganizationStatus).String(), nil + return organizationStatusMap[i.(string)], nil }, {srcType: reflect.TypeOf((*Role)(nil)).Elem(), dstType: reflect.TypeOf("")}: func(i interface{}) (interface{}, error) { return i.(Role).Name, nil diff --git a/pkg/domain/mapper_test.go b/pkg/domain/mapper_test.go index 5b476e0b..818f2ee4 100644 --- a/pkg/domain/mapper_test.go +++ b/pkg/domain/mapper_test.go @@ -18,7 +18,7 @@ func TestConvert(t *testing.T) { wantErr bool }{ { - name: "test case 1", + name: "test case: CreateOrganizationRequest->Organization", args: args{ src: CreateOrganizationRequest{ Name: "test", @@ -30,7 +30,7 @@ func TestConvert(t *testing.T) { wantErr: false, }, { - name: "test case 2", + name: "test case Organization->CreateOrganizationResponse", args: args{ src: Organization{ ID: "", @@ -47,7 +47,25 @@ func TestConvert(t *testing.T) { wantErr: false, }, { - name: "test case 3", + name: "test case Organization->GetOrganizationResponse", + args: args{ + src: Organization{ + ID: "", + Name: "test", + Description: "test", + Phone: "test", + Status: OrganizationStatus_CREATE, + StatusDescription: "good", + Creator: "", + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + }, + dst: &(&GetOrganizationResponse{}).Organization, + }, + wantErr: false, + }, + { + name: "test case CreateUserRequest->User", args: args{ src: CreateUserRequest{ AccountId: "testAccount", @@ -63,7 +81,7 @@ func TestConvert(t *testing.T) { wantErr: false, }, { - name: "test case 4", + name: "test case User->GetUserResponse", args: args{ src: User{ ID: "", @@ -80,7 +98,7 @@ func TestConvert(t *testing.T) { Department: "", Description: "", }, - dst: &User{}, + dst: &GetUserResponse{}, }, wantErr: false, }, @@ -90,7 +108,8 @@ func TestConvert(t *testing.T) { if err := Map(tt.args.src, tt.args.dst); (err != nil) != tt.wantErr { t.Errorf("Map() error = %v, wantErr %v", err, tt.wantErr) } else { - fmt.Println(tt.args.dst) + fmt.Printf("Input: %+v\n", tt.args.src) + fmt.Printf("Output: %+v\n\n", tt.args.dst) } }) } diff --git a/pkg/domain/organization.go b/pkg/domain/organization.go index a281e3ff..4c6f2823 100644 --- a/pkg/domain/organization.go +++ b/pkg/domain/organization.go @@ -29,12 +29,21 @@ var organizationStatus = [...]string{ "ERROR", } +var organizationStatusMap = map[string]OrganizationStatus{ + "PENDING": OrganizationStatus_PENDING, + "CREATE": OrganizationStatus_CREATE, + "CREATING": OrganizationStatus_CREATING, + "CREATED": OrganizationStatus_CREATED, + "DELETE": OrganizationStatus_DELETE, + "DELETING": OrganizationStatus_DELETING, + "DELETED": OrganizationStatus_DELETED, + "ERROR": OrganizationStatus_ERROR, +} + func (m OrganizationStatus) String() string { return organizationStatus[(m)] } func (m OrganizationStatus) FromString(s string) OrganizationStatus { - for i, v := range organizationStatus { - if v == s { - return OrganizationStatus(i) - } + if v, ok := organizationStatusMap[s]; ok { + return v } return OrganizationStatus_ERROR } @@ -64,16 +73,16 @@ type CreateOrganizationResponse struct { type GetOrganizationResponse struct { Organization struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Phone string `json:"phone"` - PrimaryClusterId string `json:"primaryClusterId"` - Status OrganizationStatus `json:"status"` - StatusDescription string `json:"statusDescription"` - Creator string `json:"creator"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Phone string `json:"phone"` + PrimaryClusterId string `json:"primaryClusterId"` + Status string `json:"status"` + StatusDescription string `json:"statusDescription"` + Creator string `json:"creator"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` } `json:"organization"` } type ListOrganizationResponse struct { @@ -86,6 +95,8 @@ type ListOrganizationBody struct { Phone string `json:"phone"` PrimaryClusterId string `json:"primaryClusterId"` Status OrganizationStatus `json:"status"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` } type UpdateOrganizationRequest struct { diff --git a/pkg/domain/user.go b/pkg/domain/user.go index 5dc5255e..d73cf3a0 100644 --- a/pkg/domain/user.go +++ b/pkg/domain/user.go @@ -155,6 +155,8 @@ type UpdateUserResponse struct { Email string `json:"email"` Department string `json:"department"` Description string `json:"description"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` } `json:"user"` } diff --git a/pkg/httpErrors/httpErrors.go b/pkg/httpErrors/httpErrors.go index 4c9bc640..0b19d1c9 100644 --- a/pkg/httpErrors/httpErrors.go +++ b/pkg/httpErrors/httpErrors.go @@ -141,7 +141,6 @@ func reflectVariableName(interface{}) string { */ func parseErrors(err error) IRestError { - switch { case strings.Contains(err.Error(), "SQLSTATE"): return parseSqlError(err)