mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-20 07:37:38 +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,17 +15,18 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"fmt"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
|
||||||
"github.com/vmware/harbor/src/adminserver/systemcfg/store"
|
"github.com/vmware/harbor/src/adminserver/systemcfg/store"
|
||||||
"github.com/vmware/harbor/src/common"
|
"github.com/vmware/harbor/src/common"
|
||||||
"fmt"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
name = "database"
|
name = "database"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
numKeys = map[string]bool{
|
numKeys = map[string]bool{
|
||||||
common.EmailPort: true,
|
common.EmailPort: true,
|
||||||
@ -44,8 +45,10 @@ var(
|
|||||||
common.EmailSSL: true,
|
common.EmailSSL: true,
|
||||||
common.EmailInsecure: true,
|
common.EmailInsecure: true,
|
||||||
common.LDAPVerifyCert: true,
|
common.LDAPVerifyCert: true,
|
||||||
|
common.UAAVerifyCert: true,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type cfgStore struct {
|
type cfgStore struct {
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
@ -61,6 +64,7 @@ func NewCfgStore() (store.Driver, error){
|
|||||||
name: name,
|
name: name,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read configuration from database
|
// Read configuration from database
|
||||||
func (c *cfgStore) Read() (map[string]interface{}, error) {
|
func (c *cfgStore) Read() (map[string]interface{}, error) {
|
||||||
configEntries, error := dao.GetConfigEntries()
|
configEntries, error := dao.GetConfigEntries()
|
||||||
@ -93,6 +97,7 @@ func WrapperConfig (configEntries []*models.ConfigEntry) (map[string]interface{}
|
|||||||
}
|
}
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write save configuration to database
|
// Write save configuration to database
|
||||||
func (c *cfgStore) Write(config map[string]interface{}) error {
|
func (c *cfgStore) Write(config map[string]interface{}) error {
|
||||||
configEntries, _ := TranslateConfig(config)
|
configEntries, _ := TranslateConfig(config)
|
||||||
|
@ -171,7 +171,10 @@ var (
|
|||||||
common.UAAEndpoint: "UAA_ENDPOINT",
|
common.UAAEndpoint: "UAA_ENDPOINT",
|
||||||
common.UAAClientID: "UAA_CLIENTID",
|
common.UAAClientID: "UAA_CLIENTID",
|
||||||
common.UAAClientSecret: "UAA_CLIENTSECRET",
|
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 (
|
const (
|
||||||
DBAuth = "db_auth"
|
DBAuth = "db_auth"
|
||||||
LDAPAuth = "ldap_auth"
|
LDAPAuth = "ldap_auth"
|
||||||
|
UAAAuth = "uaa_auth"
|
||||||
ProCrtRestrEveryone = "everyone"
|
ProCrtRestrEveryone = "everyone"
|
||||||
ProCrtRestrAdmOnly = "adminonly"
|
ProCrtRestrAdmOnly = "adminonly"
|
||||||
LDAPScopeBase = 1
|
LDAPScopeBase = 1
|
||||||
|
@ -116,6 +116,12 @@ func GetOrmer() orm.Ormer {
|
|||||||
func ClearTable(table string) error {
|
func ClearTable(table string) error {
|
||||||
o := GetOrmer()
|
o := GetOrmer()
|
||||||
sql := fmt.Sprintf("delete from %s where 1=1", table)
|
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()
|
_, err := o.Raw(sql).Exec()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ProjectTable is the table name for project
|
||||||
|
const ProjectTable = "project"
|
||||||
|
|
||||||
// Project holds the details of a project.
|
// Project holds the details of a project.
|
||||||
type Project struct {
|
type Project struct {
|
||||||
ProjectID int64 `orm:"pk;auto;column(project_id)" json:"project_id"`
|
ProjectID int64 `orm:"pk;auto;column(project_id)" json:"project_id"`
|
||||||
@ -174,3 +177,8 @@ type ProjectQueryResult struct {
|
|||||||
Total int64
|
Total int64
|
||||||
Projects []*Project
|
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"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// UserTable is the name of table in DB that holds the user object
|
||||||
|
const UserTable = "user"
|
||||||
|
|
||||||
// User holds the details of a user.
|
// User holds the details of a user.
|
||||||
type User struct {
|
type User struct {
|
||||||
UserID int `orm:"pk;auto;column(user_id)" json:"user_id"`
|
UserID int `orm:"pk;auto;column(user_id)" json:"user_id"`
|
||||||
@ -45,3 +48,8 @@ type UserQuery struct {
|
|||||||
Email string
|
Email string
|
||||||
Pagination *Pagination
|
Pagination *Pagination
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TableName ...
|
||||||
|
func (u *User) TableName() string {
|
||||||
|
return UserTable
|
||||||
|
}
|
||||||
|
@ -19,12 +19,25 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"golang.org/x/oauth2"
|
"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.
|
// Client provides funcs to interact with UAA.
|
||||||
@ -33,6 +46,8 @@ type Client interface {
|
|||||||
PasswordAuth(username, password string) (*oauth2.Token, error)
|
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.
|
//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)
|
GetUserInfo(token string) (*UserInfo, error)
|
||||||
|
//SearchUser searches a user based on user name.
|
||||||
|
SearchUser(name string) ([]*SearchUserEntry, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientConfig values to initialize UAA Client
|
// ClientConfig values to initialize UAA Client
|
||||||
@ -56,21 +71,43 @@ type UserInfo struct {
|
|||||||
Email string `json:"email"`
|
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
|
// DefaultClient leverages oauth2 pacakge for oauth features
|
||||||
type defaultClient struct {
|
type defaultClient struct {
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
oauth2Cfg *oauth2.Config
|
oauth2Cfg *oauth2.Config
|
||||||
|
twoLegCfg *clientcredentials.Config
|
||||||
endpoint string
|
endpoint string
|
||||||
//TODO: add public key, etc...
|
//TODO: add public key, etc...
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *defaultClient) PasswordAuth(username, password string) (*oauth2.Token, error) {
|
func (dc *defaultClient) PasswordAuth(username, password string) (*oauth2.Token, error) {
|
||||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, dc.httpClient)
|
return dc.oauth2Cfg.PasswordCredentialsToken(dc.prepareCtx(), username, password)
|
||||||
return dc.oauth2Cfg.PasswordCredentialsToken(ctx, username, password)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *defaultClient) GetUserInfo(token string) (*UserInfo, error) {
|
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)
|
req, err := http.NewRequest(http.MethodGet, userInfoURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -92,6 +129,45 @@ func (dc *defaultClient) GetUserInfo(token string) (*UserInfo, error) {
|
|||||||
return info, nil
|
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.
|
// NewDefaultClient creates an instance of defaultClient.
|
||||||
func NewDefaultClient(cfg *ClientConfig) (Client, error) {
|
func NewDefaultClient(cfg *ClientConfig) (Client, error) {
|
||||||
url := cfg.Endpoint
|
url := cfg.Endpoint
|
||||||
@ -125,14 +201,21 @@ func NewDefaultClient(cfg *ClientConfig) (Client, error) {
|
|||||||
ClientID: cfg.ClientID,
|
ClientID: cfg.ClientID,
|
||||||
ClientSecret: cfg.ClientSecret,
|
ClientSecret: cfg.ClientSecret,
|
||||||
Endpoint: oauth2.Endpoint{
|
Endpoint: oauth2.Endpoint{
|
||||||
TokenURL: url + "/uaa/oauth/token",
|
TokenURL: url + TokenURLSuffix,
|
||||||
AuthURL: url + "/uaa/oauth/authorize",
|
AuthURL: url + AuthURLSuffix,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cc := &clientcredentials.Config{
|
||||||
|
ClientID: cfg.ClientID,
|
||||||
|
ClientSecret: cfg.ClientSecret,
|
||||||
|
TokenURL: url + TokenURLSuffix,
|
||||||
|
}
|
||||||
|
|
||||||
return &defaultClient{
|
return &defaultClient{
|
||||||
httpClient: hc,
|
httpClient: hc,
|
||||||
oauth2Cfg: oc,
|
oauth2Cfg: oc,
|
||||||
|
twoLegCfg: cc,
|
||||||
endpoint: url,
|
endpoint: url,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -30,15 +30,18 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPasswordAuth(t *testing.T) {
|
func getCfg() *ClientConfig {
|
||||||
cfg := &ClientConfig{
|
return &ClientConfig{
|
||||||
ClientID: "uaa",
|
ClientID: "uaa",
|
||||||
ClientSecret: "secret",
|
ClientSecret: "secret",
|
||||||
Endpoint: mockUAAServer.URL,
|
Endpoint: mockUAAServer.URL,
|
||||||
SkipTLSVerify: true,
|
SkipTLSVerify: true,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPasswordAuth(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
client, err := NewDefaultClient(cfg)
|
client, err := NewDefaultClient(getCfg())
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
_, err = client.PasswordAuth("user1", "pass1")
|
_, err = client.PasswordAuth("user1", "pass1")
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
@ -47,14 +50,8 @@ func TestPasswordAuth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUserInfo(t *testing.T) {
|
func TestUserInfo(t *testing.T) {
|
||||||
cfg := &ClientConfig{
|
|
||||||
ClientID: "uaa",
|
|
||||||
ClientSecret: "secret",
|
|
||||||
Endpoint: mockUAAServer.URL,
|
|
||||||
SkipTLSVerify: true,
|
|
||||||
}
|
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
client, err := NewDefaultClient(cfg)
|
client, err := NewDefaultClient(getCfg())
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
token, err := ioutil.ReadFile(path.Join(currPath(), "test", "./good-access-token.txt"))
|
token, err := ioutil.ReadFile(path.Join(currPath(), "test", "./good-access-token.txt"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -68,6 +65,21 @@ func TestUserInfo(t *testing.T) {
|
|||||||
assert.NotNil(err2)
|
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 {
|
func currPath() string {
|
||||||
_, f, _, ok := runtime.Caller(0)
|
_, f, _, ok := runtime.Caller(0)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -37,3 +37,40 @@ func (fc *FakeClient) PasswordAuth(username, password string) (*oauth2.Token, er
|
|||||||
func (fc *FakeClient) GetUserInfo(token string) (*UserInfo, error) {
|
func (fc *FakeClient) GetUserInfo(token string) (*UserInfo, error) {
|
||||||
return nil, nil
|
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,24 +53,37 @@ func (t *tokenHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
http.Error(rw, "invalid client id/secret in header", http.StatusUnauthorized)
|
http.Error(rw, "invalid client id/secret in header", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if gt := req.FormValue("grant_type"); gt != "password" {
|
gt := req.FormValue("grant_type")
|
||||||
http.Error(rw, fmt.Sprintf("invalid grant_type: %s", gt), http.StatusBadRequest)
|
if gt == "password" {
|
||||||
return
|
|
||||||
}
|
|
||||||
reqUsername := req.FormValue("username")
|
reqUsername := req.FormValue("username")
|
||||||
reqPasswd := req.FormValue("password")
|
reqPasswd := req.FormValue("password")
|
||||||
if reqUsername == t.username && reqPasswd == t.password {
|
if reqUsername == t.username && reqPasswd == t.password {
|
||||||
token, err := ioutil.ReadFile(path.Join(currPath(), "./uaa-token.json"))
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
_, err2 := rw.Write(token)
|
rw.Header().Add("Content-Type", "application/json")
|
||||||
|
_, err2 := rw.Write(data)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
panic(err2)
|
panic(err2)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
http.Error(rw, fmt.Sprintf("invalid username/password %s/%s", reqUsername, reqPasswd), http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type userInfoHandler struct {
|
type userInfoHandler struct {
|
||||||
@ -78,27 +91,52 @@ type userInfoHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *userInfoHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (u *userInfoHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
v := req.Header.Get("Authorization")
|
v := req.Header.Get("authorization")
|
||||||
prefix := v[0:7]
|
prefix := v[0:7]
|
||||||
reqToken := v[7:]
|
reqToken := v[7:]
|
||||||
if strings.ToLower(prefix) != "bearer " || reqToken != u.token {
|
if strings.ToLower(prefix) != "bearer " || reqToken != u.token {
|
||||||
http.Error(rw, "invalid token", http.StatusUnauthorized)
|
http.Error(rw, "invalid token", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userInfo, err := ioutil.ReadFile(path.Join(currPath(), "./user-info.json"))
|
serveJSONFile(rw, "./user-info.json")
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
_, err2 := rw.Write(userInfo)
|
|
||||||
if err2 != nil {
|
type searchUserHandler struct {
|
||||||
panic(err2)
|
token string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (su *searchUserHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
v := req.Header.Get("authorization")
|
||||||
|
if v == "" {
|
||||||
|
v = req.Header.Get("Authorization")
|
||||||
|
}
|
||||||
|
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 ...
|
// NewMockServer ...
|
||||||
func NewMockServer(cfg *MockServerConfig) *httptest.Server {
|
func NewMockServer(cfg *MockServerConfig) *httptest.Server {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.Handle("/uaa/oauth/token", &tokenHandler{
|
mux.Handle("/oauth/token", &tokenHandler{
|
||||||
cfg.ClientID,
|
cfg.ClientID,
|
||||||
cfg.ClientSecret,
|
cfg.ClientSecret,
|
||||||
cfg.Username,
|
cfg.Username,
|
||||||
@ -108,6 +146,7 @@ func NewMockServer(cfg *MockServerConfig) *httptest.Server {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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)
|
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.
|
// Register add different authenticators to registry map.
|
||||||
func Register(name string, h AuthenticateHelper) {
|
func Register(name string, h AuthenticateHelper) {
|
||||||
if _, dup := registry[name]; dup {
|
if _, dup := registry[name]; dup {
|
||||||
log.Infof("authenticator: %s has been registered", name)
|
log.Infof("authenticator: %s has been registered,skip", name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
registry[name] = h
|
registry[name] = h
|
||||||
|
log.Debugf("Registered authencation helper for auth mode: %s", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login authenticates user credentials based on setting.
|
// Login authenticates user credentials based on setting.
|
||||||
|
@ -15,24 +15,20 @@
|
|||||||
package uaa
|
package uaa
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils/uaa"
|
"github.com/vmware/harbor/src/common/utils/uaa"
|
||||||
|
"github.com/vmware/harbor/src/ui/auth"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var lock = &sync.Mutex{}
|
//CreateClient create a UAA Client instance based on system configuration.
|
||||||
var client uaa.Client
|
func CreateClient() (uaa.Client, error) {
|
||||||
|
|
||||||
//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
|
|
||||||
}
|
|
||||||
UAASettings, err := config.UAASettings()
|
UAASettings, err := config.UAASettings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -43,51 +39,94 @@ func GetClient() (uaa.Client, error) {
|
|||||||
Endpoint: UAASettings.Endpoint,
|
Endpoint: UAASettings.Endpoint,
|
||||||
SkipTLSVerify: !UAASettings.VerifyCert,
|
SkipTLSVerify: !UAASettings.VerifyCert,
|
||||||
}
|
}
|
||||||
client, err = uaa.NewDefaultClient(cfg)
|
return uaa.NewDefaultClient(cfg)
|
||||||
return client, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func doAuth(username, password string, client uaa.Client) (*models.User, error) {
|
|
||||||
t, err := client.PasswordAuth(username, 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth is the implementation of AuthenticateHelper to access uaa for authentication.
|
// Auth is the implementation of AuthenticateHelper to access uaa for authentication.
|
||||||
type Auth struct{}
|
type Auth struct {
|
||||||
|
sync.Mutex
|
||||||
|
client uaa.Client
|
||||||
|
}
|
||||||
|
|
||||||
//Authenticate ...
|
//Authenticate ...
|
||||||
func (u *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
func (u *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
||||||
client, err := GetClient()
|
if err := u.ensureClient(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return doAuth(m.Principal, m.Password, client)
|
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.
|
||||||
|
user := &models.User{
|
||||||
|
Username: m.Principal,
|
||||||
|
}
|
||||||
|
err = u.OnBoardUser(user)
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnBoardUser will check if a user exists in user table, if not insert the user and
|
// 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.
|
// 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 {
|
func (u *Auth) OnBoardUser(user *models.User) error {
|
||||||
// panic("not implemented")
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
// // SearchUser - search user on uaa server
|
// SearchUser search user on uaa server, transform it to Harbor's user model
|
||||||
// func (u *Auth) SearchUser(username string) (*models.User, error) {
|
func (u *Auth) SearchUser(username string) (*models.User, error) {
|
||||||
// panic("not implemented")
|
if err := u.ensureClient(); err != nil {
|
||||||
// }
|
return nil, err
|
||||||
|
}
|
||||||
|
l, err := u.client.SearchUser(username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// func init() {
|
func (u *Auth) ensureClient() error {
|
||||||
// auth.Register(auth.UAAAuth, &Auth{})
|
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 (
|
import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"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"
|
utilstest "github.com/vmware/harbor/src/common/utils/test"
|
||||||
"github.com/vmware/harbor/src/common/utils/uaa"
|
"github.com/vmware/harbor/src/common/utils/uaa"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
|
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetClient(t *testing.T) {
|
func TestMain(m *testing.M) {
|
||||||
assert := assert.New(t)
|
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)
|
server, err := utilstest.NewAdminserver(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create a mock admin server: %v", err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
if err := os.Setenv("ADMINSERVER_URL", server.URL); err != nil {
|
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()
|
err = config.Init()
|
||||||
if err != nil {
|
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.Nil(err)
|
||||||
assert.NotNil(c)
|
assert.NotNil(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDoAuth(t *testing.T) {
|
func TestAuthenticate(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
client := &uaa.FakeClient{
|
client := &uaa.FakeClient{
|
||||||
Username: "user1",
|
Username: "user1",
|
||||||
Password: "password1",
|
Password: "password1",
|
||||||
}
|
}
|
||||||
dao.PrepareTestForMySQL()
|
auth := Auth{client: client}
|
||||||
u1, err1 := doAuth("user1", "password1", client)
|
m1 := models.AuthModel{
|
||||||
|
Principal: "user1",
|
||||||
|
Password: "password1",
|
||||||
|
}
|
||||||
|
u1, err1 := auth.Authenticate(m1)
|
||||||
assert.Nil(err1)
|
assert.Nil(err1)
|
||||||
assert.True(u1.UserID > 0)
|
assert.NotNil(u1)
|
||||||
u2, err2 := doAuth("wrong", "wrong", client)
|
m2 := models.AuthModel{
|
||||||
|
Principal: "wrong",
|
||||||
|
Password: "wrong",
|
||||||
|
}
|
||||||
|
u2, err2 := auth.Authenticate(m2)
|
||||||
assert.NotNil(err2)
|
assert.NotNil(err2)
|
||||||
assert.Nil(u2)
|
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