harbor/utils/registry/auth/tokenhandler.go

248 lines
6.1 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"
"sync"
2016-04-27 11:59:43 +02:00
"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
2016-04-29 08:59:00 +02:00
// Implements interface Handler
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
sync.Mutex
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
2016-04-29 10:59:54 +02:00
var token string
hasFrom := false
from := req.URL.Query().Get("from")
if len(from) != 0 {
s := &scope{
Type: "repository",
Name: from,
Actions: []string{"pull"},
}
scopes = append(scopes, s)
// do not cache the token if "from" appears
hasFrom = true
}
2016-04-27 11:59:43 +02:00
scopes = append(scopes, t.scope)
2016-04-28 12:49:59 +02:00
expired := true
2016-04-27 11:59:43 +02:00
cachedToken, cachedExpiredIn, cachedIssuedAt := t.getCachedToken()
if len(cachedToken) != 0 && cachedExpiredIn != 0 && cachedIssuedAt != nil {
expired = cachedIssuedAt.Add(time.Duration(cachedExpiredIn) * time.Second).Before(time.Now().UTC())
2016-04-27 11:59:43 +02:00
}
2016-04-29 10:59:54 +02:00
if expired || hasFrom {
2016-04-27 11:59:43 +02:00
scopeStrs := []string{}
for _, scope := range scopes {
scopeStrs = append(scopeStrs, scope.string())
}
2016-04-29 11:46:00 +02:00
to, 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-29 11:46:00 +02:00
token = to
2016-04-28 12:49:59 +02:00
2016-04-29 10:59:54 +02:00
if !hasFrom {
t.updateCachedToken(to, expiresIn, issuedAt)
2016-04-29 10:59:54 +02:00
log.Debug("add token to cache")
}
} else {
token = cachedToken
2016-04-28 12:49:59 +02:00
log.Debug("get token from cache")
2016-04-27 11:59:43 +02:00
}
2016-04-29 10:59:54 +02:00
req.Header.Add(http.CanonicalHeaderKey("Authorization"), fmt.Sprintf("Bearer %s", token))
2016-04-27 11:59:43 +02:00
log.Debugf("add token to request: %s %s", req.Method, req.URL.String())
return nil
}
func (t *tokenHandler) getCachedToken() (string, int, *time.Time) {
t.Lock()
defer t.Unlock()
return t.cache, t.expiresIn, t.issuedAt
}
func (t *tokenHandler) updateCachedToken(token string, expiresIn int, issuedAt *time.Time) {
t.Lock()
defer t.Unlock()
t.cache = token
t.expiresIn = expiresIn
t.issuedAt = issuedAt
}
2016-04-29 08:59:00 +02:00
// Implements interface Handler
2016-04-27 11:59:43 +02:00
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-29 08:59:00 +02:00
func (s *standardTokenHandler) generateToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) {
2016-04-27 11:59:43 +02:00
u, err := url.Parse(realm)
if err != nil {
2016-04-29 08:59:00 +02:00
return
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-29 08:59:00 +02:00
return
2016-04-27 11:59:43 +02:00
}
s.credential.AddAuthorization(r)
resp, err := s.client.Do(r)
if err != nil {
2016-04-29 08:59:00 +02:00
return
2016-04-27 11:59:43 +02:00
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
2016-04-29 08:59:00 +02:00
return
2016-04-27 11:59:43 +02:00
}
if resp.StatusCode != http.StatusOK {
2016-04-29 08:59:00 +02:00
err = registry_errors.Error{
2016-04-27 11:59:43 +02:00
StatusCode: resp.StatusCode,
2016-05-10 16:01:38 +02:00
StatusText: resp.Status,
2016-04-27 11:59:43 +02:00
Message: string(b),
}
2016-04-29 08:59:00 +02:00
return
2016-04-27 11:59:43 +02:00
}
tk := struct {
2016-04-29 08:59:00 +02:00
Token string `json:"token"`
2016-05-10 04:59:59 +02:00
ExpiresIn int `json:"expires_in"`
2016-04-29 08:59:00 +02:00
IssuedAt string `json:"issued_at"`
2016-04-27 11:59:43 +02:00
}{}
if err = json.Unmarshal(b, &tk); err != nil {
2016-04-29 08:59:00 +02:00
return
}
token = tk.Token
2016-05-10 04:59:59 +02:00
expiresIn = tk.ExpiresIn
t, err := time.Parse(time.RFC3339, tk.IssuedAt)
2016-04-29 08:59:00 +02:00
if err != nil {
2016-05-10 04:59:59 +02:00
log.Errorf("error occurred while parsing issued_at: %v", err)
2016-04-29 08:59:00 +02:00
err = nil
} else {
2016-05-10 04:59:59 +02:00
issuedAt = &t
2016-04-27 11:59:43 +02:00
}
log.Debug("get token from token server")
2016-04-29 08:59:00 +02:00
return
2016-04-27 11:59:43 +02:00
}
2016-04-29 08:59:00 +02:00
// Implements interface Handler
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) {
2016-04-29 08:59:00 +02:00
token, expiresIn, issuedAt, 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
}