mirror of https://github.com/kubevela/kubevela.git
Feat: add user management apis (#3458)
* Feat: add user management apis Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * Feat: add e2e test and some nit fix Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * Feat: add password validate Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * Feat: add email modification in update user Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * Fix: fix user detail to user base Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * Fix: fix ut Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * Fix: fix test Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * Fix: fix rebase Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * Fix: add password check in create user Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * Fix: fix bcode confilt Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
This commit is contained in:
parent
13c420dada
commit
3ea2ac6d0f
1
Makefile
1
Makefile
|
|
@ -132,5 +132,4 @@ def-install:
|
|||
|
||||
helm-doc-gen: helmdoc
|
||||
readme-generator -v charts/vela-core/values.yaml -r charts/vela-core/README.md
|
||||
cat charts/vela-core/README.md
|
||||
readme-generator -v charts/vela-minimal/values.yaml -r charts/vela-minimal/README.md
|
||||
|
|
@ -2600,6 +2600,64 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/auth/dexConfig": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/xml",
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/xml"
|
||||
],
|
||||
"tags": [
|
||||
"authentication"
|
||||
],
|
||||
"summary": "get Dex config",
|
||||
"operationId": "getDexConfig",
|
||||
"responses": {
|
||||
"200": {
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.DexConfigResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/auth/login": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/xml",
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/xml"
|
||||
],
|
||||
"tags": [
|
||||
"authentication"
|
||||
],
|
||||
"summary": "handle login request",
|
||||
"operationId": "login",
|
||||
"responses": {
|
||||
"200": {
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.LoginResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/clusters": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
|
|
@ -3175,9 +3233,9 @@
|
|||
"parameters": [
|
||||
{
|
||||
"enum": [
|
||||
"workflowstep",
|
||||
"component",
|
||||
"trait",
|
||||
"workflowstep"
|
||||
"trait"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "query the definition type",
|
||||
|
|
@ -4005,6 +4063,259 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/users": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/xml",
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/xml"
|
||||
],
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"summary": "list users",
|
||||
"operationId": "listUser",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "query the page number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "query the page size number",
|
||||
"name": "pageSize",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "fuzzy search based on name",
|
||||
"name": "name",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "fuzzy search based on email",
|
||||
"name": "email",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "fuzzy search based on alias",
|
||||
"name": "alias",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.ListUserResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/xml",
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/xml"
|
||||
],
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"summary": "create a user",
|
||||
"operationId": "createUser",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.CreateUserRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.DetailUserResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/users/{username}": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/xml",
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/xml"
|
||||
],
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"summary": "get user detail",
|
||||
"operationId": "detailUser",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.DetailUserResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"consumes": [
|
||||
"application/xml",
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/xml"
|
||||
],
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"summary": "update a user's alias or password",
|
||||
"operationId": "updateUser",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.DetailUserResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"consumes": [
|
||||
"application/xml",
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/xml"
|
||||
],
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"summary": "delete a user",
|
||||
"operationId": "deleteUser",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.EmptyResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/users/{username}/disable": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/xml",
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/xml"
|
||||
],
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"summary": "disable a user",
|
||||
"operationId": "disableUser",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.EmptyResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/users/{username}/enable": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/xml",
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/xml"
|
||||
],
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"summary": "enable a user",
|
||||
"operationId": "enableUser",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.EmptyResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/webhook/{token}": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
|
|
@ -5103,6 +5414,9 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"map[string]interface {}": {
|
||||
"type": "object"
|
||||
},
|
||||
"model.ApplicationComponent": {
|
||||
"required": [
|
||||
"createTime",
|
||||
|
|
@ -5304,8 +5618,8 @@
|
|||
},
|
||||
"model.Cluster": {
|
||||
"required": [
|
||||
"updateTime",
|
||||
"createTime",
|
||||
"updateTime",
|
||||
"name",
|
||||
"alias",
|
||||
"description",
|
||||
|
|
@ -5493,7 +5807,8 @@
|
|||
"createTime",
|
||||
"updateTime",
|
||||
"installID",
|
||||
"enableCollection"
|
||||
"enableCollection",
|
||||
"loginType"
|
||||
],
|
||||
"properties": {
|
||||
"createTime": {
|
||||
|
|
@ -5506,6 +5821,9 @@
|
|||
"installID": {
|
||||
"type": "string"
|
||||
},
|
||||
"loginType": {
|
||||
"type": "string"
|
||||
},
|
||||
"updateTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
|
|
@ -6011,9 +6329,9 @@
|
|||
},
|
||||
"v1.ApplicationDeployResponse": {
|
||||
"required": [
|
||||
"createTime",
|
||||
"version",
|
||||
"envName",
|
||||
"createTime",
|
||||
"status",
|
||||
"note",
|
||||
"triggerType"
|
||||
|
|
@ -6986,6 +7304,27 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.CreateUserRequest": {
|
||||
"required": [
|
||||
"name",
|
||||
"email",
|
||||
"password"
|
||||
],
|
||||
"properties": {
|
||||
"alias": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.CreateWorkflowRequest": {
|
||||
"required": [
|
||||
"name",
|
||||
|
|
@ -7051,11 +7390,11 @@
|
|||
},
|
||||
"v1.DetailAddonResponse": {
|
||||
"required": [
|
||||
"invisible",
|
||||
"name",
|
||||
"version",
|
||||
"description",
|
||||
"name",
|
||||
"icon",
|
||||
"invisible",
|
||||
"version",
|
||||
"schema",
|
||||
"uiSchema",
|
||||
"definitions"
|
||||
|
|
@ -7128,13 +7467,13 @@
|
|||
},
|
||||
"v1.DetailApplicationResponse": {
|
||||
"required": [
|
||||
"createTime",
|
||||
"updateTime",
|
||||
"icon",
|
||||
"name",
|
||||
"alias",
|
||||
"project",
|
||||
"description",
|
||||
"createTime",
|
||||
"updateTime",
|
||||
"policies",
|
||||
"envBindings",
|
||||
"status",
|
||||
|
|
@ -7196,20 +7535,20 @@
|
|||
},
|
||||
"v1.DetailClusterResponse": {
|
||||
"required": [
|
||||
"name",
|
||||
"status",
|
||||
"reason",
|
||||
"apiServerURL",
|
||||
"dashboardURL",
|
||||
"updateTime",
|
||||
"name",
|
||||
"icon",
|
||||
"labels",
|
||||
"createTime",
|
||||
"provider",
|
||||
"apiServerURL",
|
||||
"alias",
|
||||
"description",
|
||||
"icon",
|
||||
"status",
|
||||
"labels",
|
||||
"reason",
|
||||
"dashboardURL",
|
||||
"kubeConfig",
|
||||
"kubeConfigSecret",
|
||||
"alias",
|
||||
"description",
|
||||
"resourceInfo"
|
||||
],
|
||||
"properties": {
|
||||
|
|
@ -7267,14 +7606,14 @@
|
|||
},
|
||||
"v1.DetailComponentResponse": {
|
||||
"required": [
|
||||
"name",
|
||||
"appPrimaryKey",
|
||||
"type",
|
||||
"createTime",
|
||||
"creator",
|
||||
"alias",
|
||||
"main",
|
||||
"appPrimaryKey",
|
||||
"creator",
|
||||
"createTime",
|
||||
"updateTime",
|
||||
"main",
|
||||
"name",
|
||||
"type",
|
||||
"definition"
|
||||
],
|
||||
"properties": {
|
||||
|
|
@ -7376,13 +7715,13 @@
|
|||
},
|
||||
"v1.DetailPolicyResponse": {
|
||||
"required": [
|
||||
"createTime",
|
||||
"updateTime",
|
||||
"name",
|
||||
"type",
|
||||
"description",
|
||||
"creator",
|
||||
"properties",
|
||||
"createTime"
|
||||
"properties"
|
||||
],
|
||||
"properties": {
|
||||
"createTime": {
|
||||
|
|
@ -7412,17 +7751,17 @@
|
|||
},
|
||||
"v1.DetailRevisionResponse": {
|
||||
"required": [
|
||||
"createTime",
|
||||
"updateTime",
|
||||
"reason",
|
||||
"deployUser",
|
||||
"workflowName",
|
||||
"version",
|
||||
"triggerType",
|
||||
"appPrimaryKey",
|
||||
"status",
|
||||
"note",
|
||||
"envName"
|
||||
"workflowName",
|
||||
"appPrimaryKey",
|
||||
"version",
|
||||
"reason",
|
||||
"triggerType",
|
||||
"envName",
|
||||
"createTime",
|
||||
"status",
|
||||
"updateTime",
|
||||
"deployUser"
|
||||
],
|
||||
"properties": {
|
||||
"appPrimaryKey": {
|
||||
|
|
@ -7513,14 +7852,52 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.DetailUserResponse": {
|
||||
"required": [
|
||||
"lastLoginTime",
|
||||
"name",
|
||||
"email",
|
||||
"disabled",
|
||||
"createTime",
|
||||
"projects"
|
||||
],
|
||||
"properties": {
|
||||
"alias": {
|
||||
"type": "string"
|
||||
},
|
||||
"createTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"disabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastLoginTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"projects": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/v1.ProjectUserBase"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.DetailWorkflowRecordResponse": {
|
||||
"required": [
|
||||
"name",
|
||||
"namespace",
|
||||
"workflowName",
|
||||
"workflowAlias",
|
||||
"applicationRevision",
|
||||
"status",
|
||||
"name",
|
||||
"namespace",
|
||||
"deployTime",
|
||||
"deployUser",
|
||||
"note",
|
||||
|
|
@ -7572,12 +7949,12 @@
|
|||
},
|
||||
"v1.DetailWorkflowResponse": {
|
||||
"required": [
|
||||
"alias",
|
||||
"description",
|
||||
"default",
|
||||
"envName",
|
||||
"createTime",
|
||||
"name",
|
||||
"alias",
|
||||
"description",
|
||||
"enable",
|
||||
"updateTime"
|
||||
],
|
||||
|
|
@ -7616,6 +7993,28 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.DexConfigResponse": {
|
||||
"required": [
|
||||
"clientID",
|
||||
"clientSecret",
|
||||
"redirectURL",
|
||||
"issuer"
|
||||
],
|
||||
"properties": {
|
||||
"clientID": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientSecret": {
|
||||
"type": "string"
|
||||
},
|
||||
"issuer": {
|
||||
"type": "string"
|
||||
},
|
||||
"redirectURL": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.EmptyResponse": {},
|
||||
"v1.EnableAddonRequest": {
|
||||
"properties": {
|
||||
|
|
@ -7989,6 +8388,24 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.ListUserResponse": {
|
||||
"required": [
|
||||
"users",
|
||||
"total"
|
||||
],
|
||||
"properties": {
|
||||
"total": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"users": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/v1.DetailUserResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.ListWorkflowRecordsResponse": {
|
||||
"required": [
|
||||
"records",
|
||||
|
|
@ -8020,6 +8437,22 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.LoginResponse": {
|
||||
"required": [
|
||||
"userInfo"
|
||||
],
|
||||
"properties": {
|
||||
"accessToken": {
|
||||
"type": "string"
|
||||
},
|
||||
"refreshToken": {
|
||||
"type": "string"
|
||||
},
|
||||
"userInfo": {
|
||||
"$ref": "#/definitions/v1.DetailUserResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.NameAlias": {
|
||||
"required": [
|
||||
"name",
|
||||
|
|
@ -8152,6 +8585,24 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.ProjectUserBase": {
|
||||
"required": [
|
||||
"name",
|
||||
"alias",
|
||||
"userRole"
|
||||
],
|
||||
"properties": {
|
||||
"alias": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"userRole": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.PutApplicationEnvBindingRequest": {},
|
||||
"v1.SimpleResponse": {
|
||||
"required": [
|
||||
|
|
@ -8175,10 +8626,11 @@
|
|||
},
|
||||
"v1.SystemInfoResponse": {
|
||||
"required": [
|
||||
"enableCollection",
|
||||
"loginType",
|
||||
"createTime",
|
||||
"updateTime",
|
||||
"installID",
|
||||
"enableCollection",
|
||||
"systemVersion"
|
||||
],
|
||||
"properties": {
|
||||
|
|
@ -8192,6 +8644,9 @@
|
|||
"installID": {
|
||||
"type": "string"
|
||||
},
|
||||
"loginType": {
|
||||
"type": "string"
|
||||
},
|
||||
"systemVersion": {
|
||||
"$ref": "#/definitions/v1.SystemVersion"
|
||||
},
|
||||
|
|
@ -8386,6 +8841,37 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.UserBase": {
|
||||
"required": [
|
||||
"createTime",
|
||||
"lastLoginTime",
|
||||
"name",
|
||||
"email",
|
||||
"disabled"
|
||||
],
|
||||
"properties": {
|
||||
"alias": {
|
||||
"type": "string"
|
||||
},
|
||||
"createTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"disabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastLoginTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.VelaQLViewResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -62,6 +62,7 @@ require (
|
|||
github.com/wonderflow/cert-manager-api v1.0.3
|
||||
go.mongodb.org/mongo-driver v1.5.1
|
||||
go.uber.org/zap v1.18.1
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
|
||||
golang.org/x/tools v0.1.6 // indirect
|
||||
|
|
@ -246,7 +247,6 @@ require (
|
|||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
|
|
|
|||
|
|
@ -16,20 +16,26 @@ limitations under the License.
|
|||
|
||||
package model
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterModel(&User{})
|
||||
RegisterModel(&ProjectUser{})
|
||||
}
|
||||
|
||||
// User is the model of user
|
||||
type User struct {
|
||||
BaseModel
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Alias string `json:"alias,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Disabled bool `json:"disabled"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Alias string `json:"alias,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Disabled bool `json:"disabled"`
|
||||
LastLoginTime time.Time `json:"lastLoginTime,omitempty"`
|
||||
}
|
||||
|
||||
// TableName return custom table name
|
||||
|
|
@ -59,6 +65,41 @@ func (u *User) Index() map[string]string {
|
|||
return index
|
||||
}
|
||||
|
||||
// ProjectUser is the model of user in project
|
||||
type ProjectUser struct {
|
||||
BaseModel
|
||||
Username string `json:"username"`
|
||||
ProjectName string `json:"projectName"`
|
||||
UserRoles []string `json:"userRoles"`
|
||||
}
|
||||
|
||||
// TableName return custom table name
|
||||
func (u *ProjectUser) TableName() string {
|
||||
return tableNamePrefix + "project_user"
|
||||
}
|
||||
|
||||
// ShortTableName return custom table name
|
||||
func (u *ProjectUser) ShortTableName() string {
|
||||
return "pusr"
|
||||
}
|
||||
|
||||
// PrimaryKey return custom primary key
|
||||
func (u *ProjectUser) PrimaryKey() string {
|
||||
return fmt.Sprintf("%s-%s", u.ProjectName, verifyUserValue(u.Username))
|
||||
}
|
||||
|
||||
// Index return custom index
|
||||
func (u *ProjectUser) Index() map[string]string {
|
||||
index := make(map[string]string)
|
||||
if u.Username != "" {
|
||||
index["username"] = verifyUserValue(u.Username)
|
||||
}
|
||||
if u.ProjectName != "" {
|
||||
index["projectName"] = u.ProjectName
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
func verifyUserValue(v string) string {
|
||||
s := strings.ReplaceAll(v, "@", "-")
|
||||
s = strings.ReplaceAll(s, " ", "-")
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ var (
|
|||
CtxKeyApplicationEnvBinding = "envbinding-policy"
|
||||
// CtxKeyApplicationComponent request context key of component
|
||||
CtxKeyApplicationComponent = "component"
|
||||
// CtxKeyUser request context key of user
|
||||
CtxKeyUser = "user"
|
||||
)
|
||||
|
||||
// AddonPhase defines the phase of an addon
|
||||
|
|
@ -1088,13 +1090,6 @@ type LoginResponse struct {
|
|||
RefreshToken string `json:"refreshToken,omitempty"`
|
||||
}
|
||||
|
||||
// DetailUserResponse is the detail user info for the response
|
||||
type DetailUserResponse struct {
|
||||
Name string `json:"name"`
|
||||
Alias string `json:"alias,omitempty"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// DexConfigResponse is the response of dex config
|
||||
type DexConfigResponse struct {
|
||||
ClientID string `json:"clientID"`
|
||||
|
|
@ -1102,3 +1097,54 @@ type DexConfigResponse struct {
|
|||
RedirectURL string `json:"redirectURL"`
|
||||
Issuer string `json:"issuer"`
|
||||
}
|
||||
|
||||
// DetailUserResponse is the response of user detail
|
||||
type DetailUserResponse struct {
|
||||
UserBase
|
||||
Projects []ProjectUserBase `json:"projects"`
|
||||
}
|
||||
|
||||
// ProjectUserBase project user base
|
||||
type ProjectUserBase struct {
|
||||
Name string `json:"name"`
|
||||
Alias string `json:"alias"`
|
||||
UserRoles []string `json:"userRoles"`
|
||||
}
|
||||
|
||||
// CreateUserRequest create user request
|
||||
type CreateUserRequest struct {
|
||||
Name string `json:"name" validate:"checkname"`
|
||||
Alias string `json:"alias,omitempty" validate:"checkalias" optional:"true"`
|
||||
Email string `json:"email" validate:"checkemail"`
|
||||
Password string `json:"password" validate:"checkpassword"`
|
||||
}
|
||||
|
||||
// UpdateUserRequest update user request
|
||||
type UpdateUserRequest struct {
|
||||
Alias string `json:"alias,omitempty" optional:"true"`
|
||||
Password string `json:"password,omitempty" validate:"checkpassword" optional:"true"`
|
||||
Email string `json:"email,omitempty" validate:"checkemail" optional:"true"`
|
||||
}
|
||||
|
||||
// ListUserResponse list user response
|
||||
type ListUserResponse struct {
|
||||
Users []*DetailUserResponse `json:"users"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
// UserBase is the base info of user
|
||||
type UserBase struct {
|
||||
CreateTime time.Time `json:"createTime"`
|
||||
LastLoginTime time.Time `json:"lastLoginTime"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Alias string `json:"alias,omitempty"`
|
||||
Disabled bool `json:"disabled"`
|
||||
}
|
||||
|
||||
// ListUserOptions list user options
|
||||
type ListUserOptions struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Alias string `json:"alias"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,8 +195,10 @@ func (d *dexHandlerImpl) login(ctx context.Context) (*apisv1.LoginResponse, erro
|
|||
|
||||
return &apisv1.LoginResponse{
|
||||
UserInfo: apisv1.DetailUserResponse{
|
||||
Name: claims.Name,
|
||||
Email: claims.Email,
|
||||
UserBase: apisv1.UserBase{
|
||||
Name: claims.Name,
|
||||
Email: claims.Email,
|
||||
},
|
||||
},
|
||||
AccessToken: d.token.AccessToken,
|
||||
RefreshToken: d.token.RefreshToken,
|
||||
|
|
|
|||
|
|
@ -67,14 +67,14 @@
|
|||
- description: The value of the environment variable
|
||||
jsonKey: value
|
||||
label: Value
|
||||
sort: 100
|
||||
sort: 101
|
||||
uiType: Input
|
||||
validate:
|
||||
immutable: false
|
||||
- description: Specifies a source the value of this var should come from
|
||||
jsonKey: valueFrom
|
||||
label: Secret Selector
|
||||
sort: 100
|
||||
sort: 102
|
||||
subParameters:
|
||||
- description: Selects a key of a secret in the pod's namespace
|
||||
jsonKey: secretKeyRef
|
||||
|
|
@ -124,64 +124,11 @@
|
|||
defaultValue: 3
|
||||
immutable: false
|
||||
required: true
|
||||
- description: Instructions for assessing container health by executing an HTTP
|
||||
GET request. Either this attribute or the exec attribute or the tcpSocket attribute
|
||||
MUST be specified. This attribute is mutually exclusive with both the exec attribute
|
||||
and the tcpSocket attribute.
|
||||
jsonKey: httpGet
|
||||
label: HttpGet
|
||||
sort: 100
|
||||
subParameters:
|
||||
- description: The TCP socket within the container to which the HTTP GET request
|
||||
should be directed.
|
||||
jsonKey: port
|
||||
label: Port
|
||||
sort: 100
|
||||
uiType: Number
|
||||
validate:
|
||||
immutable: false
|
||||
required: true
|
||||
- description: ""
|
||||
jsonKey: httpHeaders
|
||||
label: HttpHeaders
|
||||
sort: 100
|
||||
subParameters:
|
||||
- description: ""
|
||||
jsonKey: name
|
||||
label: Name
|
||||
sort: 100
|
||||
uiType: Input
|
||||
validate:
|
||||
immutable: false
|
||||
required: true
|
||||
- description: ""
|
||||
jsonKey: value
|
||||
label: Value
|
||||
sort: 100
|
||||
uiType: Input
|
||||
validate:
|
||||
immutable: false
|
||||
required: true
|
||||
uiType: Structs
|
||||
validate:
|
||||
immutable: false
|
||||
- description: The endpoint, relative to the port, to which the HTTP GET request
|
||||
should be directed.
|
||||
jsonKey: path
|
||||
label: Path
|
||||
sort: 100
|
||||
uiType: Input
|
||||
validate:
|
||||
immutable: false
|
||||
required: true
|
||||
uiType: Group
|
||||
validate:
|
||||
immutable: false
|
||||
- description: Number of seconds after the container is started before the first
|
||||
probe is initiated.
|
||||
jsonKey: initialDelaySeconds
|
||||
label: InitialDelaySeconds
|
||||
sort: 100
|
||||
sort: 101
|
||||
uiType: Number
|
||||
validate:
|
||||
defaultValue: 0
|
||||
|
|
@ -190,7 +137,7 @@
|
|||
- description: How often, in seconds, to execute the probe.
|
||||
jsonKey: periodSeconds
|
||||
label: PeriodSeconds
|
||||
sort: 100
|
||||
sort: 102
|
||||
uiType: Number
|
||||
validate:
|
||||
defaultValue: 10
|
||||
|
|
@ -200,19 +147,50 @@
|
|||
after having failed.
|
||||
jsonKey: successThreshold
|
||||
label: SuccessThreshold
|
||||
sort: 100
|
||||
sort: 103
|
||||
uiType: Number
|
||||
validate:
|
||||
defaultValue: 1
|
||||
immutable: false
|
||||
required: true
|
||||
- description: Number of seconds after which the probe times out.
|
||||
jsonKey: timeoutSeconds
|
||||
label: TimeoutSeconds
|
||||
sort: 104
|
||||
uiType: Number
|
||||
validate:
|
||||
defaultValue: 1
|
||||
immutable: false
|
||||
required: true
|
||||
- description: Instructions for assessing container health by executing a command.
|
||||
Either this attribute or the httpGet attribute or the tcpSocket attribute MUST
|
||||
be specified. This attribute is mutually exclusive with both the httpGet attribute
|
||||
and the tcpSocket attribute.
|
||||
jsonKey: exec
|
||||
label: Exec
|
||||
sort: 105
|
||||
subParameters:
|
||||
- description: A command to be executed inside the container to assess its health.
|
||||
Each space delimited token of the command is a separate array element. Commands
|
||||
exiting 0 are considered to be successful probes, whilst all other exit codes
|
||||
are considered failures.
|
||||
jsonKey: command
|
||||
label: Command
|
||||
sort: 100
|
||||
uiType: Strings
|
||||
validate:
|
||||
immutable: false
|
||||
required: true
|
||||
uiType: Group
|
||||
validate:
|
||||
immutable: false
|
||||
- description: Instructions for assessing container health by probing a TCP socket.
|
||||
Either this attribute or the exec attribute or the httpGet attribute MUST be
|
||||
specified. This attribute is mutually exclusive with both the exec attribute
|
||||
and the httpGet attribute.
|
||||
jsonKey: tcpSocket
|
||||
label: TcpSocket
|
||||
sort: 100
|
||||
sort: 106
|
||||
subParameters:
|
||||
- description: The TCP socket within the container that should be probed to assess
|
||||
container health.
|
||||
|
|
@ -226,34 +204,56 @@
|
|||
uiType: Group
|
||||
validate:
|
||||
immutable: false
|
||||
- description: Number of seconds after which the probe times out.
|
||||
jsonKey: timeoutSeconds
|
||||
label: TimeoutSeconds
|
||||
sort: 100
|
||||
uiType: Number
|
||||
validate:
|
||||
defaultValue: 1
|
||||
immutable: false
|
||||
required: true
|
||||
- description: Instructions for assessing container health by executing a command.
|
||||
Either this attribute or the httpGet attribute or the tcpSocket attribute MUST
|
||||
be specified. This attribute is mutually exclusive with both the httpGet attribute
|
||||
- description: Instructions for assessing container health by executing an HTTP
|
||||
GET request. Either this attribute or the exec attribute or the tcpSocket attribute
|
||||
MUST be specified. This attribute is mutually exclusive with both the exec attribute
|
||||
and the tcpSocket attribute.
|
||||
jsonKey: exec
|
||||
label: Exec
|
||||
sort: 100
|
||||
jsonKey: httpGet
|
||||
label: HttpGet
|
||||
sort: 107
|
||||
subParameters:
|
||||
- description: A command to be executed inside the container to assess its health.
|
||||
Each space delimited token of the command is a separate array element. Commands
|
||||
exiting 0 are considered to be successful probes, whilst all other exit codes
|
||||
are considered failures.
|
||||
jsonKey: command
|
||||
label: Command
|
||||
- description: The endpoint, relative to the port, to which the HTTP GET request
|
||||
should be directed.
|
||||
jsonKey: path
|
||||
label: Path
|
||||
sort: 100
|
||||
uiType: Strings
|
||||
uiType: Input
|
||||
validate:
|
||||
immutable: false
|
||||
required: true
|
||||
- description: The TCP socket within the container to which the HTTP GET request
|
||||
should be directed.
|
||||
jsonKey: port
|
||||
label: Port
|
||||
sort: 101
|
||||
uiType: Number
|
||||
validate:
|
||||
immutable: false
|
||||
required: true
|
||||
- description: ""
|
||||
jsonKey: httpHeaders
|
||||
label: HttpHeaders
|
||||
sort: 102
|
||||
subParameters:
|
||||
- description: ""
|
||||
jsonKey: name
|
||||
label: Name
|
||||
sort: 100
|
||||
uiType: Input
|
||||
validate:
|
||||
immutable: false
|
||||
required: true
|
||||
- description: ""
|
||||
jsonKey: value
|
||||
label: Value
|
||||
sort: 101
|
||||
uiType: Input
|
||||
validate:
|
||||
immutable: false
|
||||
required: true
|
||||
uiType: Structs
|
||||
validate:
|
||||
immutable: false
|
||||
uiType: Group
|
||||
validate:
|
||||
immutable: false
|
||||
|
|
@ -265,11 +265,21 @@
|
|||
label: LivenessProbe
|
||||
sort: 15
|
||||
subParameters:
|
||||
- description: Number of consecutive failures required to determine the container
|
||||
is not alive (liveness probe) or not ready (readiness probe).
|
||||
jsonKey: failureThreshold
|
||||
label: FailureThreshold
|
||||
sort: 100
|
||||
uiType: Number
|
||||
validate:
|
||||
defaultValue: 3
|
||||
immutable: false
|
||||
required: true
|
||||
- description: Number of seconds after the container is started before the first
|
||||
probe is initiated.
|
||||
jsonKey: initialDelaySeconds
|
||||
label: InitialDelaySeconds
|
||||
sort: 100
|
||||
sort: 101
|
||||
uiType: Number
|
||||
validate:
|
||||
defaultValue: 0
|
||||
|
|
@ -278,7 +288,7 @@
|
|||
- description: How often, in seconds, to execute the probe.
|
||||
jsonKey: periodSeconds
|
||||
label: PeriodSeconds
|
||||
sort: 100
|
||||
sort: 102
|
||||
uiType: Number
|
||||
validate:
|
||||
defaultValue: 10
|
||||
|
|
@ -288,36 +298,16 @@
|
|||
after having failed.
|
||||
jsonKey: successThreshold
|
||||
label: SuccessThreshold
|
||||
sort: 100
|
||||
sort: 103
|
||||
uiType: Number
|
||||
validate:
|
||||
defaultValue: 1
|
||||
immutable: false
|
||||
required: true
|
||||
- description: Instructions for assessing container health by probing a TCP socket.
|
||||
Either this attribute or the exec attribute or the httpGet attribute MUST be
|
||||
specified. This attribute is mutually exclusive with both the exec attribute
|
||||
and the httpGet attribute.
|
||||
jsonKey: tcpSocket
|
||||
label: TcpSocket
|
||||
sort: 100
|
||||
subParameters:
|
||||
- description: The TCP socket within the container that should be probed to assess
|
||||
container health.
|
||||
jsonKey: port
|
||||
label: Port
|
||||
sort: 100
|
||||
uiType: Number
|
||||
validate:
|
||||
immutable: false
|
||||
required: true
|
||||
uiType: Group
|
||||
validate:
|
||||
immutable: false
|
||||
- description: Number of seconds after which the probe times out.
|
||||
jsonKey: timeoutSeconds
|
||||
label: TimeoutSeconds
|
||||
sort: 100
|
||||
sort: 104
|
||||
uiType: Number
|
||||
validate:
|
||||
defaultValue: 1
|
||||
|
|
@ -329,7 +319,7 @@
|
|||
and the tcpSocket attribute.
|
||||
jsonKey: exec
|
||||
label: Exec
|
||||
sort: 100
|
||||
sort: 105
|
||||
subParameters:
|
||||
- description: A command to be executed inside the container to assess its health.
|
||||
Each space delimited token of the command is a separate array element. Commands
|
||||
|
|
@ -345,48 +335,34 @@
|
|||
uiType: Group
|
||||
validate:
|
||||
immutable: false
|
||||
- description: Number of consecutive failures required to determine the container
|
||||
is not alive (liveness probe) or not ready (readiness probe).
|
||||
jsonKey: failureThreshold
|
||||
label: FailureThreshold
|
||||
sort: 100
|
||||
uiType: Number
|
||||
- description: Instructions for assessing container health by probing a TCP socket.
|
||||
Either this attribute or the exec attribute or the httpGet attribute MUST be
|
||||
specified. This attribute is mutually exclusive with both the exec attribute
|
||||
and the httpGet attribute.
|
||||
jsonKey: tcpSocket
|
||||
label: TcpSocket
|
||||
sort: 106
|
||||
subParameters:
|
||||
- description: The TCP socket within the container that should be probed to assess
|
||||
container health.
|
||||
jsonKey: port
|
||||
label: Port
|
||||
sort: 100
|
||||
uiType: Number
|
||||
validate:
|
||||
immutable: false
|
||||
required: true
|
||||
uiType: Group
|
||||
validate:
|
||||
defaultValue: 3
|
||||
immutable: false
|
||||
required: true
|
||||
- description: Instructions for assessing container health by executing an HTTP
|
||||
GET request. Either this attribute or the exec attribute or the tcpSocket attribute
|
||||
MUST be specified. This attribute is mutually exclusive with both the exec attribute
|
||||
and the tcpSocket attribute.
|
||||
jsonKey: httpGet
|
||||
label: HttpGet
|
||||
sort: 100
|
||||
sort: 107
|
||||
subParameters:
|
||||
- description: ""
|
||||
jsonKey: httpHeaders
|
||||
label: HttpHeaders
|
||||
sort: 100
|
||||
subParameters:
|
||||
- description: ""
|
||||
jsonKey: name
|
||||
label: Name
|
||||
sort: 100
|
||||
uiType: Input
|
||||
validate:
|
||||
immutable: false
|
||||
required: true
|
||||
- description: ""
|
||||
jsonKey: value
|
||||
label: Value
|
||||
sort: 100
|
||||
uiType: Input
|
||||
validate:
|
||||
immutable: false
|
||||
required: true
|
||||
uiType: Structs
|
||||
validate:
|
||||
immutable: false
|
||||
- description: The endpoint, relative to the port, to which the HTTP GET request
|
||||
should be directed.
|
||||
jsonKey: path
|
||||
|
|
@ -400,27 +376,41 @@
|
|||
should be directed.
|
||||
jsonKey: port
|
||||
label: Port
|
||||
sort: 100
|
||||
sort: 101
|
||||
uiType: Number
|
||||
validate:
|
||||
immutable: false
|
||||
required: true
|
||||
- description: ""
|
||||
jsonKey: httpHeaders
|
||||
label: HttpHeaders
|
||||
sort: 102
|
||||
subParameters:
|
||||
- description: ""
|
||||
jsonKey: name
|
||||
label: Name
|
||||
sort: 100
|
||||
uiType: Input
|
||||
validate:
|
||||
immutable: false
|
||||
required: true
|
||||
- description: ""
|
||||
jsonKey: value
|
||||
label: Value
|
||||
sort: 101
|
||||
uiType: Input
|
||||
validate:
|
||||
immutable: false
|
||||
required: true
|
||||
uiType: Structs
|
||||
validate:
|
||||
immutable: false
|
||||
uiType: Group
|
||||
validate:
|
||||
immutable: false
|
||||
uiType: Group
|
||||
validate:
|
||||
immutable: false
|
||||
- description: Which port do you want customer traffic sent to
|
||||
disable: true
|
||||
jsonKey: port
|
||||
label: Port
|
||||
sort: 100
|
||||
uiType: Number
|
||||
validate:
|
||||
defaultValue: 80
|
||||
immutable: false
|
||||
required: true
|
||||
- description: If addRevisionLabel is true, the appRevision label will be added to
|
||||
the underlying pods
|
||||
disable: true
|
||||
|
|
@ -432,10 +422,20 @@
|
|||
defaultValue: false
|
||||
immutable: false
|
||||
required: true
|
||||
- description: Which port do you want customer traffic sent to
|
||||
disable: true
|
||||
jsonKey: port
|
||||
label: Port
|
||||
sort: 102
|
||||
uiType: Number
|
||||
validate:
|
||||
defaultValue: 80
|
||||
immutable: false
|
||||
required: true
|
||||
- description: Specify image pull secrets for your service
|
||||
jsonKey: imagePullSecrets
|
||||
label: ImagePullSecrets
|
||||
sort: 100
|
||||
sort: 106
|
||||
uiType: Strings
|
||||
validate:
|
||||
immutable: false
|
||||
|
|
@ -443,7 +443,7 @@
|
|||
disable: true
|
||||
jsonKey: volumes
|
||||
label: Volumes
|
||||
sort: 100
|
||||
sort: 109
|
||||
subParameters:
|
||||
- description: ""
|
||||
jsonKey: mountPath
|
||||
|
|
@ -456,7 +456,7 @@
|
|||
- description: ""
|
||||
jsonKey: name
|
||||
label: Name
|
||||
sort: 100
|
||||
sort: 101
|
||||
uiType: Input
|
||||
validate:
|
||||
immutable: false
|
||||
|
|
@ -464,7 +464,7 @@
|
|||
- description: 'Specify volume type, options: "pvc","configMap","secret","emptyDir"'
|
||||
jsonKey: type
|
||||
label: Type
|
||||
sort: 100
|
||||
sort: 102
|
||||
uiType: Select
|
||||
validate:
|
||||
immutable: false
|
||||
|
|
|
|||
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/log"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
|
||||
)
|
||||
|
||||
// UserUsecase User manage api
|
||||
type UserUsecase interface {
|
||||
GetUser(ctx context.Context, username string) (*model.User, error)
|
||||
DetailUser(ctx context.Context, user *model.User) (*apisv1.DetailUserResponse, error)
|
||||
DeleteUser(ctx context.Context, username string) error
|
||||
CreateUser(ctx context.Context, req apisv1.CreateUserRequest) (*apisv1.UserBase, error)
|
||||
UpdateUser(ctx context.Context, user *model.User, req apisv1.UpdateUserRequest) (*apisv1.UserBase, error)
|
||||
ListUsers(ctx context.Context, page, pageSize int, listOptions apisv1.ListUserOptions) (*apisv1.ListUserResponse, error)
|
||||
DisableUser(ctx context.Context, user *model.User) error
|
||||
EnableUser(ctx context.Context, user *model.User) error
|
||||
}
|
||||
|
||||
type userUsecaseImpl struct {
|
||||
ds datastore.DataStore
|
||||
k8sClient client.Client
|
||||
projectUsecase ProjectUsecase
|
||||
sysUsecase SystemInfoUsecase
|
||||
}
|
||||
|
||||
// NewUserUsecase new User usecase
|
||||
func NewUserUsecase(ds datastore.DataStore, projectUsecase ProjectUsecase, sysUsecase SystemInfoUsecase) UserUsecase {
|
||||
k8sClient, err := clients.GetKubeClient()
|
||||
if err != nil {
|
||||
log.Logger.Fatalf("get k8sClient failure: %s", err.Error())
|
||||
}
|
||||
return &userUsecaseImpl{
|
||||
k8sClient: k8sClient,
|
||||
ds: ds,
|
||||
projectUsecase: projectUsecase,
|
||||
sysUsecase: sysUsecase,
|
||||
}
|
||||
}
|
||||
|
||||
// GetUser get user
|
||||
func (u *userUsecaseImpl) GetUser(ctx context.Context, username string) (*model.User, error) {
|
||||
user := &model.User{
|
||||
Name: username,
|
||||
}
|
||||
if err := u.ds.Get(ctx, user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// DetailUser return user detail
|
||||
func (u *userUsecaseImpl) DetailUser(ctx context.Context, user *model.User) (*apisv1.DetailUserResponse, error) {
|
||||
detailUser := convertUserModel(user)
|
||||
pUser := &model.ProjectUser{
|
||||
Username: user.Name,
|
||||
}
|
||||
projectUsers, err := u.ds.List(ctx, pUser, &datastore.ListOptions{
|
||||
SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range projectUsers {
|
||||
pu, ok := v.(*model.ProjectUser)
|
||||
if ok {
|
||||
project, err := u.projectUsecase.GetProject(ctx, pu.ProjectName)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("failed to delete project(%s) info: %s", pu.ProjectName, err.Error())
|
||||
continue
|
||||
}
|
||||
detailUser.Projects = append(detailUser.Projects, apisv1.ProjectUserBase{
|
||||
Name: pu.ProjectName,
|
||||
Alias: project.Alias,
|
||||
UserRoles: pu.UserRoles,
|
||||
})
|
||||
}
|
||||
}
|
||||
return detailUser, nil
|
||||
}
|
||||
|
||||
// DeleteUser delete user
|
||||
func (u *userUsecaseImpl) DeleteUser(ctx context.Context, username string) error {
|
||||
pUser := &model.ProjectUser{
|
||||
Username: username,
|
||||
}
|
||||
|
||||
projectUsers, err := u.ds.List(ctx, pUser, &datastore.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range projectUsers {
|
||||
pu := v.(*model.ProjectUser)
|
||||
if err := u.ds.Delete(ctx, pu); err != nil {
|
||||
log.Logger.Errorf("failed to delete project user %s: %s", pu.PrimaryKey(), err.Error())
|
||||
}
|
||||
}
|
||||
if err := u.ds.Delete(ctx, &model.User{Name: username}); err != nil {
|
||||
log.Logger.Errorf("failed to delete user", username, err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateUser create user
|
||||
func (u *userUsecaseImpl) CreateUser(ctx context.Context, req apisv1.CreateUserRequest) (*apisv1.UserBase, error) {
|
||||
sysInfo, err := u.sysUsecase.GetSystemInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sysInfo.LoginType == model.LoginTypeDex {
|
||||
return nil, bcode.ErrUserCannotModified
|
||||
}
|
||||
hash, err := generatePasswordHash(req.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user := &model.User{
|
||||
Name: req.Name,
|
||||
Alias: req.Alias,
|
||||
Email: req.Email,
|
||||
Password: hash,
|
||||
Disabled: false,
|
||||
}
|
||||
if err := u.ds.Add(ctx, user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return convertUserBase(user), nil
|
||||
}
|
||||
|
||||
// UpdateUser update user
|
||||
func (u *userUsecaseImpl) UpdateUser(ctx context.Context, user *model.User, req apisv1.UpdateUserRequest) (*apisv1.UserBase, error) {
|
||||
sysInfo, err := u.sysUsecase.GetSystemInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sysInfo.LoginType == model.LoginTypeDex {
|
||||
return nil, bcode.ErrUserCannotModified
|
||||
}
|
||||
if req.Alias != "" {
|
||||
user.Alias = req.Alias
|
||||
}
|
||||
if req.Password != "" {
|
||||
hash, err := generatePasswordHash(req.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.Password = hash
|
||||
}
|
||||
if req.Email != "" {
|
||||
if user.Email != "" {
|
||||
return nil, bcode.ErrUnsupportedEmailModification
|
||||
}
|
||||
user.Email = req.Email
|
||||
}
|
||||
if err := u.ds.Put(ctx, user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return convertUserBase(user), nil
|
||||
}
|
||||
|
||||
// ListUsers list users
|
||||
func (u *userUsecaseImpl) ListUsers(ctx context.Context, page, pageSize int, listOptions apisv1.ListUserOptions) (*apisv1.ListUserResponse, error) {
|
||||
user := &model.User{}
|
||||
var queries []datastore.FuzzyQueryOption
|
||||
if listOptions.Name != "" {
|
||||
queries = append(queries, datastore.FuzzyQueryOption{Key: "name", Query: listOptions.Name})
|
||||
}
|
||||
if listOptions.Email != "" {
|
||||
queries = append(queries, datastore.FuzzyQueryOption{Key: "email", Query: listOptions.Email})
|
||||
}
|
||||
if listOptions.Alias != "" {
|
||||
queries = append(queries, datastore.FuzzyQueryOption{Key: "alias", Query: listOptions.Alias})
|
||||
}
|
||||
fo := datastore.FilterOptions{Queries: queries}
|
||||
|
||||
var userList []*apisv1.DetailUserResponse
|
||||
users, err := u.ds.List(ctx, user, &datastore.ListOptions{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}},
|
||||
FilterOptions: fo,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range users {
|
||||
user, ok := v.(*model.User)
|
||||
if ok {
|
||||
userList = append(userList, convertUserModel(user))
|
||||
}
|
||||
}
|
||||
count, err := u.ds.Count(ctx, user, &fo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &apisv1.ListUserResponse{
|
||||
Users: userList,
|
||||
Total: count,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DisableUser disable user
|
||||
func (u *userUsecaseImpl) DisableUser(ctx context.Context, user *model.User) error {
|
||||
if user.Disabled {
|
||||
return bcode.ErrUserAlreadyDisabled
|
||||
}
|
||||
user.Disabled = true
|
||||
return u.ds.Put(ctx, user)
|
||||
}
|
||||
|
||||
// EnableUser disable user
|
||||
func (u *userUsecaseImpl) EnableUser(ctx context.Context, user *model.User) error {
|
||||
if !user.Disabled {
|
||||
return bcode.ErrUserAlreadyEnabled
|
||||
}
|
||||
user.Disabled = false
|
||||
return u.ds.Put(ctx, user)
|
||||
}
|
||||
|
||||
func convertUserModel(user *model.User) *apisv1.DetailUserResponse {
|
||||
return &apisv1.DetailUserResponse{
|
||||
UserBase: *convertUserBase(user),
|
||||
Projects: make([]apisv1.ProjectUserBase, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func convertUserBase(user *model.User) *apisv1.UserBase {
|
||||
return &apisv1.UserBase{
|
||||
Name: user.Name,
|
||||
Alias: user.Alias,
|
||||
Email: user.Email,
|
||||
CreateTime: user.CreateTime,
|
||||
LastLoginTime: user.LastLoginTime,
|
||||
Disabled: user.Disabled,
|
||||
}
|
||||
}
|
||||
|
||||
func generatePasswordHash(s string) (string, error) {
|
||||
if s == "" {
|
||||
return "", bcode.ErrUserInvalidPassword
|
||||
}
|
||||
hashed, err := bcrypt.GenerateFromPassword([]byte(s), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(hashed), nil
|
||||
}
|
||||
|
||||
func compareHashWithPassword(hash, password string) error {
|
||||
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
}
|
||||
|
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
|
||||
)
|
||||
|
||||
var _ = Describe("Test authentication usecase functions", func() {
|
||||
var (
|
||||
userUsecase *userUsecaseImpl
|
||||
ds datastore.DataStore
|
||||
db string
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
db = "user-test-" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
ds, err = NewDatastore(datastore.Config{Type: "kubeapi", Database: db})
|
||||
Expect(ds).ToNot(BeNil())
|
||||
Expect(err).Should(BeNil())
|
||||
projectUsecase := &projectUsecaseImpl{k8sClient: k8sClient, ds: ds}
|
||||
sysUsecase := &systemInfoUsecaseImpl{ds: ds}
|
||||
userUsecase = &userUsecaseImpl{ds: ds, projectUsecase: projectUsecase, sysUsecase: sysUsecase}
|
||||
})
|
||||
AfterEach(func() {
|
||||
err := k8sClient.Delete(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: db}})
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
It("Test create user", func() {
|
||||
user, err := userUsecase.CreateUser(context.Background(), apisv1.CreateUserRequest{
|
||||
Name: "name",
|
||||
Alias: "alias",
|
||||
Email: "email@example.com",
|
||||
Password: "password",
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(user.Name).Should(Equal("name"))
|
||||
Expect(user.Alias).Should(Equal("alias"))
|
||||
Expect(user.Email).Should(Equal("email@example.com"))
|
||||
|
||||
u := &model.User{
|
||||
Name: "name",
|
||||
}
|
||||
err = ds.Get(context.Background(), u)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(u.Name).Should(Equal("name"))
|
||||
Expect(u.Alias).Should(Equal("alias"))
|
||||
Expect(u.Email).Should(Equal("email@example.com"))
|
||||
Expect(u.Disabled).Should(Equal(false))
|
||||
Expect(compareHashWithPassword(u.Password, "password")).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test detail user", func() {
|
||||
ctx := context.Background()
|
||||
err := ds.Add(ctx, &model.User{
|
||||
Name: "name",
|
||||
Alias: "alias",
|
||||
Email: "email@example.com",
|
||||
Password: "password",
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
for i := 0; i < 2; i++ {
|
||||
err = ds.Add(ctx, &model.ProjectUser{
|
||||
Username: "name",
|
||||
ProjectName: fmt.Sprintf("project-%d", i),
|
||||
UserRoles: []string{fmt.Sprintf("user-role-%d", i)},
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
err = ds.Add(ctx, &model.Project{
|
||||
Name: fmt.Sprintf("project-%d", i),
|
||||
Alias: fmt.Sprintf("project-alias-%d", i),
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
}
|
||||
|
||||
userModel := &model.User{
|
||||
Name: "name",
|
||||
}
|
||||
err = ds.Get(ctx, userModel)
|
||||
Expect(err).Should(BeNil())
|
||||
user, err := userUsecase.DetailUser(ctx, userModel)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(user.Name).Should(Equal("name"))
|
||||
Expect(user.Alias).Should(Equal("alias"))
|
||||
Expect(user.Email).Should(Equal("email@example.com"))
|
||||
Expect(user.Projects).Should(Equal([]apisv1.ProjectUserBase{
|
||||
{
|
||||
Name: "project-1",
|
||||
Alias: "project-alias-1",
|
||||
UserRoles: []string{"user-role-1"},
|
||||
},
|
||||
{
|
||||
Name: "project-0",
|
||||
Alias: "project-alias-0",
|
||||
UserRoles: []string{"user-role-0"},
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
It("Test list users", func() {
|
||||
ctx := context.Background()
|
||||
for i := 0; i < 2; i++ {
|
||||
err := ds.Add(ctx, &model.User{
|
||||
Name: fmt.Sprintf("name-%d", i),
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
}
|
||||
users, err := userUsecase.ListUsers(ctx, 0, 10, apisv1.ListUserOptions{Name: "1"})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(users.Total).Should(Equal(int64(1)))
|
||||
|
||||
users, err = userUsecase.ListUsers(ctx, 0, 10, apisv1.ListUserOptions{})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(users.Total).Should(Equal(int64(2)))
|
||||
})
|
||||
|
||||
It("Test delete user", func() {
|
||||
ctx := context.Background()
|
||||
err := ds.Add(ctx, &model.User{
|
||||
Name: "name",
|
||||
Alias: "alias",
|
||||
Email: "email@example.com",
|
||||
Password: "password",
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
users, err := userUsecase.ListUsers(ctx, 0, 10, apisv1.ListUserOptions{})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(users.Total).Should(Equal(int64(1)))
|
||||
|
||||
err = userUsecase.DeleteUser(ctx, "name")
|
||||
Expect(err).Should(BeNil())
|
||||
users, err = userUsecase.ListUsers(ctx, 0, 10, apisv1.ListUserOptions{})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(users.Total).Should(Equal(int64(0)))
|
||||
})
|
||||
|
||||
It("Test update user", func() {
|
||||
ctx := context.Background()
|
||||
userModel := &model.User{
|
||||
Name: "name",
|
||||
Alias: "alias",
|
||||
Email: "email@example.com",
|
||||
Password: "password",
|
||||
}
|
||||
err := ds.Add(ctx, userModel)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
_, err = userUsecase.UpdateUser(ctx, userModel, apisv1.UpdateUserRequest{
|
||||
Alias: "new-alias",
|
||||
Password: "new-password",
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
newUser := &model.User{
|
||||
Name: "name",
|
||||
}
|
||||
err = ds.Get(ctx, newUser)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(newUser.Alias).Should(Equal("new-alias"))
|
||||
Expect(compareHashWithPassword(newUser.Password, "new-password")).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test disable user", func() {
|
||||
ctx := context.Background()
|
||||
userModel := &model.User{
|
||||
Name: "name",
|
||||
Disabled: true,
|
||||
}
|
||||
err := ds.Add(ctx, userModel)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
err = userUsecase.DisableUser(ctx, userModel)
|
||||
Expect(err).Should(Equal(bcode.ErrUserAlreadyDisabled))
|
||||
userModel.Disabled = false
|
||||
err = ds.Put(ctx, userModel)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
err = userUsecase.DisableUser(ctx, userModel)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
newUser := &model.User{
|
||||
Name: "name",
|
||||
}
|
||||
err = ds.Get(ctx, newUser)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(newUser.Disabled).Should(Equal(true))
|
||||
})
|
||||
|
||||
It("Test enable user", func() {
|
||||
ctx := context.Background()
|
||||
userModel := &model.User{
|
||||
Name: "name",
|
||||
Disabled: false,
|
||||
}
|
||||
err := ds.Add(ctx, userModel)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
err = userUsecase.EnableUser(ctx, userModel)
|
||||
Expect(err).Should(Equal(bcode.ErrUserAlreadyEnabled))
|
||||
userModel.Disabled = true
|
||||
err = ds.Put(ctx, userModel)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
err = userUsecase.EnableUser(ctx, userModel)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
newUser := &model.User{
|
||||
Name: "name",
|
||||
}
|
||||
err = ds.Get(ctx, newUser)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(newUser.Disabled).Should(Equal(false))
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package bcode
|
||||
|
||||
var (
|
||||
// ErrUnsupportedEmailModification is the error of unsupported email modification
|
||||
ErrUnsupportedEmailModification = NewBcode(400, 14001, "the user already has an email address and cannot modify it again")
|
||||
// ErrUserAlreadyDisabled is the error of user already disabled
|
||||
ErrUserAlreadyDisabled = NewBcode(400, 14002, "the user is already disabled")
|
||||
// ErrUserAlreadyEnabled is the error of user already enabled
|
||||
ErrUserAlreadyEnabled = NewBcode(400, 14003, "the user is already enabled")
|
||||
// ErrUserCannotModified is the error of user cannot modified
|
||||
ErrUserCannotModified = NewBcode(400, 14004, "the user cannot be modified in dex login mode")
|
||||
// ErrUserInvalidPassword is the error of user invalid password
|
||||
ErrUserInvalidPassword = NewBcode(400, 14005, "the password is invalid")
|
||||
)
|
||||
|
|
@ -38,7 +38,7 @@ func NewAuthenticationWebService(authenticationUsecase usecase.AuthenticationUse
|
|||
|
||||
func (c *authenticationWebService) GetWebService() *restful.WebService {
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(versionPrefix).
|
||||
ws.Path(versionPrefix+"/auth").
|
||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
||||
Produces(restful.MIME_JSON, restful.MIME_XML).
|
||||
Doc("api for authentication manage")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package webservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
restfulspec "github.com/emicklei/go-restful-openapi/v2"
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/usecase"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
|
||||
)
|
||||
|
||||
type userWebService struct {
|
||||
userUsecase usecase.UserUsecase
|
||||
}
|
||||
|
||||
// NewUserWebService is the webservice of user
|
||||
func NewUserWebService(userUsecase usecase.UserUsecase) WebService {
|
||||
return &userWebService{
|
||||
userUsecase: userUsecase,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *userWebService) GetWebService() *restful.WebService {
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(versionPrefix+"/users").
|
||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
||||
Produces(restful.MIME_JSON, restful.MIME_XML).
|
||||
Doc("api for user manage")
|
||||
|
||||
tags := []string{"users"}
|
||||
|
||||
ws.Route(ws.GET("/").To(c.listUser).
|
||||
Doc("list users").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Param(ws.QueryParameter("page", "query the page number").DataType("integer")).
|
||||
Param(ws.QueryParameter("pageSize", "query the page size number").DataType("integer")).
|
||||
Param(ws.QueryParameter("name", "fuzzy search based on name").DataType("string")).
|
||||
Param(ws.QueryParameter("email", "fuzzy search based on email").DataType("string")).
|
||||
Param(ws.QueryParameter("alias", "fuzzy search based on alias").DataType("string")).
|
||||
Returns(200, "OK", apis.ListUserResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ListUserResponse{}))
|
||||
|
||||
ws.Route(ws.POST("/").To(c.createUser).
|
||||
Doc("create a user").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Reads(apis.CreateUserRequest{}).
|
||||
Returns(200, "OK", apis.UserBase{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.UserBase{}))
|
||||
|
||||
ws.Route(ws.GET("/{username}").To(c.detailUser).
|
||||
Doc("get user detail").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(c.userCheckFilter).
|
||||
Returns(200, "OK", apis.DetailUserResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.DetailUserResponse{}))
|
||||
|
||||
ws.Route(ws.PUT("/{username}").To(c.updateUser).
|
||||
Doc("update a user's alias or password").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(c.userCheckFilter).
|
||||
Returns(200, "OK", apis.UserBase{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.UserBase{}))
|
||||
|
||||
ws.Route(ws.DELETE("/{username}").To(c.deleteUser).
|
||||
Doc("delete a user").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Returns(200, "OK", apis.EmptyResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.EmptyResponse{}))
|
||||
|
||||
ws.Route(ws.GET("/{username}/disable").To(c.disableUser).
|
||||
Doc("disable a user").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(c.userCheckFilter).
|
||||
Returns(200, "OK", apis.EmptyResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.EmptyResponse{}))
|
||||
|
||||
ws.Route(ws.GET("/{username}/enable").To(c.enableUser).
|
||||
Doc("enable a user").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(c.userCheckFilter).
|
||||
Returns(200, "OK", apis.EmptyResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.EmptyResponse{}))
|
||||
return ws
|
||||
}
|
||||
|
||||
func (c *userWebService) userCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
|
||||
user, err := c.userUsecase.GetUser(req.Request.Context(), req.PathParameter("username"))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
req.Request = req.Request.WithContext(context.WithValue(req.Request.Context(), &apis.CtxKeyUser, user))
|
||||
chain.ProcessFilter(req, res)
|
||||
}
|
||||
|
||||
func (c *userWebService) createUser(req *restful.Request, res *restful.Response) {
|
||||
var createReq apis.CreateUserRequest
|
||||
if err := req.ReadEntity(&createReq); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := validate.Struct(&createReq); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
resp, err := c.userUsecase.CreateUser(req.Request.Context(), createReq)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := res.WriteEntity(resp); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *userWebService) detailUser(req *restful.Request, res *restful.Response) {
|
||||
user := req.Request.Context().Value(&apis.CtxKeyUser).(*model.User)
|
||||
resp, err := c.userUsecase.DetailUser(req.Request.Context(), user)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := res.WriteEntity(resp); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *userWebService) deleteUser(req *restful.Request, res *restful.Response) {
|
||||
err := c.userUsecase.DeleteUser(req.Request.Context(), req.PathParameter("username"))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *userWebService) listUser(req *restful.Request, res *restful.Response) {
|
||||
page, pageSize, err := utils.ExtractPagingParams(req, minPageSize, maxPageSize)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
resp, err := c.userUsecase.ListUsers(req.Request.Context(), page, pageSize, apis.ListUserOptions{
|
||||
Name: req.QueryParameter("name"),
|
||||
Alias: req.QueryParameter("alias"),
|
||||
Email: req.QueryParameter("email"),
|
||||
})
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := res.WriteEntity(resp); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *userWebService) updateUser(req *restful.Request, res *restful.Response) {
|
||||
user := req.Request.Context().Value(&apis.CtxKeyUser).(*model.User)
|
||||
var updateReq apis.UpdateUserRequest
|
||||
if err := req.ReadEntity(&updateReq); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := validate.Struct(&updateReq); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
resp, err := c.userUsecase.UpdateUser(req.Request.Context(), user, updateReq)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := res.WriteEntity(resp); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *userWebService) disableUser(req *restful.Request, res *restful.Response) {
|
||||
user := req.Request.Context().Value(&apis.CtxKeyUser).(*model.User)
|
||||
err := c.userUsecase.DisableUser(req.Request.Context(), user)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *userWebService) enableUser(req *restful.Request, res *restful.Response) {
|
||||
user := req.Request.Context().Value(&apis.CtxKeyUser).(*model.User)
|
||||
err := c.userUsecase.EnableUser(req.Request.Context(), user)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ package webservice
|
|||
|
||||
import (
|
||||
"regexp"
|
||||
"unicode"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
|
||||
|
|
@ -26,7 +27,10 @@ import (
|
|||
|
||||
var validate = validator.New()
|
||||
|
||||
var nameRegexp = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)
|
||||
var (
|
||||
nameRegexp = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)
|
||||
emailRegexp = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
|
||||
)
|
||||
|
||||
const (
|
||||
minPageSize = 5
|
||||
|
|
@ -43,6 +47,12 @@ func init() {
|
|||
if err := validate.RegisterValidation("checkpayloadtype", ValidatePayloadType); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := validate.RegisterValidation("checkemail", ValidateEmail); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := validate.RegisterValidation("checkpassword", ValidatePassword); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ValidatePayloadType check PayloadType
|
||||
|
|
@ -73,3 +83,35 @@ func ValidateAlias(fl validator.FieldLevel) bool {
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ValidateEmail custom check email field
|
||||
func ValidateEmail(fl validator.FieldLevel) bool {
|
||||
value := fl.Field().String()
|
||||
if value == "" {
|
||||
return true
|
||||
}
|
||||
return emailRegexp.MatchString(value)
|
||||
}
|
||||
|
||||
// ValidatePassword custom check password field
|
||||
func ValidatePassword(fl validator.FieldLevel) bool {
|
||||
value := fl.Field().String()
|
||||
if value == "" {
|
||||
return true
|
||||
}
|
||||
if len(value) < 8 || len(value) > 16 {
|
||||
return false
|
||||
}
|
||||
// go's regex doesn't support backtracking so check the password with a loop
|
||||
letter := false
|
||||
num := false
|
||||
for _, c := range value {
|
||||
switch {
|
||||
case unicode.IsNumber(c):
|
||||
num = true
|
||||
case unicode.IsLetter(c):
|
||||
letter = true
|
||||
}
|
||||
}
|
||||
return letter && num
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,4 +67,64 @@ var _ = Describe("Test validate function", func() {
|
|||
err = validate.Struct(&component)
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test check email validate ", func() {
|
||||
invalidEmail := &apisv1.CreateUserRequest{
|
||||
Name: "user",
|
||||
Password: "password1",
|
||||
Email: "invalidEmail",
|
||||
}
|
||||
err := validate.Struct(invalidEmail)
|
||||
Expect(err).ShouldNot(BeNil())
|
||||
|
||||
validEmail := &apisv1.CreateUserRequest{
|
||||
Name: "user",
|
||||
Password: "password1",
|
||||
Email: "test@example.com",
|
||||
}
|
||||
err = validate.Struct(validEmail)
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test check password validate ", func() {
|
||||
invalidPwd := &apisv1.CreateUserRequest{
|
||||
Name: "user",
|
||||
Password: "password",
|
||||
Email: "test@example.com",
|
||||
}
|
||||
err := validate.Struct(invalidPwd)
|
||||
Expect(err).ShouldNot(BeNil())
|
||||
|
||||
invalidPwd = &apisv1.CreateUserRequest{
|
||||
Name: "user",
|
||||
Password: "a",
|
||||
Email: "test@example.com",
|
||||
}
|
||||
err = validate.Struct(invalidPwd)
|
||||
Expect(err).ShouldNot(BeNil())
|
||||
|
||||
invalidPwd = &apisv1.CreateUserRequest{
|
||||
Name: "user",
|
||||
Password: "passwordpasswordpassword",
|
||||
Email: "test@example.com",
|
||||
}
|
||||
err = validate.Struct(invalidPwd)
|
||||
Expect(err).ShouldNot(BeNil())
|
||||
|
||||
invalidPwd = &apisv1.CreateUserRequest{
|
||||
Name: "user",
|
||||
Password: "11111111",
|
||||
Email: "test@example.com",
|
||||
}
|
||||
err = validate.Struct(invalidPwd)
|
||||
Expect(err).ShouldNot(BeNil())
|
||||
|
||||
validPwd := &apisv1.CreateUserRequest{
|
||||
Name: "user",
|
||||
Password: "password1",
|
||||
Email: "test@example.com",
|
||||
}
|
||||
err = validate.Struct(validPwd)
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ func Init(ds datastore.DataStore, addonCacheTime time.Duration) {
|
|||
systemInfoUsecase := usecase.NewSystemInfoUsecase(ds)
|
||||
helmUsecase := usecase.NewHelmUsecase()
|
||||
authenticationUsecase := usecase.NewAuthenticationUsecase(ds, systemInfoUsecase)
|
||||
userUsecase := usecase.NewUserUsecase(ds, projectUsecase, systemInfoUsecase)
|
||||
|
||||
// init for default values
|
||||
|
||||
|
|
@ -100,6 +101,7 @@ func Init(ds datastore.DataStore, addonCacheTime time.Duration) {
|
|||
|
||||
// Authentication
|
||||
RegisterWebService(NewAuthenticationWebService(authenticationUsecase))
|
||||
RegisterWebService(NewUserWebService(userUsecase))
|
||||
|
||||
RegisterWebService(NewSystemInfoWebService(systemInfoUsecase))
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package e2e_apiserver_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
username = "my-user"
|
||||
urlPrefix = "http://127.0.0.1:8000/api/v1/users"
|
||||
)
|
||||
|
||||
var _ = Describe("Test user rest api", func() {
|
||||
It("Test create user", func() {
|
||||
defer GinkgoRecover()
|
||||
var req = apisv1.CreateUserRequest{
|
||||
Name: username,
|
||||
Alias: "alias",
|
||||
Email: "test@example.com",
|
||||
Password: "password1",
|
||||
}
|
||||
bodyByte, err := json.Marshal(req)
|
||||
Expect(err).Should(BeNil())
|
||||
res, err := http.Post(urlPrefix, "application/json", bytes.NewBuffer(bodyByte))
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(res).ShouldNot(BeNil())
|
||||
Expect(res.StatusCode).Should(Equal(200))
|
||||
Expect(res.Body).ShouldNot(BeNil())
|
||||
defer res.Body.Close()
|
||||
userBase := &apisv1.UserBase{}
|
||||
err = json.NewDecoder(res.Body).Decode(&userBase)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(userBase.Name).Should(Equal(username))
|
||||
Expect(userBase.Alias).Should(Equal("alias"))
|
||||
Expect(userBase.Email).Should(Equal("test@example.com"))
|
||||
})
|
||||
|
||||
It("Test list users", func() {
|
||||
defer GinkgoRecover()
|
||||
res, err := http.Get(urlPrefix)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(res).ShouldNot(BeNil())
|
||||
Expect(res.StatusCode).Should(Equal(200))
|
||||
Expect(res.Body).ShouldNot(BeNil())
|
||||
defer res.Body.Close()
|
||||
users := &apisv1.ListUserResponse{}
|
||||
err = json.NewDecoder(res.Body).Decode(users)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(users.Total).Should(Equal(int64(1)))
|
||||
})
|
||||
|
||||
It("Test detail user", func() {
|
||||
defer GinkgoRecover()
|
||||
res, err := http.Get(fmt.Sprintf("%s/%s", urlPrefix, username))
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(res).ShouldNot(BeNil())
|
||||
Expect(res.StatusCode).Should(Equal(200))
|
||||
Expect(res.Body).ShouldNot(BeNil())
|
||||
defer res.Body.Close()
|
||||
var detail apisv1.DetailUserResponse
|
||||
err = json.NewDecoder(res.Body).Decode(&detail)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(detail.Name).Should(Equal(username))
|
||||
Expect(detail.Alias).Should(Equal("alias"))
|
||||
Expect(detail.Email).Should(Equal("test@example.com"))
|
||||
Expect(len(detail.Projects)).Should(Equal(0))
|
||||
})
|
||||
|
||||
It("Test update user", func() {
|
||||
defer GinkgoRecover()
|
||||
var updateReq = apisv1.UpdateUserRequest{
|
||||
Alias: "updated-alias",
|
||||
}
|
||||
bodyByte, err := json.Marshal(updateReq)
|
||||
Expect(err).Should(BeNil())
|
||||
req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/%s", urlPrefix, username), bytes.NewBuffer(bodyByte))
|
||||
Expect(err).Should(BeNil())
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(res).ShouldNot(BeNil())
|
||||
Expect(res.StatusCode).Should(Equal(200))
|
||||
Expect(res.Body).ShouldNot(BeNil())
|
||||
defer res.Body.Close()
|
||||
userBase := &apisv1.UserBase{}
|
||||
err = json.NewDecoder(res.Body).Decode(&userBase)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(userBase.Alias).Should(Equal("updated-alias"))
|
||||
})
|
||||
|
||||
It("Test delete user", func() {
|
||||
defer GinkgoRecover()
|
||||
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/%s", urlPrefix, username), nil)
|
||||
Expect(err).Should(BeNil())
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(res).ShouldNot(BeNil())
|
||||
Expect(res.StatusCode).Should(Equal(200))
|
||||
})
|
||||
|
||||
})
|
||||
Loading…
Reference in New Issue