Support getting user info via token in UAA Client (#3686)

This commit is contained in:
Daniel Jiang 2017-11-27 18:13:36 +08:00 committed by GitHub
parent 30e536b18b
commit d13321f2b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 112 additions and 3 deletions

View File

@ -18,6 +18,7 @@ import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"io/ioutil"
"net/http"
"strings"
@ -30,6 +31,8 @@ import (
type Client interface {
//PasswordAuth accepts username and password, return a token if it's valid.
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)
}
// ClientConfig values to initialize UAA Client
@ -42,10 +45,22 @@ type ClientConfig struct {
CARootPath string
}
// UserInfo represent the JSON object of a userinfo response from UAA.
// As the response varies, this struct will contain only a subset of attributes
// that may be used in Harbor
type UserInfo struct {
UserID string `json:"user_id"`
Sub string `json:"sub"`
UserName string `json:"user_name"`
Name string `json:"name"`
Email string `json:"email"`
}
// DefaultClient leverages oauth2 pacakge for oauth features
type defaultClient struct {
httpClient *http.Client
oauth2Cfg *oauth2.Config
endpoint string
//TODO: add public key, etc...
}
@ -54,6 +69,29 @@ func (dc *defaultClient) PasswordAuth(username, password string) (*oauth2.Token,
return dc.oauth2Cfg.PasswordCredentialsToken(ctx, username, password)
}
func (dc *defaultClient) GetUserInfo(token string) (*UserInfo, error) {
userInfoURL := dc.endpoint + "/uaa/userinfo"
req, err := http.NewRequest(http.MethodGet, userInfoURL, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "bearer "+token)
resp, err := dc.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
info := &UserInfo{}
if err := json.Unmarshal(data, info); err != nil {
return nil, err
}
return info, nil
}
// NewDefaultClient creates an instance of defaultClient.
func NewDefaultClient(cfg *ClientConfig) (Client, error) {
url := cfg.Endpoint
@ -95,5 +133,6 @@ func NewDefaultClient(cfg *ClientConfig) (Client, error) {
return &defaultClient{
httpClient: hc,
oauth2Cfg: oc,
endpoint: url,
}, nil
}

View File

@ -1,12 +1,15 @@
package uaa
import (
"fmt"
"github.com/stretchr/testify/assert"
"github.com/vmware/harbor/src/common/utils/uaa/test"
"io/ioutil"
"net/http/httptest"
"os"
"path"
"runtime"
"strings"
"testing"
)
@ -43,6 +46,28 @@ func TestPasswordAuth(t *testing.T) {
assert.NotNil(err)
}
func TestUserInfo(t *testing.T) {
cfg := &ClientConfig{
ClientID: "uaa",
ClientSecret: "secret",
Endpoint: mockUAAServer.URL,
SkipTLSVerify: true,
}
assert := assert.New(t)
client, err := NewDefaultClient(cfg)
assert.Nil(err)
token, err := ioutil.ReadFile(path.Join(currPath(), "test", "./good-access-token.txt"))
if err != nil {
panic(err)
}
userInfo, err := client.GetUserInfo(strings.TrimSpace(string(token)))
assert.Nil(err, fmt.Sprintf("%v", err))
assert.Equal("user01", userInfo.UserName)
_, err2 := client.GetUserInfo("bad")
assert.NotNil(err2)
}
func currPath() string {
_, f, _, ok := runtime.Caller(0)
if !ok {

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package test
package uaa
import (
"fmt"
@ -32,3 +32,8 @@ func (fc *FakeClient) PasswordAuth(username, password string) (*oauth2.Token, er
}
return nil, fmt.Errorf("Invalide username and password")
}
// GetUserInfo ...
func (fc *FakeClient) GetUserInfo(token string) (*UserInfo, error) {
return nil, nil
}

View File

@ -0,0 +1 @@
eyJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIyNmRjYjg1YzMzZjU0OGM5ODk2YjI4MDEwN2IyOWM0NiIsInN1YiI6IjlhMTM0ODhmLWYzY2YtNDdhNi05OGYwLTRmZWQyMWY0MzUyMCIsInNjb3BlIjpbIm9wZW5pZCJdLCJjbGllbnRfaWQiOiJrdWJlcm5ldGVzIiwiY2lkIjoia3ViZXJuZXRlcyIsImF6cCI6Imt1YmVybmV0ZXMiLCJncmFudF90eXBlIjoicGFzc3dvcmQiLCJ1c2VyX2lkIjoiOWExMzQ4OGYtZjNjZi00N2E2LTk4ZjAtNGZlZDIxZjQzNTIwIiwib3JpZ2luIjoibGRhcCIsInVzZXJfbmFtZSI6InVzZXIwMSIsImVtYWlsIjoidXNlcjAxQHVzZXIuZnJvbS5sZGFwLmNmIiwiYXV0aF90aW1lIjoxNTExNDA1NDEwLCJyZXZfc2lnIjoiOGEwYmY5OWQiLCJpYXQiOjE1MTE0MDU0MTAsImV4cCI6MTUxMTQ0ODYxMCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6ODQ0My91YWEvb2F1dGgvdG9rZW4iLCJ6aWQiOiJ1YWEiLCJhdWQiOlsia3ViZXJuZXRlcyIsIm9wZW5pZCJdfQ.I7VBx_cQoYkotRJ8KdmESAf_xjzp-R44BRz9ngHPUnoqr4rSMin-Ful8wNzEnaYaG56_mrIPuLOb6vXGWW1svRU892GOK9WQRSiFp7O81V7f1bH6JXnIGvyBNl3JOkDB9d5wXn137h9vNKq3Z9TF3jD7oXR_OENS8paclW5EAjmjGvEVIhObMmHCLhsJshTWIoP8AwoP1m9iqak_-t0c99HWaf1AgVUtT2i9Jb63ndJGA6BkOSRH_YxXmM_qtXmk_0kRA5oLDR2UGA4TVXCYp1_8iwQYjvGBVxO24I5jJh_zDYs5YLTFeNzMTPEhAl_Te6NiE91gRXq6KiVk9tTfuA

View File

@ -21,6 +21,7 @@ import (
"net/http/httptest"
"path"
"runtime"
"strings"
)
// MockServerConfig ...
@ -72,6 +73,28 @@ func (t *tokenHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
}
}
type userInfoHandler struct {
token string
}
func (u *userInfoHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
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)
}
_, err2 := rw.Write(userInfo)
if err2 != nil {
panic(err2)
}
}
// NewMockServer ...
func NewMockServer(cfg *MockServerConfig) *httptest.Server {
mux := http.NewServeMux()
@ -81,5 +104,10 @@ func NewMockServer(cfg *MockServerConfig) *httptest.Server {
cfg.Username,
cfg.Password,
})
token, err := ioutil.ReadFile(path.Join(currPath(), "./good-access-token.txt"))
if err != nil {
panic(err)
}
mux.Handle("/uaa/userinfo", &userInfoHandler{strings.TrimSpace(string(token))})
return httptest.NewTLSServer(mux)
}

View File

@ -0,0 +1,11 @@
{
"user_id": "9a13488f-f3cf-47a6-98f0-4fed21f43520",
"sub": "9a13488f-f3cf-47a6-98f0-4fed21f43520",
"user_name": "user01",
"given_name": null,
"family_name": null,
"email": "user01@user.from.ldap.cf",
"phone_number": null,
"previous_logon_time": 1511247236160,
"name": ""
}

View File

@ -18,7 +18,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/vmware/harbor/src/common/dao"
utilstest "github.com/vmware/harbor/src/common/utils/test"
uaatest "github.com/vmware/harbor/src/common/utils/uaa/test"
"github.com/vmware/harbor/src/common/utils/uaa"
"github.com/vmware/harbor/src/ui/config"
"os"
@ -47,7 +47,7 @@ func TestGetClient(t *testing.T) {
func TestDoAuth(t *testing.T) {
assert := assert.New(t)
client := &uaatest.FakeClient{
client := &uaa.FakeClient{
Username: "user1",
Password: "password1",
}