Merge pull request #2685 from ywk253100/170630_integration

Update codes of auth context
This commit is contained in:
Wenkai Yin 2017-07-04 13:14:21 +08:00 committed by GitHub
commit 7a85eef0ef
5 changed files with 136 additions and 121 deletions

View File

@ -23,6 +23,7 @@ import (
"strings"
)
// TODO update the value of role when admiral API is ready
const (
// AuthTokenHeader is the key of auth token header
AuthTokenHeader = "x-xenon-auth-token"
@ -32,16 +33,19 @@ const (
guestRole = "GUEST"
)
var client = &http.Client{
Transport: &http.Transport{},
type project struct {
DocumentSelfLink string `json:"documentSelfLink"`
Name string `json:"name"`
Roles []string `json:"roles"`
}
// AuthContext ...
type AuthContext struct {
PrincipalID string `json:"principalId"`
Name string `json:"name"`
Roles []string `json:"projects"`
Projects map[string][]string `json:"roles"`
PrincipalID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Roles []string `json:"roles"`
Projects []*project `json:"projects"`
}
// GetUsername ...
@ -53,7 +57,6 @@ func (a *AuthContext) GetUsername() string {
func (a *AuthContext) IsSysAdmin() bool {
isSysAdmin := false
for _, role := range a.Roles {
// TODO update the value of role when admiral API is ready
if role == sysAdminRole {
isSysAdmin = true
break
@ -63,14 +66,14 @@ func (a *AuthContext) IsSysAdmin() bool {
}
// HasReadPerm ...
func (a *AuthContext) HasReadPerm(project string) bool {
_, exist := a.Projects[project]
return exist
func (a *AuthContext) HasReadPerm(projectName string) bool {
roles := a.getRoles(projectName)
return len(roles) > 0
}
// HasWritePerm ...
func (a *AuthContext) HasWritePerm(project string) bool {
roles, _ := a.Projects[project]
func (a *AuthContext) HasWritePerm(projectName string) bool {
roles := a.getRoles(projectName)
for _, role := range roles {
if role == projectAdminRole || role == developerRole {
return true
@ -80,8 +83,8 @@ func (a *AuthContext) HasWritePerm(project string) bool {
}
// HasAllPerm ...
func (a *AuthContext) HasAllPerm(project string) bool {
roles, _ := a.Projects[project]
func (a *AuthContext) HasAllPerm(projectName string) bool {
roles := a.getRoles(projectName)
for _, role := range roles {
if role == projectAdminRole {
return true
@ -90,66 +93,57 @@ func (a *AuthContext) HasAllPerm(project string) bool {
return false
}
// GetMyProjects returns all projects which the user is a member of
func (a *AuthContext) GetMyProjects() ([]string, error) {
existence := map[string]bool{}
projects := []string{}
for _, list := range a.Projects {
for _, p := range list {
if existence[p] {
continue
}
existence[p] = true
projects = append(projects, p)
func (a *AuthContext) getRoles(projectName string) []string {
for _, project := range a.Projects {
if project.Name == projectName {
return project.Roles
}
}
return projects, nil
return []string{}
}
// GetByToken ...
func GetByToken(url, token string) (*AuthContext, error) {
return get(url, token)
// GetMyProjects returns all projects which the user is a member of
func (a *AuthContext) GetMyProjects() []string {
projects := []string{}
for _, project := range a.Projects {
projects = append(projects, project.Name)
}
return projects
}
// GetAuthCtxOfUser gets the user's auth context
func GetAuthCtxOfUser(url, token string, username string) (*AuthContext, error) {
return get(url, token, username)
// GetAuthCtx returns the auth context of the current user
func GetAuthCtx(client *http.Client, url, token string) (*AuthContext, error) {
return get(client, url, token)
}
// GetAuthCtxOfUser returns the auth context of the specific user
func GetAuthCtxOfUser(client *http.Client, url, token string, username string) (*AuthContext, error) {
return get(client, url, token, username)
}
// get the user's auth context, if the username is not provided
// get the default auth context of the token
func get(url, token string, username ...string) (*AuthContext, error) {
principalID := ""
if len(username) > 0 {
principalID = username[0]
func get(client *http.Client, url, token string, username ...string) (*AuthContext, error) {
endpoint := ""
if len(username) > 0 && len(username[0]) > 0 {
endpoint = buildSpecificUserAuthCtxURL(url, username[0])
} else {
endpoint = buildCurrentUserAuthCtxURL(url)
}
req, err := http.NewRequest(http.MethodGet, buildCtxURL(url, principalID), nil)
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
if err != nil {
return nil, err
}
req.Header.Add(AuthTokenHeader, token)
code, _, data, err := send(req)
if code != http.StatusOK {
return nil, fmt.Errorf("failed to get auth context by token: %d %s",
code, string(data))
}
ctx := &AuthContext{
Projects: make(map[string][]string),
}
if err = json.Unmarshal(data, ctx); err != nil {
return nil, err
}
return ctx, nil
return send(client, req)
}
// Login with credential and returns token, auth context and error
func Login(url, username, password string) (string, *AuthContext, error) {
// Login with credential and returns auth context and error
func Login(client *http.Client, url, username, password string) (*AuthContext, error) {
data, err := json.Marshal(&struct {
Username string `json:"username"`
Password string `json:"password"`
@ -158,52 +152,51 @@ func Login(url, username, password string) (string, *AuthContext, error) {
Password: password,
})
if err != nil {
return "", nil, err
return nil, err
}
req, err := http.NewRequest(http.MethodPost, buildLoginURL(url), bytes.NewBuffer(data))
if err != nil {
return "", nil, err
return nil, err
}
code, header, data, err := send(req)
if code != http.StatusOK {
return "", nil, fmt.Errorf("failed to login with user %s: %d %s", username,
code, string(data))
}
ctx := &AuthContext{
Projects: make(map[string][]string),
}
if err = json.Unmarshal(data, ctx); err != nil {
return "", nil, err
}
return header.Get(AuthTokenHeader), ctx, nil
return send(client, req)
}
func send(req *http.Request) (int, http.Header, []byte, error) {
func send(client *http.Client, req *http.Request) (*AuthContext, error) {
resp, err := client.Do(req)
if err != nil {
return 0, nil, nil, err
return nil, err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return 0, nil, nil, err
return nil, err
}
return resp.StatusCode, resp.Header, data, nil
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d %s", resp.StatusCode, string(data))
}
ctx := &AuthContext{}
if err = json.Unmarshal(data, ctx); err != nil {
return nil, err
}
return ctx, nil
}
func buildCtxURL(url, principalID string) string {
url = strings.TrimRight(url, "/") + "/sso/auth-context"
if len(principalID) > 0 {
url += "/" + principalID
}
return url
func buildCurrentUserAuthCtxURL(url string) string {
return strings.TrimRight(url, "/") + "/auth/session"
}
func buildSpecificUserAuthCtxURL(url, principalID string) string {
return fmt.Sprintf("%s/auth/idm/principals/%s/security-context",
strings.TrimRight(url, "/"), principalID)
}
// TODO update the url
func buildLoginURL(url string) string {
return strings.TrimRight(url, "/") + "/sso/login"
}

View File

@ -79,12 +79,18 @@ func (p *ProjectAPI) Post() {
p.HandleUnauthorized()
return
}
onlyAdmin, err := config.OnlyAdminCreateProject()
if err != nil {
log.Errorf("failed to determine whether only admin can create projects: %v", err)
p.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
var onlyAdmin bool
var err error
if config.WithAdmiral() {
onlyAdmin = true
} else {
onlyAdmin, err = config.OnlyAdminCreateProject()
if err != nil {
log.Errorf("failed to determine whether only admin can create projects: %v", err)
p.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
}
if onlyAdmin && !p.SecurityCtx.IsSysAdmin() {
log.Errorf("Only sys admin can create project")
p.RenderError(http.StatusForbidden, "Only system admin can create project")

View File

@ -113,14 +113,14 @@ func initProjectManager() {
// integration with admiral
log.Info("initializing the project manager based on PMS...")
// TODO read ca/cert file and pass it to the TLS config
AdminserverClient := &http.Client{
AdmiralClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
GlobalProjectMgr = pms.NewProjectManager(AdminserverClient,
GlobalProjectMgr = pms.NewProjectManager(AdmiralClient,
AdmiralEndpoint(), &pms.FileTokenReader{
Path: defaultTokenFilePath,
})

View File

@ -126,46 +126,65 @@ func (b *basicAuthReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
}
log.Debug("got user information via basic auth")
var securCtx security.Context
var pm projectmanager.ProjectManager
// integration with admiral
if config.WithAdmiral() {
// integration with admiral
_, authCtx, err := authcontext.Login(config.AdmiralEndpoint(), username, password)
// Can't get a token from Admiral's login API, we can only
// create a project manager with the token of the solution user.
// That way may cause some wrong permission promotion in some API
// calls, so we just handle the requests which are necessary
if !filterReq(ctx.Request) {
log.Debugf("basic auth is not supported for request %s %s, skip",
ctx.Request.Method, ctx.Request.URL.Path)
return false
}
authCtx, err := authcontext.Login(config.AdmiralClient,
config.AdmiralEndpoint(), username, password)
if err != nil {
log.Errorf("failed to authenticate %s: %v", username, err)
return false
}
log.Debug("using glocal project manager...")
pm = config.GlobalProjectMgr
pm := config.GlobalProjectMgr
log.Debug("creating admiral security context...")
securCtx = admiral.NewSecurityContext(authCtx, pm)
} else {
// standalone
user, err := auth.Login(models.AuthModel{
Principal: username,
Password: password,
})
if err != nil {
log.Errorf("failed to authenticate %s: %v", username, err)
return false
}
if user == nil {
log.Debug("basic auth user is nil")
return false
}
log.Debug("using local database project manager")
pm = config.GlobalProjectMgr
log.Debug("creating local database security context...")
securCtx = local.NewSecurityContext(user, pm)
securCtx := admiral.NewSecurityContext(authCtx, pm)
setSecurCtxAndPM(ctx.Request, securCtx, pm)
return true
}
setSecurCtxAndPM(ctx.Request, securCtx, pm)
// standalone
user, err := auth.Login(models.AuthModel{
Principal: username,
Password: password,
})
if err != nil {
log.Errorf("failed to authenticate %s: %v", username, err)
return false
}
if user == nil {
log.Debug("basic auth user is nil")
return false
}
log.Debug("using local database project manager")
pm := config.GlobalProjectMgr
log.Debug("creating local database security context...")
securCtx := local.NewSecurityContext(user, pm)
setSecurCtxAndPM(ctx.Request, securCtx, pm)
return true
}
func filterReq(req *http.Request) bool {
path := req.URL.Path
if path == "/api/projects" && req.Method == http.MethodPost ||
path == "/service/token" && req.Method == http.MethodGet {
return true
}
return false
}
type sessionReqCtxModifier struct{}
func (s *sessionReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
@ -203,7 +222,8 @@ func (t *tokenReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
log.Debug("got token from request")
authContext, err := authcontext.GetByToken(config.AdmiralEndpoint(), token)
authContext, err := authcontext.GetAuthCtx(config.AdmiralClient,
config.AdmiralEndpoint(), token)
if err != nil {
log.Errorf("failed to get auth context: %v", err)
return false

View File

@ -304,16 +304,12 @@ func (p *ProjectManager) GetPublic() ([]*models.Project, error) {
// GetByMember ...
func (p *ProjectManager) GetByMember(username string) ([]*models.Project, error) {
projects := []*models.Project{}
ctx, err := authcontext.GetAuthCtxOfUser(p.endpoint, p.getToken(), username)
if err != nil {
return projects, err
}
names, err := ctx.GetMyProjects()
ctx, err := authcontext.GetAuthCtxOfUser(p.client, p.endpoint, p.getToken(), username)
if err != nil {
return projects, err
}
names := ctx.GetMyProjects()
for _, name := range names {
project, err := p.Get(name)
if err != nil {