Skip to content

Commit

Permalink
Set up ory kratos, add identity admin api (#1460)
Browse files Browse the repository at this point in the history
* Set up ory kratos, add identity admin api

* Code refactor, comment in kratos config
  • Loading branch information
shuofan authored Sep 22, 2021
1 parent 87f47cb commit af0f09f
Show file tree
Hide file tree
Showing 44 changed files with 1,411 additions and 156 deletions.
8 changes: 8 additions & 0 deletions .erda/migrations/kratos/20210914-kratos-uc-id.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CREATE TABLE `kratos_uc_userid_mapping` (
`id` varchar(50) NOT NULL COMMENT 'uc userid',
`user_id` varchar(191) NOT NULL COMMENT 'kratos user uuid',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'created time',
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'updated time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='id mapping';
5 changes: 5 additions & 0 deletions apistructs/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ type UserListResponse struct {
Data UserListResponseData `json:"data"`
}

type UserIDResponse struct {
Header
Data string `json:"data"`
}

// UserListResponseData 用户批量查询响应数据
type UserListResponseData struct {
Users []UserInfo `json:"users"`
Expand Down
21 changes: 21 additions & 0 deletions bundle/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,27 @@ func (b *Bundle) ListUsers(req apistructs.UserListRequest) (*apistructs.UserList
return &userResp.Data, nil
}

func (b *Bundle) GetUcUserID(uuid string) (string, error) {
host, err := b.urls.CoreServices()
if err != nil {
return "", err
}
hc := b.hc

var userResp apistructs.UserIDResponse
resp, err := hc.Get(host).Path("/api/users/actions/get-uc-user-id").
Header(httputil.InternalHeader, "bundle").
Param("id", uuid).
Do().JSON(&userResp)
if err != nil {
return "", apierrors.ErrInvoke.InternalError(err)
}
if !resp.IsOK() || !userResp.Success {
return "", toAPIError(resp.StatusCode(), userResp.Error)
}
return userResp.Data, nil
}

func (b *Bundle) SearchUser(params url.Values) (*apistructs.UserListResponseData, error) {
host, err := b.urls.CoreServices()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion conf/openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ openapi-auth:
openapi-auth-ory-kratos:
_enable: ${ORY_ENABLED:false}
weight: 100
ory_kratos_addr: "${ORY_KRATOS_ADDR:kratos:4433}"
ory_kratos_addr: "${ORY_KRATOS_ADDR:kratos-public}"
openapi-auth-uc:
_enable: ${UC_ENABLED:true}
weight: 100
Expand Down
3 changes: 1 addition & 2 deletions modules/cmp/conf/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ type Conf struct {
UCClientSecret string `env:"UC_CLIENT_SECRET"`
// ory/kratos config
OryEnabled bool `default:"false" env:"ORY_ENABLED"`
OryKratosAddr string `default:"kratos:4433" env:"KRATOS_ADDR"`
OryKratosPrivateAddr string `default:"kratos:4434" env:"KRATOS_PRIVATE_ADDR"`
OryKratosPrivateAddr string `default:"kratos-admin" env:"ORY_KRATOS_ADMIN_ADDR"`

// size of steve server cache, default 1Gi
CacheSize int64 `default:"1073741824" env:"CMP_CACHE_SIZE"`
Expand Down
1 change: 1 addition & 0 deletions modules/cmp/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ func do(ctx context.Context) (*httpserver.Server, error) {
uc := ucauth.NewUCClient(discover.UC(), conf.UCClientID(), conf.UCClientSecret())
if conf.OryEnabled() {
uc = ucauth.NewUCClient(conf.OryKratosPrivateAddr(), conf.OryCompatibleClientID(), conf.OryCompatibleClientSecret())
uc.SetDBClient(db.DB)
}

// init Bundle
Expand Down
4 changes: 1 addition & 3 deletions modules/core-services/conf/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ type Conf struct {

// ory/kratos config
OryEnabled bool `default:"false" env:"ORY_ENABLED"`
OryKratosAddr string `default:"kratos:4433" env:"KRATOS_ADDR"`
OryKratosPrivateAddr string `default:"kratos:4434" env:"KRATOS_PRIVATE_ADDR"`
OryKratosPrivateAddr string `default:"kratos-admin" env:"ORY_KRATOS_ADMIN_ADDR"`

// Allow people who are not admin to create org
CreateOrgEnabled bool `default:"false" env:"CREATE_ORG_ENABLED"`
Expand Down Expand Up @@ -112,7 +111,6 @@ var (
func initPermissions() {
permissions = getAllFiles("erda-configs/permission", permissions)
}

func initAuditTemplate() {
auditsTemplate = genTempFromFiles("erda-configs/audit/template.json")
}
Expand Down
58 changes: 58 additions & 0 deletions modules/core-services/dao/migration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) 2021 Terminus, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package dao

import (
"fmt"
"strconv"

"github.com/jinzhu/gorm"

"github.com/erda-project/erda/modules/core-services/model"
)

var joinMap = "LEFT OUTER JOIN kratos_uc_userid_mapping on kratos_uc_userid_mapping.id = uc_user.id"

const noPass = "no pass"

func (client *DBClient) GetUcUserList() ([]model.User, error) {
var users []model.User
sql := client.Table("uc_user").Joins(joinMap)
if err := sql.Where("password != ? AND password IS NOT NULL AND kratos_uc_userid_mapping.id IS NULL", noPass).Find(&users).Error; err != nil {
return nil, err
}
return users, nil
}

func (client *DBClient) InsertMapping(userID, uuid, hash string) error {
return client.Transaction(func(tx *gorm.DB) error {
sql := fmt.Sprintf("UPDATE identity_credentials SET config = JSON_SET(config, '$.hashed_password', ?) WHERE identity_id = ?")
if err := tx.Exec(sql, hash, uuid).Error; err != nil {
return err
}
return client.Table("kratos_uc_userid_mapping").Create(&model.UserIDMapping{ID: userID, UserID: uuid}).Error
})
}

func (client *DBClient) GetUcUserID(uuid string) (string, error) {
var users []model.User
if err := client.Table("kratos_uc_userid_mapping").Select("id").Where("user_id = ?", uuid).Find(&users).Error; err != nil {
return "", err
}
if len(users) == 0 {
return "", nil
}
return strconv.FormatInt(users[0].ID, 10), nil
}
15 changes: 15 additions & 0 deletions modules/core-services/dao/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,18 @@ func (client *DBClient) BulkInsert(objects interface{}, excludeColumns ...string
}
return gormbulk.BulkInsert(client.DB, structSlice, BULK_INSERT_CHUNK_SIZE, excludeColumns...)
}

func (db *DBClient) Transaction(f func(tx *gorm.DB) error) error {
tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := f(tx); err != nil {
tx.Rollback()
return err
}

return tx.Commit().Error
}
13 changes: 13 additions & 0 deletions modules/core-services/endpoints/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"github.com/erda-project/erda/modules/core-services/services/org"
"github.com/erda-project/erda/modules/core-services/services/permission"
"github.com/erda-project/erda/modules/core-services/services/project"
"github.com/erda-project/erda/modules/core-services/services/user"
"github.com/erda-project/erda/pkg/http/httpserver"
"github.com/erda-project/erda/pkg/i18n"
"github.com/erda-project/erda/pkg/jsonstore"
Expand Down Expand Up @@ -71,6 +72,7 @@ type Endpoints struct {
audit *audit.Audit
errorbox *errorbox.ErrorBox
fileSvc *filesvc.FileService
user *user.User
}

type Option func(*Endpoints)
Expand Down Expand Up @@ -244,6 +246,12 @@ func WithFileSvc(svc *filesvc.FileService) Option {
}
}

func WithUserSvc(svc *user.User) Option {
return func(e *Endpoints) {
e.user = svc
}
}

// DBClient 获取db client
func (e *Endpoints) DBClient() *dao.DBClient {
return e.db
Expand All @@ -254,6 +262,10 @@ func (e *Endpoints) GetLocale(request *http.Request) *i18n.LocaleResource {
return e.bdl.GetLocaleByRequest(request)
}

func (e *Endpoints) UserSvc() *user.User {
return e.user
}

// Routes 返回 endpoints 的所有 endpoint 方法,也就是 route.
func (e *Endpoints) Routes() []httpserver.Endpoint {
return []httpserver.Endpoint{
Expand Down Expand Up @@ -433,5 +445,6 @@ func (e *Endpoints) Routes() []httpserver.Endpoint {
{Path: "/api/users", Method: http.MethodGet, Handler: e.ListUser},
{Path: "/api/users/current", Method: http.MethodGet, Handler: e.GetCurrentUser},
{Path: "/api/users/actions/search", Method: http.MethodGet, Handler: e.SearchUser},
{Path: "/api/users/actions/get-uc-user-id", Method: http.MethodGet, Handler: e.GetUcUserID},
}
}
10 changes: 10 additions & 0 deletions modules/core-services/endpoints/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,16 @@ func (e *Endpoints) GetCurrentUser(ctx context.Context, r *http.Request, vars ma
return httpserver.OkResp(*convertToUserInfo(user, false))
}

func (e *Endpoints) GetUcUserID(ctx context.Context, r *http.Request, vars map[string]string) (
httpserver.Responser, error) {
id := r.URL.Query().Get("id")
userID, err := e.db.GetUcUserID(id)
if err != nil {
return apierrors.ErrGetUser.InternalError(err).ToResp(), nil
}
return httpserver.OkResp(userID)
}

func convertToUserInfo(user *ucauth.User, plaintext bool) *apistructs.UserInfo {
if !plaintext {
user.Phone = desensitize.Mobile(user.Phone)
Expand Down
10 changes: 10 additions & 0 deletions modules/core-services/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import (
"github.com/erda-project/erda/modules/core-services/services/org"
"github.com/erda-project/erda/modules/core-services/services/permission"
"github.com/erda-project/erda/modules/core-services/services/project"
"github.com/erda-project/erda/modules/core-services/services/user"
"github.com/erda-project/erda/modules/core-services/utils"
"github.com/erda-project/erda/pkg/discover"
"github.com/erda-project/erda/pkg/http/httpclient"
Expand Down Expand Up @@ -74,6 +75,8 @@ func (p *provider) Initialize() error {
bundle.WithCollector(),
)

go ep.UserSvc().UcUserMigration()

server := httpserver.New(conf.ListenAddr())
server.RegisterEndpoint(ep.Routes())
server.WithLocaleLoader(bdl.GetLocaleLoader())
Expand Down Expand Up @@ -143,6 +146,7 @@ func (p *provider) initEndpoints() (*endpoints.Endpoints, error) {
uc := ucauth.NewUCClient(discover.UC(), conf.UCClientID(), conf.UCClientSecret())
if conf.OryEnabled() {
uc = ucauth.NewUCClient(conf.OryKratosPrivateAddr(), conf.OryCompatibleClientID(), conf.OryCompatibleClientSecret())
uc.SetDBClient(db.DB)
}

// init bundle
Expand Down Expand Up @@ -253,6 +257,11 @@ func (p *provider) initEndpoints() (*endpoints.Endpoints, error) {
filesvc.WithEtcdClient(etcdStore),
)

user := user.New(
user.WithDBClient(db),
user.WithUCClient(uc),
)

// queryStringDecoder
queryStringDecoder := schema.NewDecoder()
queryStringDecoder.IgnoreUnknownKeys(true)
Expand Down Expand Up @@ -282,6 +291,7 @@ func (p *provider) initEndpoints() (*endpoints.Endpoints, error) {
endpoints.WithAudit(audit),
endpoints.WithErrorBox(errorBox),
endpoints.WithFileSvc(fileSvc),
endpoints.WithUserSvc(user),
)

return ep, nil
Expand Down
34 changes: 34 additions & 0 deletions modules/core-services/model/uc_migration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) 2021 Terminus, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package model

type User struct {
BaseModel
Avatar string
Username string
Nickname string
Mobile string
Email string
Password string
}

type Config struct {
HashedPassword string `json:"hashed_password"`
}

type UserIDMapping struct {
ID string
UserID string
}
Loading

0 comments on commit af0f09f

Please sign in to comment.