mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-19 15:17:43 +01:00
Search UAA when adding member to a project.
1)Enable UAA client to search UAA by calling '/Users' API. 2)Implement 'SearchUser' in UAA auth helper, register it to auth package.
This commit is contained in:
parent
7c510fa2c8
commit
da20e4f11c
@ -15,37 +15,40 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"fmt"
|
||||
"github.com/vmware/harbor/src/adminserver/systemcfg/store"
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"fmt"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
name = "database"
|
||||
)
|
||||
var(
|
||||
)
|
||||
|
||||
var (
|
||||
numKeys = map[string]bool{
|
||||
common.EmailPort:true,
|
||||
common.LDAPScope:true,
|
||||
common.LDAPTimeout:true,
|
||||
common.TokenExpiration:true,
|
||||
common.MySQLPort:true,
|
||||
common.MaxJobWorkers:true,
|
||||
common.CfgExpiration:true,
|
||||
common.ClairDBPort:true,
|
||||
}
|
||||
boolKeys = map[string]bool{
|
||||
common.WithClair:true,
|
||||
common.WithNotary:true,
|
||||
common.SelfRegistration:true,
|
||||
common.EmailSSL:true,
|
||||
common.EmailInsecure:true,
|
||||
common.LDAPVerifyCert:true,
|
||||
common.EmailPort: true,
|
||||
common.LDAPScope: true,
|
||||
common.LDAPTimeout: true,
|
||||
common.TokenExpiration: true,
|
||||
common.MySQLPort: true,
|
||||
common.MaxJobWorkers: true,
|
||||
common.CfgExpiration: true,
|
||||
common.ClairDBPort: true,
|
||||
}
|
||||
)
|
||||
boolKeys = map[string]bool{
|
||||
common.WithClair: true,
|
||||
common.WithNotary: true,
|
||||
common.SelfRegistration: true,
|
||||
common.EmailSSL: true,
|
||||
common.EmailInsecure: true,
|
||||
common.LDAPVerifyCert: true,
|
||||
common.UAAVerifyCert: true,
|
||||
}
|
||||
)
|
||||
|
||||
type cfgStore struct {
|
||||
name string
|
||||
}
|
||||
@ -56,14 +59,15 @@ func (c *cfgStore) Name() string {
|
||||
}
|
||||
|
||||
// NewCfgStore New a cfg store for database driver
|
||||
func NewCfgStore() (store.Driver, error){
|
||||
func NewCfgStore() (store.Driver, error) {
|
||||
return &cfgStore{
|
||||
name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Read configuration from database
|
||||
func (c *cfgStore) Read() (map[string]interface{}, error) {
|
||||
configEntries,error := dao.GetConfigEntries()
|
||||
configEntries, error := dao.GetConfigEntries()
|
||||
if error != nil {
|
||||
return nil, error
|
||||
}
|
||||
@ -71,53 +75,54 @@ func (c *cfgStore) Read() (map[string]interface{}, error) {
|
||||
}
|
||||
|
||||
// WrapperConfig Wrapper the configuration
|
||||
func WrapperConfig (configEntries []*models.ConfigEntry) (map[string]interface{}, error) {
|
||||
func WrapperConfig(configEntries []*models.ConfigEntry) (map[string]interface{}, error) {
|
||||
config := make(map[string]interface{})
|
||||
for _,entry := range configEntries{
|
||||
if numKeys[entry.Key]{
|
||||
for _, entry := range configEntries {
|
||||
if numKeys[entry.Key] {
|
||||
strvalue, err := strconv.Atoi(entry.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config[entry.Key] = float64(strvalue)
|
||||
}else if boolKeys[entry.Key] {
|
||||
} else if boolKeys[entry.Key] {
|
||||
strvalue, err := strconv.ParseBool(entry.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config[entry.Key]=strvalue
|
||||
}else{
|
||||
config[entry.Key] = strvalue
|
||||
} else {
|
||||
config[entry.Key] = entry.Value
|
||||
}
|
||||
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// Write save configuration to database
|
||||
func (c *cfgStore) Write(config map[string]interface{}) error {
|
||||
configEntries ,_:= TranslateConfig(config)
|
||||
configEntries, _ := TranslateConfig(config)
|
||||
return dao.SaveConfigEntries(configEntries)
|
||||
}
|
||||
|
||||
// TranslateConfig Translate configuration from int, bool, float64 to string
|
||||
func TranslateConfig(config map[string]interface{}) ([]models.ConfigEntry,error) {
|
||||
func TranslateConfig(config map[string]interface{}) ([]models.ConfigEntry, error) {
|
||||
var configEntries []models.ConfigEntry
|
||||
for k, v := range config {
|
||||
var entry = new(models.ConfigEntry)
|
||||
entry.Key = k
|
||||
switch v.(type) {
|
||||
case string:
|
||||
entry.Value=v.(string)
|
||||
entry.Value = v.(string)
|
||||
case int:
|
||||
entry.Value=strconv.Itoa(v.(int))
|
||||
entry.Value = strconv.Itoa(v.(int))
|
||||
case bool:
|
||||
entry.Value=strconv.FormatBool(v.(bool))
|
||||
entry.Value = strconv.FormatBool(v.(bool))
|
||||
case float64:
|
||||
entry.Value=strconv.Itoa(int(v.(float64)))
|
||||
entry.Value = strconv.Itoa(int(v.(float64)))
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown type %v", v)
|
||||
}
|
||||
configEntries = append(configEntries,*entry)
|
||||
configEntries = append(configEntries, *entry)
|
||||
}
|
||||
return configEntries,nil
|
||||
return configEntries, nil
|
||||
}
|
||||
|
@ -130,10 +130,10 @@ var (
|
||||
parse: parseStringToBool,
|
||||
},
|
||||
common.ClairDBPassword: "CLAIR_DB_PASSWORD",
|
||||
common.ClairDB: "CLAIR_DB",
|
||||
common.ClairDB: "CLAIR_DB",
|
||||
common.ClairDBUsername: "CLAIR_DB_USERNAME",
|
||||
common.ClairDBHost: "CLAIR_DB_HOST",
|
||||
common.ClairDBPort: "CLAIR_DB_PORT",
|
||||
common.ClairDBHost: "CLAIR_DB_HOST",
|
||||
common.ClairDBPort: "CLAIR_DB_PORT",
|
||||
common.UAAEndpoint: "UAA_ENDPOINT",
|
||||
common.UAAClientID: "UAA_CLIENTID",
|
||||
common.UAAClientSecret: "UAA_CLIENTSECRET",
|
||||
@ -171,7 +171,10 @@ var (
|
||||
common.UAAEndpoint: "UAA_ENDPOINT",
|
||||
common.UAAClientID: "UAA_CLIENTID",
|
||||
common.UAAClientSecret: "UAA_CLIENTSECRET",
|
||||
common.UAAVerifyCert: "UAA_VERIFY_CERT",
|
||||
common.UAAVerifyCert: &parser{
|
||||
env: "UAA_VERIFY_CERT",
|
||||
parse: parseStringToBool,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -18,6 +18,7 @@ package common
|
||||
const (
|
||||
DBAuth = "db_auth"
|
||||
LDAPAuth = "ldap_auth"
|
||||
UAAAuth = "uaa_auth"
|
||||
ProCrtRestrEveryone = "everyone"
|
||||
ProCrtRestrAdmOnly = "adminonly"
|
||||
LDAPScopeBase = 1
|
||||
|
@ -116,6 +116,12 @@ func GetOrmer() orm.Ormer {
|
||||
func ClearTable(table string) error {
|
||||
o := GetOrmer()
|
||||
sql := fmt.Sprintf("delete from %s where 1=1", table)
|
||||
if table == models.ProjectTable {
|
||||
sql = fmt.Sprintf("delete from %s where project_id > 1", table)
|
||||
}
|
||||
if table == models.UserTable {
|
||||
sql = fmt.Sprintf("delete from %s where user_id > 2", table)
|
||||
}
|
||||
_, err := o.Raw(sql).Exec()
|
||||
return err
|
||||
}
|
||||
|
@ -19,6 +19,9 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ProjectTable is the table name for project
|
||||
const ProjectTable = "project"
|
||||
|
||||
// Project holds the details of a project.
|
||||
type Project struct {
|
||||
ProjectID int64 `orm:"pk;auto;column(project_id)" json:"project_id"`
|
||||
@ -174,3 +177,8 @@ type ProjectQueryResult struct {
|
||||
Total int64
|
||||
Projects []*Project
|
||||
}
|
||||
|
||||
//TableName is required by beego orm to map Project to table project
|
||||
func (p *Project) TableName() string {
|
||||
return ProjectTable
|
||||
}
|
||||
|
@ -18,6 +18,9 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// UserTable is the name of table in DB that holds the user object
|
||||
const UserTable = "user"
|
||||
|
||||
// User holds the details of a user.
|
||||
type User struct {
|
||||
UserID int `orm:"pk;auto;column(user_id)" json:"user_id"`
|
||||
@ -45,3 +48,8 @@ type UserQuery struct {
|
||||
Email string
|
||||
Pagination *Pagination
|
||||
}
|
||||
|
||||
// TableName ...
|
||||
func (u *User) TableName() string {
|
||||
return UserTable
|
||||
}
|
||||
|
@ -19,12 +19,25 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/clientcredentials"
|
||||
)
|
||||
|
||||
const (
|
||||
//TokenURLSuffix ...
|
||||
TokenURLSuffix = "/oauth/token"
|
||||
//AuthURLSuffix ...
|
||||
AuthURLSuffix = "/oauth/authorize"
|
||||
//UserInfoURLSuffix ...
|
||||
UserInfoURLSuffix = "/userinfo"
|
||||
//UsersURLSuffix ...
|
||||
UsersURLSuffix = "/Users"
|
||||
)
|
||||
|
||||
// Client provides funcs to interact with UAA.
|
||||
@ -33,6 +46,8 @@ type Client interface {
|
||||
PasswordAuth(username, password string) (*oauth2.Token, error)
|
||||
//GetUserInfoByToken send the token to OIDC endpoint to get user info, currently it's also used to validate the token.
|
||||
GetUserInfo(token string) (*UserInfo, error)
|
||||
//SearchUser searches a user based on user name.
|
||||
SearchUser(name string) ([]*SearchUserEntry, error)
|
||||
}
|
||||
|
||||
// ClientConfig values to initialize UAA Client
|
||||
@ -56,21 +71,43 @@ type UserInfo struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
//SearchUserEmailEntry ...
|
||||
type SearchUserEmailEntry struct {
|
||||
Value string `json:"value"`
|
||||
Primary bool `json:"primary"`
|
||||
}
|
||||
|
||||
//SearchUserEntry is the struct of an entry of user within search result.
|
||||
type SearchUserEntry struct {
|
||||
ID string `json:"id"`
|
||||
ExtID string `json:"externalId"`
|
||||
UserName string `json:"userName"`
|
||||
Emails []SearchUserEmailEntry `json:"emails"`
|
||||
Groups []interface{}
|
||||
}
|
||||
|
||||
//SearchUserRes is the struct to parse the result of search user API of UAA
|
||||
type SearchUserRes struct {
|
||||
Resources []*SearchUserEntry `json:"resources"`
|
||||
TotalResults int `json:"totalResults"`
|
||||
Schemas []string `json:"schemas"`
|
||||
}
|
||||
|
||||
// DefaultClient leverages oauth2 pacakge for oauth features
|
||||
type defaultClient struct {
|
||||
httpClient *http.Client
|
||||
oauth2Cfg *oauth2.Config
|
||||
twoLegCfg *clientcredentials.Config
|
||||
endpoint string
|
||||
//TODO: add public key, etc...
|
||||
}
|
||||
|
||||
func (dc *defaultClient) PasswordAuth(username, password string) (*oauth2.Token, error) {
|
||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, dc.httpClient)
|
||||
return dc.oauth2Cfg.PasswordCredentialsToken(ctx, username, password)
|
||||
return dc.oauth2Cfg.PasswordCredentialsToken(dc.prepareCtx(), username, password)
|
||||
}
|
||||
|
||||
func (dc *defaultClient) GetUserInfo(token string) (*UserInfo, error) {
|
||||
userInfoURL := dc.endpoint + "/uaa/userinfo"
|
||||
userInfoURL := dc.endpoint + UserInfoURLSuffix
|
||||
req, err := http.NewRequest(http.MethodGet, userInfoURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -92,6 +129,45 @@ func (dc *defaultClient) GetUserInfo(token string) (*UserInfo, error) {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (dc *defaultClient) SearchUser(username string) ([]*SearchUserEntry, error) {
|
||||
token, err := dc.twoLegCfg.Token(dc.prepareCtx())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
url := dc.endpoint + UsersURLSuffix
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q := req.URL.Query()
|
||||
q.Add("filter", fmt.Sprintf("Username eq '%s'", username))
|
||||
req.URL.RawQuery = q.Encode()
|
||||
token.SetAuthHeader(req)
|
||||
log.Debugf("request URL: %s", req.URL)
|
||||
resp, err := dc.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("Unexpected status code for searching user in UAA: %d, response: %s", resp.StatusCode, string(bytes))
|
||||
}
|
||||
res := &SearchUserRes{}
|
||||
if err := json.Unmarshal(bytes, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.Resources, nil
|
||||
}
|
||||
|
||||
func (dc *defaultClient) prepareCtx() context.Context {
|
||||
return context.WithValue(context.Background(), oauth2.HTTPClient, dc.httpClient)
|
||||
}
|
||||
|
||||
// NewDefaultClient creates an instance of defaultClient.
|
||||
func NewDefaultClient(cfg *ClientConfig) (Client, error) {
|
||||
url := cfg.Endpoint
|
||||
@ -125,14 +201,21 @@ func NewDefaultClient(cfg *ClientConfig) (Client, error) {
|
||||
ClientID: cfg.ClientID,
|
||||
ClientSecret: cfg.ClientSecret,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
TokenURL: url + "/uaa/oauth/token",
|
||||
AuthURL: url + "/uaa/oauth/authorize",
|
||||
TokenURL: url + TokenURLSuffix,
|
||||
AuthURL: url + AuthURLSuffix,
|
||||
},
|
||||
}
|
||||
|
||||
cc := &clientcredentials.Config{
|
||||
ClientID: cfg.ClientID,
|
||||
ClientSecret: cfg.ClientSecret,
|
||||
TokenURL: url + TokenURLSuffix,
|
||||
}
|
||||
|
||||
return &defaultClient{
|
||||
httpClient: hc,
|
||||
oauth2Cfg: oc,
|
||||
twoLegCfg: cc,
|
||||
endpoint: url,
|
||||
}, nil
|
||||
}
|
||||
|
@ -30,15 +30,18 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordAuth(t *testing.T) {
|
||||
cfg := &ClientConfig{
|
||||
func getCfg() *ClientConfig {
|
||||
return &ClientConfig{
|
||||
ClientID: "uaa",
|
||||
ClientSecret: "secret",
|
||||
Endpoint: mockUAAServer.URL,
|
||||
SkipTLSVerify: true,
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordAuth(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
client, err := NewDefaultClient(cfg)
|
||||
client, err := NewDefaultClient(getCfg())
|
||||
assert.Nil(err)
|
||||
_, err = client.PasswordAuth("user1", "pass1")
|
||||
assert.Nil(err)
|
||||
@ -47,14 +50,8 @@ func TestPasswordAuth(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUserInfo(t *testing.T) {
|
||||
cfg := &ClientConfig{
|
||||
ClientID: "uaa",
|
||||
ClientSecret: "secret",
|
||||
Endpoint: mockUAAServer.URL,
|
||||
SkipTLSVerify: true,
|
||||
}
|
||||
assert := assert.New(t)
|
||||
client, err := NewDefaultClient(cfg)
|
||||
client, err := NewDefaultClient(getCfg())
|
||||
assert.Nil(err)
|
||||
token, err := ioutil.ReadFile(path.Join(currPath(), "test", "./good-access-token.txt"))
|
||||
if err != nil {
|
||||
@ -68,6 +65,21 @@ func TestUserInfo(t *testing.T) {
|
||||
assert.NotNil(err2)
|
||||
}
|
||||
|
||||
func TestSearchUser(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
client, err := NewDefaultClient(getCfg())
|
||||
assert.Nil(err)
|
||||
res1, err := client.SearchUser("one")
|
||||
assert.Nil(err)
|
||||
assert.Equal(1, len(res1))
|
||||
if len(res1) == 1 {
|
||||
assert.Equal("one", res1[0].UserName)
|
||||
}
|
||||
res2, err := client.SearchUser("none")
|
||||
assert.Nil(err)
|
||||
assert.Equal(0, len(res2))
|
||||
}
|
||||
|
||||
func currPath() string {
|
||||
_, f, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
|
@ -37,3 +37,40 @@ func (fc *FakeClient) PasswordAuth(username, password string) (*oauth2.Token, er
|
||||
func (fc *FakeClient) GetUserInfo(token string) (*UserInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// SearchUser ...
|
||||
func (fc *FakeClient) SearchUser(name string) ([]*SearchUserEntry, error) {
|
||||
res := []*SearchUserEntry{}
|
||||
entryOne := &SearchUserEntry{
|
||||
ExtID: "some-external-id-1",
|
||||
ID: "u-0001",
|
||||
UserName: "one",
|
||||
Emails: []SearchUserEmailEntry{SearchUserEmailEntry{
|
||||
Primary: false,
|
||||
Value: "one@email.com",
|
||||
}},
|
||||
}
|
||||
entryTwoA := &SearchUserEntry{
|
||||
ExtID: "some-external-id-2-a",
|
||||
ID: "u-0002a",
|
||||
UserName: "two",
|
||||
Emails: []SearchUserEmailEntry{SearchUserEmailEntry{
|
||||
Primary: false,
|
||||
Value: "two@email.com",
|
||||
}},
|
||||
}
|
||||
entryTwoB := &SearchUserEntry{
|
||||
ExtID: "some-external-id-2-b",
|
||||
ID: "u-0002b",
|
||||
UserName: "two",
|
||||
}
|
||||
if name == "one" {
|
||||
res = append(res, entryOne)
|
||||
} else if name == "two" {
|
||||
res = append(res, entryTwoA)
|
||||
res = append(res, entryTwoB)
|
||||
} else if name == "error" {
|
||||
return res, fmt.Errorf("some error")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
eyJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIyNmRjYjg1YzMzZjU0OGM5ODk2YjI4MDEwN2IyOWM0NiIsInN1YiI6IjlhMTM0ODhmLWYzY2YtNDdhNi05OGYwLTRmZWQyMWY0MzUyMCIsInNjb3BlIjpbIm9wZW5pZCJdLCJjbGllbnRfaWQiOiJrdWJlcm5ldGVzIiwiY2lkIjoia3ViZXJuZXRlcyIsImF6cCI6Imt1YmVybmV0ZXMiLCJncmFudF90eXBlIjoicGFzc3dvcmQiLCJ1c2VyX2lkIjoiOWExMzQ4OGYtZjNjZi00N2E2LTk4ZjAtNGZlZDIxZjQzNTIwIiwib3JpZ2luIjoibGRhcCIsInVzZXJfbmFtZSI6InVzZXIwMSIsImVtYWlsIjoidXNlcjAxQHVzZXIuZnJvbS5sZGFwLmNmIiwiYXV0aF90aW1lIjoxNTExNDA1NDEwLCJyZXZfc2lnIjoiOGEwYmY5OWQiLCJpYXQiOjE1MTE0MDU0MTAsImV4cCI6MTUxMTQ0ODYxMCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6ODQ0My91YWEvb2F1dGgvdG9rZW4iLCJ6aWQiOiJ1YWEiLCJhdWQiOlsia3ViZXJuZXRlcyIsIm9wZW5pZCJdfQ.I7VBx_cQoYkotRJ8KdmESAf_xjzp-R44BRz9ngHPUnoqr4rSMin-Ful8wNzEnaYaG56_mrIPuLOb6vXGWW1svRU892GOK9WQRSiFp7O81V7f1bH6JXnIGvyBNl3JOkDB9d5wXn137h9vNKq3Z9TF3jD7oXR_OENS8paclW5EAjmjGvEVIhObMmHCLhsJshTWIoP8AwoP1m9iqak_-t0c99HWaf1AgVUtT2i9Jb63ndJGA6BkOSRH_YxXmM_qtXmk_0kRA5oLDR2UGA4TVXCYp1_8iwQYjvGBVxO24I5jJh_zDYs5YLTFeNzMTPEhAl_Te6NiE91gRXq6KiVk9tTfuA
|
||||
eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIyNzlhNmI2MTRhMzM0NjVjYjYxZTM4ZmY5YTc4Y2YxZSIsInN1YiI6IjIwMTExNzE5LWNlM2EtNDRhYS05MmFjLTE3NmM0ZTM4MWY2NiIsInNjb3BlIjpbIm9wZW5pZCJdLCJjbGllbnRfaWQiOiJrdWJlcm5ldGVzIiwiY2lkIjoia3ViZXJuZXRlcyIsImF6cCI6Imt1YmVybmV0ZXMiLCJncmFudF90eXBlIjoicGFzc3dvcmQiLCJ1c2VyX2lkIjoiMjAxMTE3MTktY2UzYS00NGFhLTkyYWMtMTc2YzRlMzgxZjY2Iiwib3JpZ2luIjoibGRhcCIsInVzZXJfbmFtZSI6ImFkbWluIiwiZW1haWwiOiJhZG1pbkB1c2VyLmZyb20ubGRhcC5jZiIsImF1dGhfdGltZSI6MTUwNjg1MDQyNiwicmV2X3NpZyI6IjZkOWNlN2UwIiwiaWF0IjoxNTA2ODUwNDI2LCJleHAiOjE1MDY4OTM2MjYsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0Ojg0NDMvdWFhL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImt1YmVybmV0ZXMiLCJvcGVuaWQiXX0.Ni2yJ7Gp6OnEdhcWyfGeCm1yG_rqQgf9BA0raJ37hdH-_ZRQ4HIELkWLv3gGPuWPV4HX6EKKerjJWXCPKihyiIIVT-W7VkwFMdDv9e4aA_h2eXjxHeUdjl0Cgw7gSAPSmm_QtkeLPuj15Ngd31yiuBoxy49_sjCyn3hjd8LP2ENEVtpk2vcCiQigW-YWbDaG64im1IP6jjRruwRdPF0Idjf4vuimFG-tiRdauDvnZc90W5fIJ3AUFW_ryGnSvc7E0rBZFYOgD5BB_3HLmWzB64-D3AVe9h5wQXOBorEaXLlSXfm16RQHFI_duSh3YOZUjHLuUYIRCuKaK5RPi0Fztg
|
||||
|
1
src/common/utils/uaa/test/no-user.json
Normal file
1
src/common/utils/uaa/test/no-user.json
Normal file
@ -0,0 +1 @@
|
||||
{"resources":[],"startIndex":1,"itemsPerPage":100,"totalResults":0,"schemas":["urn:scim:schemas:core:1.0"]}
|
1
src/common/utils/uaa/test/one-user.json
Normal file
1
src/common/utils/uaa/test/one-user.json
Normal file
@ -0,0 +1 @@
|
||||
{"resources":[{"id":"6af888a1-92fa-4a30-82dd-4db28f2e15f0","externalId":"cn=one,dc=vmware,dc=com","meta":{"version":0,"created":"2017-12-20T22:54:34.493Z","lastModified":"2017-12-20T22:54:34.493Z"},"userName":"one","name":{},"emails":[{"value":"one@example.com","primary":false}],"groups":[{"value":"546a79d3-609b-49df-8111-56eee574fc99","display":"roles","type":"DIRECT"},{"value":"7e5eb7dc-8067-424a-a593-8620e9ef4962","display":"approvals.me","type":"DIRECT"},{"value":"9a946687-7be7-4a79-9742-462ae52e4833","display":"password.write","type":"DIRECT"},{"value":"f263a309-f855-405b-bcc4-e1c7453420c3","display":"uaa.offline_token","type":"DIRECT"},{"value":"80898c93-64a8-46cc-ba15-37fec9e2e56d","display":"uaa.user","type":"DIRECT"},{"value":"f8605b49-0dbc-47cf-a993-9691b7e313ab","display":"scim.userids","type":"DIRECT"},{"value":"83237f80-e709-40b9-8599-ab08b0f141a9","display":"oauth.approvals","type":"DIRECT"},{"value":"f685504d-c760-41cf-9c26-da8edddf643e","display":"user_attributes","type":"DIRECT"},{"value":"4243ba6a-001f-4052-8059-ada841a14e62","display":"cloud_controller.write","type":"DIRECT"},{"value":"36a94fb1-3bd2-4db6-8246-8a00256b080f","display":"profile","type":"DIRECT"},{"value":"0ead714c-02f1-403b-bd24-950089772f47","display":"scim.me","type":"DIRECT"},{"value":"a0944e04-1007-43ba-9745-e1ed62de21f5","display":"cloud_controller.read","type":"DIRECT"},{"value":"e9f2b839-9e2d-45b4-9179-5fce07cd013b","display":"cloud_controller_service_permissions.read","type":"DIRECT"},{"value":"2bb835d6-c62d-477b-a1df-780bb3ec560b","display":"openid","type":"DIRECT"}],"approvals":[],"active":true,"verified":true,"origin":"ldap","zoneId":"uaa","passwordLastModified":"2017-12-20T22:54:34.000Z","lastLogonTime":1513839274546,"schemas":["urn:scim:schemas:core:1.0"]}],"startIndex":1,"itemsPerPage":100,"totalResults":1,"schemas":["urn:scim:schemas:core:1.0"]}
|
@ -53,23 +53,36 @@ func (t *tokenHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
http.Error(rw, "invalid client id/secret in header", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if gt := req.FormValue("grant_type"); gt != "password" {
|
||||
gt := req.FormValue("grant_type")
|
||||
if gt == "password" {
|
||||
reqUsername := req.FormValue("username")
|
||||
reqPasswd := req.FormValue("password")
|
||||
if reqUsername == t.username && reqPasswd == t.password {
|
||||
serveToken(rw)
|
||||
} else {
|
||||
http.Error(rw, fmt.Sprintf("invalid username/password %s/%s", reqUsername, reqPasswd), http.StatusUnauthorized)
|
||||
}
|
||||
} else if gt == "client_credentials" {
|
||||
serveToken(rw)
|
||||
} else {
|
||||
http.Error(rw, fmt.Sprintf("invalid grant_type: %s", gt), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
reqUsername := req.FormValue("username")
|
||||
reqPasswd := req.FormValue("password")
|
||||
if reqUsername == t.username && reqPasswd == t.password {
|
||||
token, err := ioutil.ReadFile(path.Join(currPath(), "./uaa-token.json"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err2 := rw.Write(token)
|
||||
if err2 != nil {
|
||||
panic(err2)
|
||||
}
|
||||
} else {
|
||||
http.Error(rw, fmt.Sprintf("invalid username/password %s/%s", reqUsername, reqPasswd), http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
func serveToken(rw http.ResponseWriter) {
|
||||
serveJSONFile(rw, "uaa-token.json")
|
||||
}
|
||||
|
||||
func serveJSONFile(rw http.ResponseWriter, filename string) {
|
||||
data, err := ioutil.ReadFile(path.Join(currPath(), filename))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rw.Header().Add("Content-Type", "application/json")
|
||||
_, err2 := rw.Write(data)
|
||||
if err2 != nil {
|
||||
panic(err2)
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,27 +91,52 @@ type userInfoHandler struct {
|
||||
}
|
||||
|
||||
func (u *userInfoHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
v := req.Header.Get("Authorization")
|
||||
v := req.Header.Get("authorization")
|
||||
prefix := v[0:7]
|
||||
reqToken := v[7:]
|
||||
if strings.ToLower(prefix) != "bearer " || reqToken != u.token {
|
||||
http.Error(rw, "invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
userInfo, err := ioutil.ReadFile(path.Join(currPath(), "./user-info.json"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
serveJSONFile(rw, "./user-info.json")
|
||||
}
|
||||
|
||||
type searchUserHandler struct {
|
||||
token string
|
||||
}
|
||||
|
||||
func (su *searchUserHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
v := req.Header.Get("authorization")
|
||||
if v == "" {
|
||||
v = req.Header.Get("Authorization")
|
||||
}
|
||||
_, err2 := rw.Write(userInfo)
|
||||
if err2 != nil {
|
||||
panic(err2)
|
||||
prefix := v[0:7]
|
||||
reqToken := v[7:]
|
||||
if strings.ToLower(prefix) != "bearer " || reqToken != su.token {
|
||||
http.Error(rw, "invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
f := req.URL.Query().Get("filter")
|
||||
elements := strings.Split(f, " ")
|
||||
if len(elements) == 3 {
|
||||
if elements[0] == "Username" && elements[1] == "eq" {
|
||||
if elements[2] == "'one'" {
|
||||
serveJSONFile(rw, "one-user.json")
|
||||
return
|
||||
}
|
||||
serveJSONFile(rw, "no-user.json")
|
||||
return
|
||||
}
|
||||
http.Error(rw, "invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
http.Error(rw, fmt.Sprintf("Invalid request, elements: %v", elements), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
// NewMockServer ...
|
||||
func NewMockServer(cfg *MockServerConfig) *httptest.Server {
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/uaa/oauth/token", &tokenHandler{
|
||||
mux.Handle("/oauth/token", &tokenHandler{
|
||||
cfg.ClientID,
|
||||
cfg.ClientSecret,
|
||||
cfg.Username,
|
||||
@ -108,6 +146,7 @@ func NewMockServer(cfg *MockServerConfig) *httptest.Server {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
mux.Handle("/uaa/userinfo", &userInfoHandler{strings.TrimSpace(string(token))})
|
||||
mux.Handle("/userinfo", &userInfoHandler{strings.TrimSpace(string(token))})
|
||||
mux.Handle("/Users", &searchUserHandler{strings.TrimSpace(string(token))})
|
||||
return httptest.NewTLSServer(mux)
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
{"access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIyNzlhNmI2MTRhMzM0NjVjYjYxZTM4ZmY5YTc4Y2YxZSIsInN1YiI6IjIwMTExNzE5LWNlM2EtNDRhYS05MmFjLTE3NmM0ZTM4MWY2NiIsInNjb3BlIjpbIm9wZW5pZCJdLCJjbGllbnRfaWQiOiJrdWJlcm5ldGVzIiwiY2lkIjoia3ViZXJuZXRlcyIsImF6cCI6Imt1YmVybmV0ZXMiLCJncmFudF90eXBlIjoicGFzc3dvcmQiLCJ1c2VyX2lkIjoiMjAxMTE3MTktY2UzYS00NGFhLTkyYWMtMTc2YzRlMzgxZjY2Iiwib3JpZ2luIjoibGRhcCIsInVzZXJfbmFtZSI6ImFkbWluIiwiZW1haWwiOiJhZG1pbkB1c2VyLmZyb20ubGRhcC5jZiIsImF1dGhfdGltZSI6MTUwNjg1MDQyNiwicmV2X3NpZyI6IjZkOWNlN2UwIiwiaWF0IjoxNTA2ODUwNDI2LCJleHAiOjE1MDY4OTM2MjYsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0Ojg0NDMvdWFhL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImt1YmVybmV0ZXMiLCJvcGVuaWQiXX0.Ni2yJ7Gp6OnEdhcWyfGeCm1yG_rqQgf9BA0raJ37hdH-_ZRQ4HIELkWLv3gGPuWPV4HX6EKKerjJWXCPKihyiIIVT-W7VkwFMdDv9e4aA_h2eXjxHeUdjl0Cgw7gSAPSmm_QtkeLPuj15Ngd31yiuBoxy49_sjCyn3hjd8LP2ENEVtpk2vcCiQigW-YWbDaG64im1IP6jjRruwRdPF0Idjf4vuimFG-tiRdauDvnZc90W5fIJ3AUFW_ryGnSvc7E0rBZFYOgD5BB_3HLmWzB64-D3AVe9h5wQXOBorEaXLlSXfm16RQHFI_duSh3YOZUjHLuUYIRCuKaK5RPi0Fztg","token_type":"bearer","refresh_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiJkZTU0ZjJkMDlkODc0ZTliOTExZDk4YWQ1MTQzMjljZC1yIiwic3ViIjoiMjAxMTE3MTktY2UzYS00NGFhLTkyYWMtMTc2YzRlMzgxZjY2Iiwic2NvcGUiOlsib3BlbmlkIl0sImlhdCI6MTUwNjg1MDQyNiwiZXhwIjoxNTA5NDQyNDI2LCJjaWQiOiJrdWJlcm5ldGVzIiwiY2xpZW50X2lkIjoia3ViZXJuZXRlcyIsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0Ojg0NDMvdWFhL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwidXNlcl9uYW1lIjoiYWRtaW4iLCJvcmlnaW4iOiJsZGFwIiwidXNlcl9pZCI6IjIwMTExNzE5LWNlM2EtNDRhYS05MmFjLTE3NmM0ZTM4MWY2NiIsInJldl9zaWciOiI2ZDljZTdlMCIsImF1ZCI6WyJrdWJlcm5ldGVzIiwib3BlbmlkIl19.oW4xK3QBjMtjUH_AWWyO6A0QwbIbTwrEFnc-hulj3QbLoULvC2V3L53rcKhT1gOtj8aaQTZFdBEQNGjBpzjFU8bpwxb0szyPMkc5PjXjcJGltL3MvmBf3P0TuUxJU9vP3FjrvwwueNAafLAyRIHy8yA3ZngzkL8KCI0ps51gCRU2oOe9hGDv2ZrsZ21u760hFGiRq5-7HWJu3VMqhMVRkUyPD_3j9AGZr6gf3o_7S9oJYwEDxPZaBhhVZI6QHeQNa07w7jCqTX97_fcpeTMbrBJiz_5yD9-kJZneI4xzAMIyNwAcbSJYrL7WZ2H01heGwWFEkrrv68YUJ762jB4WAw","expires_in":43199,"scope":"openid","jti":"279a6b614a33465cb61e38ff9a78cf1e"}
|
||||
{"access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIyNzlhNmI2MTRhMzM0NjVjYjYxZTM4ZmY5YTc4Y2YxZSIsInN1YiI6IjIwMTExNzE5LWNlM2EtNDRhYS05MmFjLTE3NmM0ZTM4MWY2NiIsInNjb3BlIjpbIm9wZW5pZCJdLCJjbGllbnRfaWQiOiJrdWJlcm5ldGVzIiwiY2lkIjoia3ViZXJuZXRlcyIsImF6cCI6Imt1YmVybmV0ZXMiLCJncmFudF90eXBlIjoicGFzc3dvcmQiLCJ1c2VyX2lkIjoiMjAxMTE3MTktY2UzYS00NGFhLTkyYWMtMTc2YzRlMzgxZjY2Iiwib3JpZ2luIjoibGRhcCIsInVzZXJfbmFtZSI6ImFkbWluIiwiZW1haWwiOiJhZG1pbkB1c2VyLmZyb20ubGRhcC5jZiIsImF1dGhfdGltZSI6MTUwNjg1MDQyNiwicmV2X3NpZyI6IjZkOWNlN2UwIiwiaWF0IjoxNTA2ODUwNDI2LCJleHAiOjE1MDY4OTM2MjYsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0Ojg0NDMvdWFhL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImt1YmVybmV0ZXMiLCJvcGVuaWQiXX0.Ni2yJ7Gp6OnEdhcWyfGeCm1yG_rqQgf9BA0raJ37hdH-_ZRQ4HIELkWLv3gGPuWPV4HX6EKKerjJWXCPKihyiIIVT-W7VkwFMdDv9e4aA_h2eXjxHeUdjl0Cgw7gSAPSmm_QtkeLPuj15Ngd31yiuBoxy49_sjCyn3hjd8LP2ENEVtpk2vcCiQigW-YWbDaG64im1IP6jjRruwRdPF0Idjf4vuimFG-tiRdauDvnZc90W5fIJ3AUFW_ryGnSvc7E0rBZFYOgD5BB_3HLmWzB64-D3AVe9h5wQXOBorEaXLlSXfm16RQHFI_duSh3YOZUjHLuUYIRCuKaK5RPi0Fztg","token_type":"bearer","refresh_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiJkZTU0ZjJkMDlkODc0ZTliOTExZDk4YWQ1MTQzMjljZC1yIiwic3ViIjoiMjAxMTE3MTktY2UzYS00NGFhLTkyYWMtMTc2YzRlMzgxZjY2Iiwic2NvcGUiOlsib3BlbmlkIl0sImlhdCI6MTUwNjg1MDQyNiwiZXhwIjoxNTA5NDQyNDI2LCJjaWQiOiJrdWJlcm5ldGVzIiwiY2xpZW50X2lkIjoia3ViZXJuZXRlcyIsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0Ojg0NDMvdWFhL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwidXNlcl9uYW1lIjoiYWRtaW4iLCJvcmlnaW4iOiJsZGFwIiwidXNlcl9pZCI6IjIwMTExNzE5LWNlM2EtNDRhYS05MmFjLTE3NmM0ZTM4MWY2NiIsInJldl9zaWciOiI2ZDljZTdlMCIsImF1ZCI6WyJrdWJlcm5ldGVzIiwib3BlbmlkIl19.oW4xK3QBjMtjUH_AWWyO6A0QwbIbTwrEFnc-hulj3QbLoULvC2V3L53rcKhT1gOtj8aaQTZFdBEQNGjBpzjFU8bpwxb0szyPMkc5PjXjcJGltL3MvmBf3P0TuUxJU9vP3FjrvwwueNAafLAyRIHy8yA3ZngzkL8KCI0ps51gCRU2oOe9hGDv2ZrsZ21u760hFGiRq5-7HWJu3VMqhMVRkUyPD_3j9AGZr6gf3o_7S9oJYwEDxPZaBhhVZI6QHeQNa07w7jCqTX97_fcpeTMbrBJiz_5yD9-kJZneI4xzAMIyNwAcbSJYrL7WZ2H01heGwWFEkrrv68YUJ762jB4WAw","expires_in":43199,"jti":"279a6b614a33465cb61e38ff9a78cf1e", "scope":"clients.read password.write clients.secret uaa.resource openid clients.write uaa.admin scim.write scim.read client_id"}
|
||||
|
@ -46,10 +46,11 @@ var registry = make(map[string]AuthenticateHelper)
|
||||
// Register add different authenticators to registry map.
|
||||
func Register(name string, h AuthenticateHelper) {
|
||||
if _, dup := registry[name]; dup {
|
||||
log.Infof("authenticator: %s has been registered", name)
|
||||
log.Infof("authenticator: %s has been registered,skip", name)
|
||||
return
|
||||
}
|
||||
registry[name] = h
|
||||
log.Debugf("Registered authencation helper for auth mode: %s", name)
|
||||
}
|
||||
|
||||
// Login authenticates user credentials based on setting.
|
||||
|
@ -15,24 +15,20 @@
|
||||
package uaa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/uaa"
|
||||
"github.com/vmware/harbor/src/ui/auth"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
)
|
||||
|
||||
var lock = &sync.Mutex{}
|
||||
var client uaa.Client
|
||||
|
||||
//GetClient returns the client instance, if the client is not created it creates one.
|
||||
func GetClient() (uaa.Client, error) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
if client != nil {
|
||||
return client, nil
|
||||
}
|
||||
//CreateClient create a UAA Client instance based on system configuration.
|
||||
func CreateClient() (uaa.Client, error) {
|
||||
UAASettings, err := config.UAASettings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -43,51 +39,94 @@ func GetClient() (uaa.Client, error) {
|
||||
Endpoint: UAASettings.Endpoint,
|
||||
SkipTLSVerify: !UAASettings.VerifyCert,
|
||||
}
|
||||
client, err = uaa.NewDefaultClient(cfg)
|
||||
return client, err
|
||||
return uaa.NewDefaultClient(cfg)
|
||||
}
|
||||
|
||||
func doAuth(username, password string, client uaa.Client) (*models.User, error) {
|
||||
t, err := client.PasswordAuth(username, password)
|
||||
// Auth is the implementation of AuthenticateHelper to access uaa for authentication.
|
||||
type Auth struct {
|
||||
sync.Mutex
|
||||
client uaa.Client
|
||||
}
|
||||
|
||||
//Authenticate ...
|
||||
func (u *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
||||
if err := u.ensureClient(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, err := u.client.PasswordAuth(m.Principal, m.Password)
|
||||
if t != nil && err == nil {
|
||||
//TODO: See if it's possible to get more information from token.
|
||||
u := &models.User{
|
||||
Username: username,
|
||||
Password: "1234567ab",
|
||||
Email: username + "@placeholder.com",
|
||||
Realname: username,
|
||||
}
|
||||
err = dao.OnBoardUser(u)
|
||||
if err == nil {
|
||||
return u, nil
|
||||
user := &models.User{
|
||||
Username: m.Principal,
|
||||
}
|
||||
err = u.OnBoardUser(user)
|
||||
return user, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Auth is the implementation of AuthenticateHelper to access uaa for authentication.
|
||||
type Auth struct{}
|
||||
// OnBoardUser will check if a user exists in user table, if not insert the user and
|
||||
// put the id in the pointer of user model, if it does exist, return the user's profile.
|
||||
func (u *Auth) OnBoardUser(user *models.User) error {
|
||||
user.Username = strings.TrimSpace(user.Username)
|
||||
if len(user.Username) == 0 {
|
||||
return fmt.Errorf("The Username is empty")
|
||||
}
|
||||
if len(user.Password) == 0 {
|
||||
user.Password = "1234567ab"
|
||||
}
|
||||
if len(user.Realname) == 0 {
|
||||
user.Realname = user.Username
|
||||
}
|
||||
if len(user.Email) == 0 {
|
||||
//TODO: handle the case when user.Username itself is an email address.
|
||||
user.Email = user.Username + "@uaa.placeholder"
|
||||
}
|
||||
user.Comment = "From UAA"
|
||||
return dao.OnBoardUser(user)
|
||||
}
|
||||
|
||||
//Authenticate ...
|
||||
func (u *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
||||
client, err := GetClient()
|
||||
// SearchUser search user on uaa server, transform it to Harbor's user model
|
||||
func (u *Auth) SearchUser(username string) (*models.User, error) {
|
||||
if err := u.ensureClient(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l, err := u.client.SearchUser(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return doAuth(m.Principal, m.Password, client)
|
||||
if len(l) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if len(l) > 1 {
|
||||
return nil, fmt.Errorf("Multiple entries found for username: %s", username)
|
||||
}
|
||||
e := l[0]
|
||||
email := ""
|
||||
if len(e.Emails) > 0 {
|
||||
email = e.Emails[0].Value
|
||||
}
|
||||
return &models.User{
|
||||
Username: username,
|
||||
Email: email,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// OnBoardUser will check if a user exists in user table, if not insert the user and
|
||||
// put the id in the pointer of user model, if it does exist, return the user's profile.
|
||||
// func (u *Auth) OnBoardUser(user *models.User) error {
|
||||
// panic("not implemented")
|
||||
// }
|
||||
|
||||
// // SearchUser - search user on uaa server
|
||||
// func (u *Auth) SearchUser(username string) (*models.User, error) {
|
||||
// panic("not implemented")
|
||||
// }
|
||||
|
||||
// func init() {
|
||||
// auth.Register(auth.UAAAuth, &Auth{})
|
||||
// }
|
||||
func (u *Auth) ensureClient() error {
|
||||
if u.client != nil {
|
||||
return nil
|
||||
}
|
||||
u.Lock()
|
||||
defer u.Unlock()
|
||||
if u.client == nil {
|
||||
c, err := CreateClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.client = c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func init() {
|
||||
auth.Register(common.UAAAuth, &Auth{})
|
||||
}
|
||||
|
@ -17,45 +17,159 @@ package uaa
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
utilstest "github.com/vmware/harbor/src/common/utils/test"
|
||||
"github.com/vmware/harbor/src/common/utils/uaa"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetClient(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
func TestMain(m *testing.M) {
|
||||
dbHost := os.Getenv("MYSQL_HOST")
|
||||
if len(dbHost) == 0 {
|
||||
log.Fatalf("environment variable MYSQL_HOST is not set")
|
||||
}
|
||||
dbUser := os.Getenv("MYSQL_USR")
|
||||
if len(dbUser) == 0 {
|
||||
log.Fatalf("environment variable MYSQL_USR is not set")
|
||||
}
|
||||
dbPortStr := os.Getenv("MYSQL_PORT")
|
||||
if len(dbPortStr) == 0 {
|
||||
log.Fatalf("environment variable MYSQL_PORT is not set")
|
||||
}
|
||||
dbPort, err := strconv.Atoi(dbPortStr)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid MYSQL_PORT: %v", err)
|
||||
}
|
||||
|
||||
dbPassword := os.Getenv("MYSQL_PWD")
|
||||
dbDatabase := os.Getenv("MYSQL_DATABASE")
|
||||
if len(dbDatabase) == 0 {
|
||||
log.Fatalf("environment variable MYSQL_DATABASE is not set")
|
||||
}
|
||||
|
||||
database := &models.Database{
|
||||
Type: "mysql",
|
||||
MySQL: &models.MySQL{
|
||||
Host: dbHost,
|
||||
Port: dbPort,
|
||||
Username: dbUser,
|
||||
Password: dbPassword,
|
||||
Database: dbDatabase,
|
||||
},
|
||||
}
|
||||
dao.InitDatabase(database)
|
||||
server, err := utilstest.NewAdminserver(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create a mock admin server: %v", err)
|
||||
panic(err)
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
if err := os.Setenv("ADMINSERVER_URL", server.URL); err != nil {
|
||||
t.Fatalf("failed to set env %s: %v", "ADMINSERVER_URL", err)
|
||||
panic(err)
|
||||
}
|
||||
err = config.Init()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to init config: %v", err)
|
||||
panic(err)
|
||||
}
|
||||
c, err := GetClient()
|
||||
|
||||
err = dao.ClearTable("project_member")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = dao.ClearTable("project_metadata")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = dao.ClearTable("access_log")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = dao.ClearTable("project")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = dao.ClearTable("user")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
rc := m.Run()
|
||||
os.Exit(rc)
|
||||
}
|
||||
|
||||
func TestCreateClient(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
c, err := CreateClient()
|
||||
assert.Nil(err)
|
||||
assert.NotNil(c)
|
||||
}
|
||||
|
||||
func TestDoAuth(t *testing.T) {
|
||||
func TestAuthenticate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
client := &uaa.FakeClient{
|
||||
Username: "user1",
|
||||
Password: "password1",
|
||||
}
|
||||
dao.PrepareTestForMySQL()
|
||||
u1, err1 := doAuth("user1", "password1", client)
|
||||
auth := Auth{client: client}
|
||||
m1 := models.AuthModel{
|
||||
Principal: "user1",
|
||||
Password: "password1",
|
||||
}
|
||||
u1, err1 := auth.Authenticate(m1)
|
||||
assert.Nil(err1)
|
||||
assert.True(u1.UserID > 0)
|
||||
u2, err2 := doAuth("wrong", "wrong", client)
|
||||
assert.NotNil(u1)
|
||||
m2 := models.AuthModel{
|
||||
Principal: "wrong",
|
||||
Password: "wrong",
|
||||
}
|
||||
u2, err2 := auth.Authenticate(m2)
|
||||
assert.NotNil(err2)
|
||||
assert.Nil(u2)
|
||||
err3 := dao.ClearTable(models.UserTable)
|
||||
assert.Nil(err3)
|
||||
}
|
||||
|
||||
func TestOnBoardUser(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
auth := Auth{}
|
||||
um1 := &models.User{
|
||||
Username: " ",
|
||||
}
|
||||
err1 := auth.OnBoardUser(um1)
|
||||
assert.NotNil(err1)
|
||||
um2 := &models.User{
|
||||
Username: "test ",
|
||||
}
|
||||
user2, _ := dao.GetUser(models.User{Username: "test"})
|
||||
assert.Nil(user2)
|
||||
err2 := auth.OnBoardUser(um2)
|
||||
assert.Nil(err2)
|
||||
user, _ := dao.GetUser(models.User{Username: "test"})
|
||||
assert.Equal("test", user.Realname)
|
||||
assert.Equal("test", user.Username)
|
||||
assert.Equal("test@uaa.placeholder", user.Email)
|
||||
}
|
||||
|
||||
func TestSearchUser(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
client := &uaa.FakeClient{
|
||||
Username: "user1",
|
||||
Password: "password1",
|
||||
}
|
||||
auth := Auth{client: client}
|
||||
_, err0 := auth.SearchUser("error")
|
||||
assert.NotNil(err0)
|
||||
u1, err1 := auth.SearchUser("one")
|
||||
assert.Nil(err1)
|
||||
assert.Equal("one@email.com", u1.Email)
|
||||
_, err2 := auth.SearchUser("two")
|
||||
assert.NotNil(err2)
|
||||
user3, err3 := auth.SearchUser("none")
|
||||
assert.Nil(user3)
|
||||
assert.Nil(err3)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user