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"
|
2016-05-13 10:09:43 +02:00
|
|
|
"sync"
|
2016-04-27 11:59:43 +02:00
|
|
|
"time"
|
|
|
|
|
|
|
|
token_util "github.com/vmware/harbor/service/token"
|
|
|
|
"github.com/vmware/harbor/utils/log"
|
2016-05-24 08:59:36 +02:00
|
|
|
registry_error "github.com/vmware/harbor/utils/registry/error"
|
2016-04-27 11:59:43 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
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
|
2016-05-13 10:09:43 +02:00
|
|
|
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
|
|
|
|
2016-05-24 08:59:36 +02:00
|
|
|
if t.scope != nil {
|
|
|
|
scopes = append(scopes, t.scope)
|
|
|
|
}
|
2016-04-27 11:59:43 +02:00
|
|
|
|
2016-04-28 12:49:59 +02:00
|
|
|
expired := true
|
2016-04-27 11:59:43 +02:00
|
|
|
|
2016-05-13 10:09: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 {
|
2016-05-13 10:09:43 +02:00
|
|
|
t.updateCachedToken(to, expiresIn, issuedAt)
|
2016-04-29 10:59:54 +02:00
|
|
|
log.Debug("add token to cache")
|
|
|
|
}
|
|
|
|
} else {
|
2016-05-13 10:09:43 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2016-05-13 10:09:43 +02:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
2016-05-24 08:59:36 +02:00
|
|
|
if len(scopeType) != 0 || len(scopeName) != 0 {
|
|
|
|
handler.scope = &scope{
|
|
|
|
Type: scopeType,
|
|
|
|
Name: scopeName,
|
|
|
|
Actions: scopeActions,
|
|
|
|
}
|
2016-04-27 11:59:43 +02:00
|
|
|
}
|
2016-05-24 08:59:36 +02:00
|
|
|
|
2016-04-27 11:59:43 +02:00
|
|
|
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-05-24 08:59:36 +02:00
|
|
|
err = ®istry_error.Error{
|
2016-04-27 11:59:43 +02:00
|
|
|
StatusCode: resp.StatusCode,
|
2016-05-24 08:59:36 +02:00
|
|
|
Detail: string(b),
|
2016-04-27 11:59:43 +02:00
|
|
|
}
|
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
|
|
|
|
|
2016-05-12 07:41:48 +02:00
|
|
|
if len(tk.IssuedAt) != 0 {
|
|
|
|
t, err := time.Parse(time.RFC3339, tk.IssuedAt)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("error occurred while parsing issued_at: %v", err)
|
|
|
|
err = nil
|
|
|
|
} else {
|
|
|
|
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
|
|
|
}
|