mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-03 15:43:39 +01:00
Merge pull request #1462 from reasonerjt/token-service-refactory
Token service refactory
This commit is contained in:
commit
1405661f5f
@ -20,8 +20,8 @@
|
||||
"type": "token",
|
||||
"options": {
|
||||
"realm": "$token_endpoint/service/token",
|
||||
"service": "token-service",
|
||||
"issuer": "registry-token-issuer",
|
||||
"service": "harbor-registry",
|
||||
"issuer": "harbor-token-issuer",
|
||||
"rootcertbundle": "/config/root.crt"
|
||||
}
|
||||
}
|
||||
|
@ -20,10 +20,10 @@ http:
|
||||
addr: localhost:5001
|
||||
auth:
|
||||
token:
|
||||
issuer: registry-token-issuer
|
||||
issuer: harbor-token-issuer
|
||||
realm: $ui_url/service/token
|
||||
rootcertbundle: /etc/registry/root.crt
|
||||
service: token-service
|
||||
service: harbor-registry
|
||||
|
||||
notifications:
|
||||
endpoints:
|
||||
|
@ -24,7 +24,7 @@ import (
|
||||
)
|
||||
|
||||
var adminServerDefaultConfig = map[string]interface{}{
|
||||
config.ExtEndpoint: "host01.com",
|
||||
config.ExtEndpoint: "https://host01.com",
|
||||
config.AUTHMode: config.DBAuth,
|
||||
config.DatabaseType: "mysql",
|
||||
config.MySQLHost: "127.0.0.1",
|
||||
|
@ -31,6 +31,7 @@ import (
|
||||
_ "github.com/vmware/harbor/src/ui/auth/db"
|
||||
_ "github.com/vmware/harbor/src/ui/auth/ldap"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
"github.com/vmware/harbor/src/ui/service/token"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -78,7 +79,7 @@ func main() {
|
||||
log.Fatalf("failed to initialize configurations: %v", err)
|
||||
}
|
||||
log.Info("configurations initialization completed")
|
||||
|
||||
token.InitCreators()
|
||||
database, err := config.Database()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to get database configuration: %v", err)
|
||||
|
@ -33,7 +33,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
issuer = "registry-token-issuer"
|
||||
issuer = "harbor-token-issuer"
|
||||
)
|
||||
|
||||
var privateKey string
|
||||
@ -74,84 +74,31 @@ func GetResourceActions(scopes []string) []*token.ResourceActions {
|
||||
return res
|
||||
}
|
||||
|
||||
// FilterAccess modify the action list in access based on permission
|
||||
func FilterAccess(username string, a *token.ResourceActions) {
|
||||
|
||||
if a.Type == "registry" && a.Name == "catalog" {
|
||||
log.Infof("current access, type: %s, name:%s, actions:%v \n", a.Type, a.Name, a.Actions)
|
||||
return
|
||||
}
|
||||
|
||||
//clear action list to assign to new acess element after perm check.
|
||||
a.Actions = []string{}
|
||||
if a.Type == "repository" {
|
||||
repoSplit := strings.Split(a.Name, "/")
|
||||
repoLength := len(repoSplit)
|
||||
if repoLength > 1 { //Only check the permission when the requested image has a namespace, i.e. project
|
||||
var projectName string
|
||||
registryURL, err := config.ExtEndpoint()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get domain name: %v", err)
|
||||
return
|
||||
}
|
||||
registryURL = strings.Split(registryURL, "://")[1]
|
||||
if repoSplit[0] == registryURL {
|
||||
projectName = repoSplit[1]
|
||||
log.Infof("Detected Registry URL in Project Name. Assuming this is a notary request and setting Project Name as %s\n", projectName)
|
||||
} else {
|
||||
projectName = repoSplit[0]
|
||||
}
|
||||
var permission string
|
||||
if len(username) > 0 {
|
||||
isAdmin, err := dao.IsAdminRole(username)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in IsAdminRole: %v", err)
|
||||
}
|
||||
if isAdmin {
|
||||
exist, err := dao.ProjectExists(projectName)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in CheckExistProject: %v", err)
|
||||
return
|
||||
}
|
||||
if exist {
|
||||
permission = "RWM"
|
||||
} else {
|
||||
permission = ""
|
||||
log.Infof("project %s does not exist, set empty permission for admin\n", projectName)
|
||||
}
|
||||
} else {
|
||||
permission, err = dao.GetPermission(username, projectName)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in GetPermission: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if strings.Contains(permission, "W") {
|
||||
a.Actions = append(a.Actions, "push")
|
||||
}
|
||||
if strings.Contains(permission, "M") {
|
||||
a.Actions = append(a.Actions, "*")
|
||||
}
|
||||
if strings.Contains(permission, "R") || dao.IsProjectPublic(projectName) {
|
||||
a.Actions = append(a.Actions, "pull")
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Infof("current access, type: %s, name:%s, actions:%v \n", a.Type, a.Name, a.Actions)
|
||||
}
|
||||
|
||||
// GenTokenForUI is for the UI process to call, so it won't establish a https connection from UI to proxy.
|
||||
func GenTokenForUI(username string, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) {
|
||||
func GenTokenForUI(username string, service string, scopes []string) (string, int, *time.Time, error) {
|
||||
isAdmin, err := dao.IsAdminRole(username)
|
||||
if err != nil {
|
||||
return "", 0, nil, err
|
||||
}
|
||||
f := &repositoryFilter{
|
||||
parser: &basicParser{},
|
||||
}
|
||||
u := userInfo{
|
||||
name: username,
|
||||
allPerm: isAdmin,
|
||||
}
|
||||
access := GetResourceActions(scopes)
|
||||
for _, a := range access {
|
||||
FilterAccess(username, a)
|
||||
err = f.filter(u, a)
|
||||
if err != nil {
|
||||
return "", 0, nil, err
|
||||
}
|
||||
}
|
||||
return MakeToken(username, service, access)
|
||||
return MakeRawToken(username, service, access)
|
||||
}
|
||||
|
||||
// MakeToken makes a valid jwt token based on parms.
|
||||
func MakeToken(username, service string, access []*token.ResourceActions) (token string, expiresIn int, issuedAt *time.Time, err error) {
|
||||
// MakeRawToken makes a valid jwt token based on parms.
|
||||
func MakeRawToken(username, service string, access []*token.ResourceActions) (token string, expiresIn int, issuedAt *time.Time, err error) {
|
||||
pk, err := libtrust.LoadKeyFile(privateKey)
|
||||
if err != nil {
|
||||
return "", 0, nil, err
|
||||
@ -169,6 +116,34 @@ func MakeToken(username, service string, access []*token.ResourceActions) (token
|
||||
return rs, expiresIn, issuedAt, nil
|
||||
}
|
||||
|
||||
type tokenJSON struct {
|
||||
Token string `json:"token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
IssuedAt string `json:"issued_at"`
|
||||
}
|
||||
|
||||
func makeToken(username, service string, access []*token.ResourceActions) (*tokenJSON, error) {
|
||||
raw, expires, issued, err := MakeRawToken(username, service, access)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tokenJSON{raw, expires, issued.Format(time.RFC3339)}, nil
|
||||
}
|
||||
|
||||
func permToActions(p string) []string {
|
||||
res := []string{}
|
||||
if strings.Contains(p, "W") {
|
||||
res = append(res, "push")
|
||||
}
|
||||
if strings.Contains(p, "M") {
|
||||
res = append(res, "*")
|
||||
}
|
||||
if strings.Contains(p, "R") {
|
||||
res = append(res, "pull")
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
//make token core
|
||||
func makeTokenCore(issuer, subject, audience string, expiration int,
|
||||
access []*token.ResourceActions, signingKey libtrust.PrivateKey) (t *token.Token, expiresIn int, issuedAt *time.Time, err error) {
|
||||
|
239
src/ui/service/token/creator.go
Normal file
239
src/ui/service/token/creator.go
Normal file
@ -0,0 +1,239 @@
|
||||
/*
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package token
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/docker/distribution/registry/auth/token"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var creatorMap map[string]Creator
|
||||
|
||||
const (
|
||||
notary = "harbor-notary"
|
||||
registry = "harbor-registry"
|
||||
)
|
||||
|
||||
//InitCreators initialize the token creators for different services
|
||||
func InitCreators() {
|
||||
creatorMap = make(map[string]Creator)
|
||||
ext, err := config.ExtEndpoint()
|
||||
if err != nil {
|
||||
log.Warningf("Failed to get ext enpoint, err: %v, the token service will not be functional with notary requests", err)
|
||||
} else {
|
||||
creatorMap[notary] = &generalCreator{
|
||||
validators: []ReqValidator{
|
||||
&basicAuthValidator{},
|
||||
},
|
||||
service: notary,
|
||||
filterMap: map[string]accessFilter{
|
||||
"repository": &repositoryFilter{
|
||||
parser: &endpointParser{
|
||||
endpoint: strings.Split(ext, "//")[1],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
creatorMap[registry] = &generalCreator{
|
||||
validators: []ReqValidator{
|
||||
&secretValidator{config.JobserviceSecret()},
|
||||
&basicAuthValidator{},
|
||||
},
|
||||
service: registry,
|
||||
filterMap: map[string]accessFilter{
|
||||
"repository": &repositoryFilter{
|
||||
//Workaround, had to use same service for both notary and registry
|
||||
parser: &endpointParser{
|
||||
endpoint: ext,
|
||||
},
|
||||
},
|
||||
"registry": ®istryFilter{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Creator creates a token ready to be served based on the http request.
|
||||
type Creator interface {
|
||||
Create(r *http.Request) (*tokenJSON, error)
|
||||
}
|
||||
|
||||
type imageParser interface {
|
||||
parse(s string) (*image, error)
|
||||
}
|
||||
|
||||
type image struct {
|
||||
namespace string
|
||||
repo string
|
||||
tag string
|
||||
}
|
||||
|
||||
type basicParser struct{}
|
||||
|
||||
func (b basicParser) parse(s string) (*image, error) {
|
||||
return parseImg(s)
|
||||
}
|
||||
|
||||
type endpointParser struct {
|
||||
endpoint string
|
||||
}
|
||||
|
||||
func (e endpointParser) parse(s string) (*image, error) {
|
||||
repo := strings.SplitN(s, "/", 2)
|
||||
if len(repo) < 2 {
|
||||
return nil, fmt.Errorf("Unable to parse image from string: %s", s)
|
||||
}
|
||||
//Workaround, need to use endpoint Parser to handle both cases.
|
||||
if strings.ContainsRune(repo[0], '.') {
|
||||
if repo[0] != e.endpoint {
|
||||
log.Warningf("Mismatch endpoint from string: %s, expected endpoint: %s, fallback to basic parser", s, e.endpoint)
|
||||
return parseImg(s)
|
||||
}
|
||||
return parseImg(repo[1])
|
||||
}
|
||||
return parseImg(s)
|
||||
}
|
||||
|
||||
//build Image accepts a string like library/ubuntu:14.04 and build a image struct
|
||||
func parseImg(s string) (*image, error) {
|
||||
repo := strings.SplitN(s, "/", 2)
|
||||
if len(repo) < 2 {
|
||||
return nil, fmt.Errorf("Unable to parse image from string: %s", s)
|
||||
}
|
||||
i := strings.SplitN(repo[1], ":", 2)
|
||||
res := &image{
|
||||
namespace: repo[0],
|
||||
repo: i[0],
|
||||
}
|
||||
if len(i) == 2 {
|
||||
res.tag = i[1]
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// An accessFilter will filter access based on userinfo
|
||||
type accessFilter interface {
|
||||
filter(user userInfo, a *token.ResourceActions) error
|
||||
}
|
||||
|
||||
type registryFilter struct {
|
||||
}
|
||||
|
||||
func (reg registryFilter) filter(user userInfo, a *token.ResourceActions) error {
|
||||
//Do not filter if the request is to access registry catalog
|
||||
if a.Name != "catalog" {
|
||||
return fmt.Errorf("Unable to handle, type: %s, name: %s", a.Type, a.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//repositoryFilter filters the access based on Harbor's permission model
|
||||
type repositoryFilter struct {
|
||||
parser imageParser
|
||||
}
|
||||
|
||||
func (rep repositoryFilter) filter(user userInfo, a *token.ResourceActions) error {
|
||||
//clear action list to assign to new acess element after perm check.
|
||||
a.Actions = []string{}
|
||||
img, err := rep.parser.parse(a.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
project := img.namespace
|
||||
permission := ""
|
||||
if user.allPerm {
|
||||
exist, err := dao.ProjectExists(project)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in CheckExistProject: %v", err)
|
||||
//just leave empty permission
|
||||
return nil
|
||||
}
|
||||
if exist {
|
||||
permission = "RWM"
|
||||
} else {
|
||||
log.Infof("project %s does not exist, set empty permission for admin\n", project)
|
||||
}
|
||||
} else {
|
||||
permission, err = dao.GetPermission(user.name, project)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in GetPermission: %v", err)
|
||||
//just leave empty permission
|
||||
return nil
|
||||
}
|
||||
if dao.IsProjectPublic(project) {
|
||||
permission += "R"
|
||||
}
|
||||
}
|
||||
a.Actions = permToActions(permission)
|
||||
return nil
|
||||
}
|
||||
|
||||
type generalCreator struct {
|
||||
validators []ReqValidator
|
||||
service string
|
||||
filterMap map[string]accessFilter
|
||||
}
|
||||
|
||||
type unauthorizedError struct{}
|
||||
|
||||
func (e *unauthorizedError) Error() string {
|
||||
return "Unauthorized"
|
||||
}
|
||||
|
||||
func (g generalCreator) Create(r *http.Request) (*tokenJSON, error) {
|
||||
var user *userInfo
|
||||
var err error
|
||||
var scopes []string
|
||||
scopeParm := r.URL.Query()["scope"]
|
||||
if len(scopeParm) > 0 {
|
||||
scopes = strings.Split(r.URL.Query()["scope"][0], " ")
|
||||
}
|
||||
log.Debugf("scopes: %v", scopes)
|
||||
for _, v := range g.validators {
|
||||
user, err = v.validate(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if user == nil {
|
||||
if len(scopes) == 0 {
|
||||
return nil, &unauthorizedError{}
|
||||
}
|
||||
user = &userInfo{}
|
||||
}
|
||||
access := GetResourceActions(scopes)
|
||||
for _, a := range access {
|
||||
f, ok := g.filterMap[a.Type]
|
||||
if !ok {
|
||||
log.Warningf("No filter found for access type: %s, skip.", a.Type)
|
||||
continue
|
||||
}
|
||||
err = f.filter(*user, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return makeToken(user.name, g.service, access)
|
||||
}
|
@ -16,17 +16,11 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/auth"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
svc_utils "github.com/vmware/harbor/src/ui/service/utils"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/docker/distribution/registry/auth/token"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
// Handler handles request on /service/token, which is the auth provider for registry.
|
||||
@ -38,62 +32,24 @@ type Handler struct {
|
||||
// and parse service and scope based on docker registry v2 standard,
|
||||
// checkes the permission agains local DB and generates jwt token.
|
||||
func (h *Handler) Get() {
|
||||
|
||||
var uid, password, username string
|
||||
request := h.Ctx.Request
|
||||
log.Debugf("URL for token request: %s", request.URL.String())
|
||||
service := h.GetString("service")
|
||||
scopes := h.GetStrings("scope")
|
||||
access := GetResourceActions(scopes)
|
||||
log.Infof("request url: %v", request.URL.String())
|
||||
|
||||
if svc_utils.VerifySecret(request, config.JobserviceSecret()) {
|
||||
log.Debugf("Will grant all access as this request is from job service with legal secret.")
|
||||
username = "job-service-user"
|
||||
} else {
|
||||
uid, password, _ = request.BasicAuth()
|
||||
log.Debugf("uid for logging: %s", uid)
|
||||
user := authenticate(uid, password)
|
||||
if user == nil {
|
||||
log.Warningf("login request with invalid credentials in token service, uid: %s", uid)
|
||||
if len(scopes) == 0 {
|
||||
h.CustomAbort(http.StatusUnauthorized, "")
|
||||
}
|
||||
} else {
|
||||
username = user.Username
|
||||
}
|
||||
log.Debugf("username for filtering access: %s.", username)
|
||||
for _, a := range access {
|
||||
FilterAccess(username, a)
|
||||
}
|
||||
tokenCreator, ok := creatorMap[service]
|
||||
if !ok {
|
||||
errMsg := fmt.Sprintf("Unable to handle service: %s", service)
|
||||
log.Errorf(errMsg)
|
||||
h.CustomAbort(http.StatusBadRequest, errMsg)
|
||||
}
|
||||
h.serveToken(username, service, access)
|
||||
}
|
||||
|
||||
func (h *Handler) serveToken(username, service string, access []*token.ResourceActions) {
|
||||
writer := h.Ctx.ResponseWriter
|
||||
//create token
|
||||
rawToken, expiresIn, issuedAt, err := MakeToken(username, service, access)
|
||||
token, err := tokenCreator.Create(request)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to make token, error: %v", err)
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
if _, ok := err.(*unauthorizedError); ok {
|
||||
h.CustomAbort(http.StatusUnauthorized, "")
|
||||
}
|
||||
log.Errorf("Unexpected error when creating the token, error: %v", err)
|
||||
h.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
tk := make(map[string]interface{})
|
||||
tk["token"] = rawToken
|
||||
tk["expires_in"] = expiresIn
|
||||
tk["issued_at"] = issuedAt.Format(time.RFC3339)
|
||||
h.Data["json"] = tk
|
||||
h.Data["json"] = token
|
||||
h.ServeJSON()
|
||||
}
|
||||
|
||||
func authenticate(principal, password string) *models.User {
|
||||
user, err := auth.Login(models.AuthModel{
|
||||
Principal: principal,
|
||||
Password: password,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in UserLogin: %v", err)
|
||||
return nil
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
@ -9,21 +9,28 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/vmware/harbor/src/common/utils/test"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if err := config.Init(); err != nil {
|
||||
log.Fatalf("failed to initialize configurations: %v", err)
|
||||
server, err := test.NewAdminserver(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
if err := os.Setenv("ADMIN_SERVER_URL", server.URL); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := config.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
result := m.Run()
|
||||
if result != 0 {
|
||||
os.Exit(result)
|
||||
@ -87,10 +94,11 @@ func TestMakeToken(t *testing.T) {
|
||||
}}
|
||||
svc := "harbor-registry"
|
||||
u := "tester"
|
||||
tokenString, _, _, err := MakeToken(u, svc, ra)
|
||||
tokenJSON, err := makeToken(u, svc, ra)
|
||||
if err != nil {
|
||||
t.Errorf("Error while making token: %v", err)
|
||||
}
|
||||
tokenString := tokenJSON.Token
|
||||
//t.Logf("privatekey: %s, crt: %s", tokenString, crt)
|
||||
pubKey, err := getPublicKey(crt)
|
||||
if err != nil {
|
||||
@ -102,13 +110,78 @@ func TestMakeToken(t *testing.T) {
|
||||
}
|
||||
return pubKey, nil
|
||||
})
|
||||
t.Logf("validity: %v", tok.Valid)
|
||||
t.Logf("Token validity: %v", tok.Valid)
|
||||
if err != nil {
|
||||
t.Errorf("Error while parsing the token: %v", err)
|
||||
}
|
||||
claims := tok.Claims.(*harborClaims)
|
||||
t.Logf("claims: %+v", *claims)
|
||||
assert.Equal(t, *(claims.Access[0]), *(ra[0]), "Access mismatch")
|
||||
assert.Equal(t, claims.Audience, svc, "Audience mismatch")
|
||||
|
||||
}
|
||||
|
||||
func TestPermToActions(t *testing.T) {
|
||||
perm1 := "RWM"
|
||||
perm2 := "MRR"
|
||||
perm3 := ""
|
||||
expect1 := []string{"push", "*", "pull"}
|
||||
expect2 := []string{"*", "pull"}
|
||||
expect3 := []string{}
|
||||
res1 := permToActions(perm1)
|
||||
res2 := permToActions(perm2)
|
||||
res3 := permToActions(perm3)
|
||||
assert.Equal(t, res1, expect1, fmt.Sprintf("actions mismatch for permission: %s", perm1))
|
||||
assert.Equal(t, res2, expect2, fmt.Sprintf("actions mismatch for permission: %s", perm2))
|
||||
assert.Equal(t, res3, expect3, fmt.Sprintf("actions mismatch for permission: %s", perm3))
|
||||
}
|
||||
|
||||
type parserTestRec struct {
|
||||
input string
|
||||
expect image
|
||||
expectError bool
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
InitCreators()
|
||||
}
|
||||
|
||||
func TestBasicParser(t *testing.T) {
|
||||
testList := []parserTestRec{parserTestRec{"library/ubuntu:14.04", image{"library", "ubuntu", "14.04"}, false},
|
||||
parserTestRec{"test/hello", image{"test", "hello", ""}, false},
|
||||
parserTestRec{"myimage:14.04", image{}, true},
|
||||
parserTestRec{"org/team/img", image{"org", "team/img", ""}, false},
|
||||
}
|
||||
|
||||
p := &basicParser{}
|
||||
for _, rec := range testList {
|
||||
r, err := p.parse(rec.input)
|
||||
if rec.expectError {
|
||||
assert.Error(t, err, "Expected error for input: %s", rec.input)
|
||||
} else {
|
||||
assert.Nil(t, err, "Expected no error for input: %s", rec.input)
|
||||
assert.Equal(t, rec.expect, *r, "result mismatch for input: %s", rec.input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndpointParser(t *testing.T) {
|
||||
p := &endpointParser{
|
||||
"10.117.4.142:5000",
|
||||
}
|
||||
testList := []parserTestRec{parserTestRec{"10.117.4.142:5000/library/ubuntu:14.04", image{"library", "ubuntu", "14.04"}, false},
|
||||
parserTestRec{"myimage:14.04", image{}, true},
|
||||
//Test the temp workaround
|
||||
parserTestRec{"10.117.4.142:80/library/myimage:14.04", image{"10.117.4.142:80", "library/myimage", "14.04"}, false},
|
||||
parserTestRec{"library/myimage:14.04", image{"library", "myimage", "14.04"}, false},
|
||||
parserTestRec{"10.117.4.142:5000/myimage:14.04", image{}, true},
|
||||
parserTestRec{"10.117.4.142:5000/org/team/img", image{"org", "team/img", ""}, false},
|
||||
}
|
||||
for _, rec := range testList {
|
||||
r, err := p.parse(rec.input)
|
||||
if rec.expectError {
|
||||
assert.Error(t, err, "Expected error for input: %s", rec.input)
|
||||
} else {
|
||||
assert.Nil(t, err, "Expected no error for input: %s", rec.input)
|
||||
assert.Equal(t, rec.expect, *r, "result mismatch for input: %s", rec.input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
84
src/ui/service/token/validator.go
Normal file
84
src/ui/service/token/validator.go
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package token
|
||||
|
||||
import (
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/auth"
|
||||
svc_utils "github.com/vmware/harbor/src/ui/service/utils"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//For filtering permission by token creators.
|
||||
type userInfo struct {
|
||||
name string
|
||||
allPerm bool
|
||||
}
|
||||
|
||||
//ReqValidator validates request based on different rules and returns userInfo
|
||||
type ReqValidator interface {
|
||||
validate(req *http.Request) (*userInfo, error)
|
||||
}
|
||||
|
||||
type secretValidator struct {
|
||||
secret string
|
||||
}
|
||||
|
||||
var jobServiceUserInfo userInfo
|
||||
|
||||
func init() {
|
||||
jobServiceUserInfo = userInfo{
|
||||
name: "job-service-user",
|
||||
allPerm: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (sv secretValidator) validate(r *http.Request) (*userInfo, error) {
|
||||
if svc_utils.VerifySecret(r, sv.secret) {
|
||||
return &jobServiceUserInfo, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type basicAuthValidator struct {
|
||||
}
|
||||
|
||||
func (ba basicAuthValidator) validate(r *http.Request) (*userInfo, error) {
|
||||
uid, password, _ := r.BasicAuth()
|
||||
user, err := auth.Login(models.AuthModel{
|
||||
Principal: uid,
|
||||
Password: password,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in UserLogin: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
if user == nil {
|
||||
log.Warningf("Invalid credentials for uid: %s", uid)
|
||||
return nil, nil
|
||||
}
|
||||
isAdmin, err := dao.IsAdminRole(user.UserID)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in IsAdminRole: %v", err)
|
||||
}
|
||||
info := &userInfo{
|
||||
name: user.Username,
|
||||
allPerm: isAdmin,
|
||||
}
|
||||
return info, nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user