harbor/utils/registry/auth/tokenhandler.go

199 lines
5.0 KiB
Go
Raw Normal View History

2016-04-27 11:59:43 +02:00
/*
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 auth
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
token_util "github.com/vmware/harbor/service/token"
"github.com/vmware/harbor/utils/log"
registry_errors "github.com/vmware/harbor/utils/registry/errors"
)
type scope struct {
Type string
Name string
Actions []string
}
func (s *scope) string() string {
return fmt.Sprintf("%s:%s:%s", s.Type, s.Name, strings.Join(s.Actions, ","))
}
2016-04-28 12:49:59 +02:00
type tokenGenerator func(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error)
2016-04-27 11:59:43 +02:00
type tokenHandler struct {
2016-04-28 12:49:59 +02:00
scope *scope
tg tokenGenerator
cache string // cached token
expiresIn int // The duration in seconds since the token was issued that it will remain valid
issuedAt *time.Time // The RFC3339-serialized UTC standard time at which a given token was issued
2016-04-27 11:59:43 +02:00
}
2016-04-28 12:49:59 +02:00
// Scheme returns the scheme that the handler can handle
func (t *tokenHandler) Scheme() string {
2016-04-27 11:59:43 +02:00
return "bearer"
}
// AuthorizeRequest will add authorization header which contains a token before the request is sent
func (t *tokenHandler) AuthorizeRequest(req *http.Request, params map[string]string) error {
var scopes []*scope
// TODO handle additional scope: xxx.xxx.xxx?from=repo
scopes = append(scopes, t.scope)
2016-04-28 12:49:59 +02:00
expired := true
2016-04-27 11:59:43 +02:00
2016-04-28 12:49:59 +02:00
if t.expiresIn != 0 && t.issuedAt != nil {
expired = t.issuedAt.Add(time.Duration(t.expiresIn) * time.Second).Before(time.Now().UTC())
2016-04-27 11:59:43 +02:00
}
2016-04-28 12:49:59 +02:00
if expired {
2016-04-27 11:59:43 +02:00
scopeStrs := []string{}
for _, scope := range scopes {
scopeStrs = append(scopeStrs, scope.string())
}
2016-04-28 12:49:59 +02:00
token, expiresIn, issuedAt, err := t.tg(params["realm"], params["service"], scopeStrs)
2016-04-27 11:59:43 +02:00
if err != nil {
return err
}
2016-04-28 12:49:59 +02:00
t.cache = token
t.expiresIn = expiresIn
t.issuedAt = issuedAt
}
if !expired {
log.Debug("get token from cache")
2016-04-27 11:59:43 +02:00
}
2016-04-28 12:49:59 +02:00
req.Header.Add(http.CanonicalHeaderKey("Authorization"), fmt.Sprintf("Bearer %s", t.cache))
2016-04-27 11:59:43 +02:00
log.Debugf("add token to request: %s %s", req.Method, req.URL.String())
return nil
}
type standardTokenHandler struct {
tokenHandler
client *http.Client
credential Credential
}
// NewStandardTokenHandler returns a standard token handler. The handler will request a token
// from token server and add it to the origin request
// TODO deal with https
func NewStandardTokenHandler(credential Credential, scopeType, scopeName string, scopeActions ...string) Handler {
handler := &standardTokenHandler{
client: &http.Client{
Transport: http.DefaultTransport,
},
credential: credential,
}
handler.scope = &scope{
Type: scopeType,
Name: scopeName,
Actions: scopeActions,
}
handler.tg = handler.generateToken
return handler
}
2016-04-28 12:49:59 +02:00
func (s *standardTokenHandler) generateToken(realm, service string, scopes []string) (string, int, *time.Time, error) {
2016-04-27 11:59:43 +02:00
u, err := url.Parse(realm)
if err != nil {
2016-04-28 12:49:59 +02:00
return "", 0, nil, err
2016-04-27 11:59:43 +02:00
}
q := u.Query()
q.Add("service", service)
for _, scope := range scopes {
q.Add("scope", scope)
}
u.RawQuery = q.Encode()
r, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
2016-04-28 12:49:59 +02:00
return "", 0, nil, err
2016-04-27 11:59:43 +02:00
}
s.credential.AddAuthorization(r)
resp, err := s.client.Do(r)
if err != nil {
2016-04-28 12:49:59 +02:00
return "", 0, nil, err
2016-04-27 11:59:43 +02:00
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
2016-04-28 12:49:59 +02:00
return "", 0, nil, err
2016-04-27 11:59:43 +02:00
}
if resp.StatusCode != http.StatusOK {
2016-04-28 12:49:59 +02:00
return "", 0, nil, registry_errors.Error{
2016-04-27 11:59:43 +02:00
StatusCode: resp.StatusCode,
Message: string(b),
}
}
2016-04-28 12:49:59 +02:00
// TODO
2016-04-27 11:59:43 +02:00
tk := struct {
Token string `json:"token"`
}{}
if err = json.Unmarshal(b, &tk); err != nil {
2016-04-28 12:49:59 +02:00
return "", 0, nil, err
2016-04-27 11:59:43 +02:00
}
log.Debug("get token from token server")
2016-04-28 12:49:59 +02:00
return tk.Token, 0, nil, nil
2016-04-27 11:59:43 +02:00
}
type usernameTokenHandler struct {
tokenHandler
username string
}
// NewUsernameTokenHandler returns a handler which will generate a token according to
// the user's privileges
func NewUsernameTokenHandler(username string, scopeType, scopeName string, scopeActions ...string) Handler {
handler := &usernameTokenHandler{
username: username,
}
handler.scope = &scope{
Type: scopeType,
Name: scopeName,
Actions: scopeActions,
}
handler.tg = handler.generateToken
return handler
}
2016-04-28 12:49:59 +02:00
func (u *usernameTokenHandler) generateToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) {
// TODO
token, err = token_util.GenTokenForUI(u.username, service, scopes)
2016-04-27 11:59:43 +02:00
log.Debug("get token by calling GenTokenForUI directly")
2016-04-28 12:49:59 +02:00
return
2016-04-27 11:59:43 +02:00
}