mirror of
https://github.com/openfaas/faas.git
synced 2025-06-22 14:53:25 +00:00
Vendoring with Glide and delete function handler
This commit is contained in:
58
gateway/vendor/github.com/docker/distribution/registry/client/auth/api_version.go
generated
vendored
Normal file
58
gateway/vendor/github.com/docker/distribution/registry/client/auth/api_version.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// APIVersion represents a version of an API including its
|
||||
// type and version number.
|
||||
type APIVersion struct {
|
||||
// Type refers to the name of a specific API specification
|
||||
// such as "registry"
|
||||
Type string
|
||||
|
||||
// Version is the version of the API specification implemented,
|
||||
// This may omit the revision number and only include
|
||||
// the major and minor version, such as "2.0"
|
||||
Version string
|
||||
}
|
||||
|
||||
// String returns the string formatted API Version
|
||||
func (v APIVersion) String() string {
|
||||
return v.Type + "/" + v.Version
|
||||
}
|
||||
|
||||
// APIVersions gets the API versions out of an HTTP response using the provided
|
||||
// version header as the key for the HTTP header.
|
||||
func APIVersions(resp *http.Response, versionHeader string) []APIVersion {
|
||||
versions := []APIVersion{}
|
||||
if versionHeader != "" {
|
||||
for _, supportedVersions := range resp.Header[http.CanonicalHeaderKey(versionHeader)] {
|
||||
for _, version := range strings.Fields(supportedVersions) {
|
||||
versions = append(versions, ParseAPIVersion(version))
|
||||
}
|
||||
}
|
||||
}
|
||||
return versions
|
||||
}
|
||||
|
||||
// ParseAPIVersion parses an API version string into an APIVersion
|
||||
// Format (Expected, not enforced):
|
||||
// API version string = <API type> '/' <API version>
|
||||
// API type = [a-z][a-z0-9]*
|
||||
// API version = [0-9]+(\.[0-9]+)?
|
||||
// TODO(dmcgowan): Enforce format, add error condition, remove unknown type
|
||||
func ParseAPIVersion(versionStr string) APIVersion {
|
||||
idx := strings.IndexRune(versionStr, '/')
|
||||
if idx == -1 {
|
||||
return APIVersion{
|
||||
Type: "unknown",
|
||||
Version: versionStr,
|
||||
}
|
||||
}
|
||||
return APIVersion{
|
||||
Type: strings.ToLower(versionStr[:idx]),
|
||||
Version: versionStr[idx+1:],
|
||||
}
|
||||
}
|
27
gateway/vendor/github.com/docker/distribution/registry/client/auth/challenge/addr.go
generated
vendored
Normal file
27
gateway/vendor/github.com/docker/distribution/registry/client/auth/challenge/addr.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
package challenge
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FROM: https://golang.org/src/net/http/http.go
|
||||
// Given a string of the form "host", "host:port", or "[ipv6::address]:port",
|
||||
// return true if the string includes a port.
|
||||
func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
|
||||
|
||||
// FROM: http://golang.org/src/net/http/transport.go
|
||||
var portMap = map[string]string{
|
||||
"http": "80",
|
||||
"https": "443",
|
||||
}
|
||||
|
||||
// canonicalAddr returns url.Host but always with a ":port" suffix
|
||||
// FROM: http://golang.org/src/net/http/transport.go
|
||||
func canonicalAddr(url *url.URL) string {
|
||||
addr := url.Host
|
||||
if !hasPort(addr) {
|
||||
return addr + ":" + portMap[url.Scheme]
|
||||
}
|
||||
return addr
|
||||
}
|
237
gateway/vendor/github.com/docker/distribution/registry/client/auth/challenge/authchallenge.go
generated
vendored
Normal file
237
gateway/vendor/github.com/docker/distribution/registry/client/auth/challenge/authchallenge.go
generated
vendored
Normal file
@ -0,0 +1,237 @@
|
||||
package challenge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Challenge carries information from a WWW-Authenticate response header.
|
||||
// See RFC 2617.
|
||||
type Challenge struct {
|
||||
// Scheme is the auth-scheme according to RFC 2617
|
||||
Scheme string
|
||||
|
||||
// Parameters are the auth-params according to RFC 2617
|
||||
Parameters map[string]string
|
||||
}
|
||||
|
||||
// Manager manages the challenges for endpoints.
|
||||
// The challenges are pulled out of HTTP responses. Only
|
||||
// responses which expect challenges should be added to
|
||||
// the manager, since a non-unauthorized request will be
|
||||
// viewed as not requiring challenges.
|
||||
type Manager interface {
|
||||
// GetChallenges returns the challenges for the given
|
||||
// endpoint URL.
|
||||
GetChallenges(endpoint url.URL) ([]Challenge, error)
|
||||
|
||||
// AddResponse adds the response to the challenge
|
||||
// manager. The challenges will be parsed out of
|
||||
// the WWW-Authenicate headers and added to the
|
||||
// URL which was produced the response. If the
|
||||
// response was authorized, any challenges for the
|
||||
// endpoint will be cleared.
|
||||
AddResponse(resp *http.Response) error
|
||||
}
|
||||
|
||||
// NewSimpleManager returns an instance of
|
||||
// Manger which only maps endpoints to challenges
|
||||
// based on the responses which have been added the
|
||||
// manager. The simple manager will make no attempt to
|
||||
// perform requests on the endpoints or cache the responses
|
||||
// to a backend.
|
||||
func NewSimpleManager() Manager {
|
||||
return &simpleManager{
|
||||
Challanges: make(map[string][]Challenge),
|
||||
}
|
||||
}
|
||||
|
||||
type simpleManager struct {
|
||||
sync.RWMutex
|
||||
Challanges map[string][]Challenge
|
||||
}
|
||||
|
||||
func normalizeURL(endpoint *url.URL) {
|
||||
endpoint.Host = strings.ToLower(endpoint.Host)
|
||||
endpoint.Host = canonicalAddr(endpoint)
|
||||
}
|
||||
|
||||
func (m *simpleManager) GetChallenges(endpoint url.URL) ([]Challenge, error) {
|
||||
normalizeURL(&endpoint)
|
||||
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
challenges := m.Challanges[endpoint.String()]
|
||||
return challenges, nil
|
||||
}
|
||||
|
||||
func (m *simpleManager) AddResponse(resp *http.Response) error {
|
||||
challenges := ResponseChallenges(resp)
|
||||
if resp.Request == nil {
|
||||
return fmt.Errorf("missing request reference")
|
||||
}
|
||||
urlCopy := url.URL{
|
||||
Path: resp.Request.URL.Path,
|
||||
Host: resp.Request.URL.Host,
|
||||
Scheme: resp.Request.URL.Scheme,
|
||||
}
|
||||
normalizeURL(&urlCopy)
|
||||
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
m.Challanges[urlCopy.String()] = challenges
|
||||
return nil
|
||||
}
|
||||
|
||||
// Octet types from RFC 2616.
|
||||
type octetType byte
|
||||
|
||||
var octetTypes [256]octetType
|
||||
|
||||
const (
|
||||
isToken octetType = 1 << iota
|
||||
isSpace
|
||||
)
|
||||
|
||||
func init() {
|
||||
// OCTET = <any 8-bit sequence of data>
|
||||
// CHAR = <any US-ASCII character (octets 0 - 127)>
|
||||
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
|
||||
// CR = <US-ASCII CR, carriage return (13)>
|
||||
// LF = <US-ASCII LF, linefeed (10)>
|
||||
// SP = <US-ASCII SP, space (32)>
|
||||
// HT = <US-ASCII HT, horizontal-tab (9)>
|
||||
// <"> = <US-ASCII double-quote mark (34)>
|
||||
// CRLF = CR LF
|
||||
// LWS = [CRLF] 1*( SP | HT )
|
||||
// TEXT = <any OCTET except CTLs, but including LWS>
|
||||
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
|
||||
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
|
||||
// token = 1*<any CHAR except CTLs or separators>
|
||||
// qdtext = <any TEXT except <">>
|
||||
|
||||
for c := 0; c < 256; c++ {
|
||||
var t octetType
|
||||
isCtl := c <= 31 || c == 127
|
||||
isChar := 0 <= c && c <= 127
|
||||
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
|
||||
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
|
||||
t |= isSpace
|
||||
}
|
||||
if isChar && !isCtl && !isSeparator {
|
||||
t |= isToken
|
||||
}
|
||||
octetTypes[c] = t
|
||||
}
|
||||
}
|
||||
|
||||
// ResponseChallenges returns a list of authorization challenges
|
||||
// for the given http Response. Challenges are only checked if
|
||||
// the response status code was a 401.
|
||||
func ResponseChallenges(resp *http.Response) []Challenge {
|
||||
if resp.StatusCode == http.StatusUnauthorized {
|
||||
// Parse the WWW-Authenticate Header and store the challenges
|
||||
// on this endpoint object.
|
||||
return parseAuthHeader(resp.Header)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseAuthHeader(header http.Header) []Challenge {
|
||||
challenges := []Challenge{}
|
||||
for _, h := range header[http.CanonicalHeaderKey("WWW-Authenticate")] {
|
||||
v, p := parseValueAndParams(h)
|
||||
if v != "" {
|
||||
challenges = append(challenges, Challenge{Scheme: v, Parameters: p})
|
||||
}
|
||||
}
|
||||
return challenges
|
||||
}
|
||||
|
||||
func parseValueAndParams(header string) (value string, params map[string]string) {
|
||||
params = make(map[string]string)
|
||||
value, s := expectToken(header)
|
||||
if value == "" {
|
||||
return
|
||||
}
|
||||
value = strings.ToLower(value)
|
||||
s = "," + skipSpace(s)
|
||||
for strings.HasPrefix(s, ",") {
|
||||
var pkey string
|
||||
pkey, s = expectToken(skipSpace(s[1:]))
|
||||
if pkey == "" {
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(s, "=") {
|
||||
return
|
||||
}
|
||||
var pvalue string
|
||||
pvalue, s = expectTokenOrQuoted(s[1:])
|
||||
if pvalue == "" {
|
||||
return
|
||||
}
|
||||
pkey = strings.ToLower(pkey)
|
||||
params[pkey] = pvalue
|
||||
s = skipSpace(s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func skipSpace(s string) (rest string) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if octetTypes[s[i]]&isSpace == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[i:]
|
||||
}
|
||||
|
||||
func expectToken(s string) (token, rest string) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if octetTypes[s[i]]&isToken == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[:i], s[i:]
|
||||
}
|
||||
|
||||
func expectTokenOrQuoted(s string) (value string, rest string) {
|
||||
if !strings.HasPrefix(s, "\"") {
|
||||
return expectToken(s)
|
||||
}
|
||||
s = s[1:]
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '"':
|
||||
return s[:i], s[i+1:]
|
||||
case '\\':
|
||||
p := make([]byte, len(s)-1)
|
||||
j := copy(p, s[:i])
|
||||
escape := true
|
||||
for i = i + 1; i < len(s); i++ {
|
||||
b := s[i]
|
||||
switch {
|
||||
case escape:
|
||||
escape = false
|
||||
p[j] = b
|
||||
j++
|
||||
case b == '\\':
|
||||
escape = true
|
||||
case b == '"':
|
||||
return string(p[:j]), s[i+1:]
|
||||
default:
|
||||
p[j] = b
|
||||
j++
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
127
gateway/vendor/github.com/docker/distribution/registry/client/auth/challenge/authchallenge_test.go
generated
vendored
Normal file
127
gateway/vendor/github.com/docker/distribution/registry/client/auth/challenge/authchallenge_test.go
generated
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
package challenge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAuthChallengeParse(t *testing.T) {
|
||||
header := http.Header{}
|
||||
header.Add("WWW-Authenticate", `Bearer realm="https://auth.example.com/token",service="registry.example.com",other=fun,slashed="he\"\l\lo"`)
|
||||
|
||||
challenges := parseAuthHeader(header)
|
||||
if len(challenges) != 1 {
|
||||
t.Fatalf("Unexpected number of auth challenges: %d, expected 1", len(challenges))
|
||||
}
|
||||
challenge := challenges[0]
|
||||
|
||||
if expected := "bearer"; challenge.Scheme != expected {
|
||||
t.Fatalf("Unexpected scheme: %s, expected: %s", challenge.Scheme, expected)
|
||||
}
|
||||
|
||||
if expected := "https://auth.example.com/token"; challenge.Parameters["realm"] != expected {
|
||||
t.Fatalf("Unexpected param: %s, expected: %s", challenge.Parameters["realm"], expected)
|
||||
}
|
||||
|
||||
if expected := "registry.example.com"; challenge.Parameters["service"] != expected {
|
||||
t.Fatalf("Unexpected param: %s, expected: %s", challenge.Parameters["service"], expected)
|
||||
}
|
||||
|
||||
if expected := "fun"; challenge.Parameters["other"] != expected {
|
||||
t.Fatalf("Unexpected param: %s, expected: %s", challenge.Parameters["other"], expected)
|
||||
}
|
||||
|
||||
if expected := "he\"llo"; challenge.Parameters["slashed"] != expected {
|
||||
t.Fatalf("Unexpected param: %s, expected: %s", challenge.Parameters["slashed"], expected)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAuthChallengeNormalization(t *testing.T) {
|
||||
testAuthChallengeNormalization(t, "reg.EXAMPLE.com")
|
||||
testAuthChallengeNormalization(t, "bɿɒʜɔiɿ-ɿɘƚƨim-ƚol-ɒ-ƨʞnɒʜƚ.com")
|
||||
testAuthChallengeNormalization(t, "reg.example.com:80")
|
||||
testAuthChallengeConcurrent(t, "reg.EXAMPLE.com")
|
||||
}
|
||||
|
||||
func testAuthChallengeNormalization(t *testing.T, host string) {
|
||||
|
||||
scm := NewSimpleManager()
|
||||
|
||||
url, err := url.Parse(fmt.Sprintf("http://%s/v2/", host))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp := &http.Response{
|
||||
Request: &http.Request{
|
||||
URL: url,
|
||||
},
|
||||
Header: make(http.Header),
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
}
|
||||
resp.Header.Add("WWW-Authenticate", fmt.Sprintf("Bearer realm=\"https://%s/token\",service=\"registry.example.com\"", host))
|
||||
|
||||
err = scm.AddResponse(resp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
lowered := *url
|
||||
lowered.Host = strings.ToLower(lowered.Host)
|
||||
lowered.Host = canonicalAddr(&lowered)
|
||||
c, err := scm.GetChallenges(lowered)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(c) == 0 {
|
||||
t.Fatal("Expected challenge for lower-cased-host URL")
|
||||
}
|
||||
}
|
||||
|
||||
func testAuthChallengeConcurrent(t *testing.T, host string) {
|
||||
|
||||
scm := NewSimpleManager()
|
||||
|
||||
url, err := url.Parse(fmt.Sprintf("http://%s/v2/", host))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp := &http.Response{
|
||||
Request: &http.Request{
|
||||
URL: url,
|
||||
},
|
||||
Header: make(http.Header),
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
}
|
||||
resp.Header.Add("WWW-Authenticate", fmt.Sprintf("Bearer realm=\"https://%s/token\",service=\"registry.example.com\"", host))
|
||||
var s sync.WaitGroup
|
||||
s.Add(2)
|
||||
go func() {
|
||||
defer s.Done()
|
||||
for i := 0; i < 200; i++ {
|
||||
err = scm.AddResponse(resp)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer s.Done()
|
||||
lowered := *url
|
||||
lowered.Host = strings.ToLower(lowered.Host)
|
||||
for k := 0; k < 200; k++ {
|
||||
_, err := scm.GetChallenges(lowered)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
s.Wait()
|
||||
}
|
505
gateway/vendor/github.com/docker/distribution/registry/client/auth/session.go
generated
vendored
Normal file
505
gateway/vendor/github.com/docker/distribution/registry/client/auth/session.go
generated
vendored
Normal file
@ -0,0 +1,505 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/registry/client"
|
||||
"github.com/docker/distribution/registry/client/auth/challenge"
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoBasicAuthCredentials is returned if a request can't be authorized with
|
||||
// basic auth due to lack of credentials.
|
||||
ErrNoBasicAuthCredentials = errors.New("no basic auth credentials")
|
||||
|
||||
// ErrNoToken is returned if a request is successful but the body does not
|
||||
// contain an authorization token.
|
||||
ErrNoToken = errors.New("authorization server did not include a token in the response")
|
||||
)
|
||||
|
||||
const defaultClientID = "registry-client"
|
||||
|
||||
// AuthenticationHandler is an interface for authorizing a request from
|
||||
// params from a "WWW-Authenicate" header for a single scheme.
|
||||
type AuthenticationHandler interface {
|
||||
// Scheme returns the scheme as expected from the "WWW-Authenicate" header.
|
||||
Scheme() string
|
||||
|
||||
// AuthorizeRequest adds the authorization header to a request (if needed)
|
||||
// using the parameters from "WWW-Authenticate" method. The parameters
|
||||
// values depend on the scheme.
|
||||
AuthorizeRequest(req *http.Request, params map[string]string) error
|
||||
}
|
||||
|
||||
// CredentialStore is an interface for getting credentials for
|
||||
// a given URL
|
||||
type CredentialStore interface {
|
||||
// Basic returns basic auth for the given URL
|
||||
Basic(*url.URL) (string, string)
|
||||
|
||||
// RefreshToken returns a refresh token for the
|
||||
// given URL and service
|
||||
RefreshToken(*url.URL, string) string
|
||||
|
||||
// SetRefreshToken sets the refresh token if none
|
||||
// is provided for the given url and service
|
||||
SetRefreshToken(realm *url.URL, service, token string)
|
||||
}
|
||||
|
||||
// NewAuthorizer creates an authorizer which can handle multiple authentication
|
||||
// schemes. The handlers are tried in order, the higher priority authentication
|
||||
// methods should be first. The challengeMap holds a list of challenges for
|
||||
// a given root API endpoint (for example "https://registry-1.docker.io/v2/").
|
||||
func NewAuthorizer(manager challenge.Manager, handlers ...AuthenticationHandler) transport.RequestModifier {
|
||||
return &endpointAuthorizer{
|
||||
challenges: manager,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
type endpointAuthorizer struct {
|
||||
challenges challenge.Manager
|
||||
handlers []AuthenticationHandler
|
||||
transport http.RoundTripper
|
||||
}
|
||||
|
||||
func (ea *endpointAuthorizer) ModifyRequest(req *http.Request) error {
|
||||
pingPath := req.URL.Path
|
||||
if v2Root := strings.Index(req.URL.Path, "/v2/"); v2Root != -1 {
|
||||
pingPath = pingPath[:v2Root+4]
|
||||
} else if v1Root := strings.Index(req.URL.Path, "/v1/"); v1Root != -1 {
|
||||
pingPath = pingPath[:v1Root] + "/v2/"
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
ping := url.URL{
|
||||
Host: req.URL.Host,
|
||||
Scheme: req.URL.Scheme,
|
||||
Path: pingPath,
|
||||
}
|
||||
|
||||
challenges, err := ea.challenges.GetChallenges(ping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(challenges) > 0 {
|
||||
for _, handler := range ea.handlers {
|
||||
for _, c := range challenges {
|
||||
if c.Scheme != handler.Scheme() {
|
||||
continue
|
||||
}
|
||||
if err := handler.AuthorizeRequest(req, c.Parameters); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is the minimum duration a token can last (in seconds).
|
||||
// A token must not live less than 60 seconds because older versions
|
||||
// of the Docker client didn't read their expiration from the token
|
||||
// response and assumed 60 seconds. So to remain compatible with
|
||||
// those implementations, a token must live at least this long.
|
||||
const minimumTokenLifetimeSeconds = 60
|
||||
|
||||
// Private interface for time used by this package to enable tests to provide their own implementation.
|
||||
type clock interface {
|
||||
Now() time.Time
|
||||
}
|
||||
|
||||
type tokenHandler struct {
|
||||
header http.Header
|
||||
creds CredentialStore
|
||||
transport http.RoundTripper
|
||||
clock clock
|
||||
|
||||
offlineAccess bool
|
||||
forceOAuth bool
|
||||
clientID string
|
||||
scopes []Scope
|
||||
|
||||
tokenLock sync.Mutex
|
||||
tokenCache string
|
||||
tokenExpiration time.Time
|
||||
}
|
||||
|
||||
// Scope is a type which is serializable to a string
|
||||
// using the allow scope grammar.
|
||||
type Scope interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
// RepositoryScope represents a token scope for access
|
||||
// to a repository.
|
||||
type RepositoryScope struct {
|
||||
Repository string
|
||||
Class string
|
||||
Actions []string
|
||||
}
|
||||
|
||||
// String returns the string representation of the repository
|
||||
// using the scope grammar
|
||||
func (rs RepositoryScope) String() string {
|
||||
repoType := "repository"
|
||||
// Keep existing format for image class to maintain backwards compatibility
|
||||
// with authorization servers which do not support the expanded grammar.
|
||||
if rs.Class != "" && rs.Class != "image" {
|
||||
repoType = fmt.Sprintf("%s(%s)", repoType, rs.Class)
|
||||
}
|
||||
return fmt.Sprintf("%s:%s:%s", repoType, rs.Repository, strings.Join(rs.Actions, ","))
|
||||
}
|
||||
|
||||
// RegistryScope represents a token scope for access
|
||||
// to resources in the registry.
|
||||
type RegistryScope struct {
|
||||
Name string
|
||||
Actions []string
|
||||
}
|
||||
|
||||
// String returns the string representation of the user
|
||||
// using the scope grammar
|
||||
func (rs RegistryScope) String() string {
|
||||
return fmt.Sprintf("registry:%s:%s", rs.Name, strings.Join(rs.Actions, ","))
|
||||
}
|
||||
|
||||
// TokenHandlerOptions is used to configure a new token handler
|
||||
type TokenHandlerOptions struct {
|
||||
Transport http.RoundTripper
|
||||
Credentials CredentialStore
|
||||
|
||||
OfflineAccess bool
|
||||
ForceOAuth bool
|
||||
ClientID string
|
||||
Scopes []Scope
|
||||
}
|
||||
|
||||
// An implementation of clock for providing real time data.
|
||||
type realClock struct{}
|
||||
|
||||
// Now implements clock
|
||||
func (realClock) Now() time.Time { return time.Now() }
|
||||
|
||||
// NewTokenHandler creates a new AuthenicationHandler which supports
|
||||
// fetching tokens from a remote token server.
|
||||
func NewTokenHandler(transport http.RoundTripper, creds CredentialStore, scope string, actions ...string) AuthenticationHandler {
|
||||
// Create options...
|
||||
return NewTokenHandlerWithOptions(TokenHandlerOptions{
|
||||
Transport: transport,
|
||||
Credentials: creds,
|
||||
Scopes: []Scope{
|
||||
RepositoryScope{
|
||||
Repository: scope,
|
||||
Actions: actions,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// NewTokenHandlerWithOptions creates a new token handler using the provided
|
||||
// options structure.
|
||||
func NewTokenHandlerWithOptions(options TokenHandlerOptions) AuthenticationHandler {
|
||||
handler := &tokenHandler{
|
||||
transport: options.Transport,
|
||||
creds: options.Credentials,
|
||||
offlineAccess: options.OfflineAccess,
|
||||
forceOAuth: options.ForceOAuth,
|
||||
clientID: options.ClientID,
|
||||
scopes: options.Scopes,
|
||||
clock: realClock{},
|
||||
}
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
func (th *tokenHandler) client() *http.Client {
|
||||
return &http.Client{
|
||||
Transport: th.transport,
|
||||
Timeout: 15 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func (th *tokenHandler) Scheme() string {
|
||||
return "bearer"
|
||||
}
|
||||
|
||||
func (th *tokenHandler) AuthorizeRequest(req *http.Request, params map[string]string) error {
|
||||
var additionalScopes []string
|
||||
if fromParam := req.URL.Query().Get("from"); fromParam != "" {
|
||||
additionalScopes = append(additionalScopes, RepositoryScope{
|
||||
Repository: fromParam,
|
||||
Actions: []string{"pull"},
|
||||
}.String())
|
||||
}
|
||||
|
||||
token, err := th.getToken(params, additionalScopes...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (th *tokenHandler) getToken(params map[string]string, additionalScopes ...string) (string, error) {
|
||||
th.tokenLock.Lock()
|
||||
defer th.tokenLock.Unlock()
|
||||
scopes := make([]string, 0, len(th.scopes)+len(additionalScopes))
|
||||
for _, scope := range th.scopes {
|
||||
scopes = append(scopes, scope.String())
|
||||
}
|
||||
var addedScopes bool
|
||||
for _, scope := range additionalScopes {
|
||||
scopes = append(scopes, scope)
|
||||
addedScopes = true
|
||||
}
|
||||
|
||||
now := th.clock.Now()
|
||||
if now.After(th.tokenExpiration) || addedScopes {
|
||||
token, expiration, err := th.fetchToken(params, scopes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// do not update cache for added scope tokens
|
||||
if !addedScopes {
|
||||
th.tokenCache = token
|
||||
th.tokenExpiration = expiration
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
return th.tokenCache, nil
|
||||
}
|
||||
|
||||
type postTokenResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
IssuedAt time.Time `json:"issued_at"`
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
func (th *tokenHandler) fetchTokenWithOAuth(realm *url.URL, refreshToken, service string, scopes []string) (token string, expiration time.Time, err error) {
|
||||
form := url.Values{}
|
||||
form.Set("scope", strings.Join(scopes, " "))
|
||||
form.Set("service", service)
|
||||
|
||||
clientID := th.clientID
|
||||
if clientID == "" {
|
||||
// Use default client, this is a required field
|
||||
clientID = defaultClientID
|
||||
}
|
||||
form.Set("client_id", clientID)
|
||||
|
||||
if refreshToken != "" {
|
||||
form.Set("grant_type", "refresh_token")
|
||||
form.Set("refresh_token", refreshToken)
|
||||
} else if th.creds != nil {
|
||||
form.Set("grant_type", "password")
|
||||
username, password := th.creds.Basic(realm)
|
||||
form.Set("username", username)
|
||||
form.Set("password", password)
|
||||
|
||||
// attempt to get a refresh token
|
||||
form.Set("access_type", "offline")
|
||||
} else {
|
||||
// refuse to do oauth without a grant type
|
||||
return "", time.Time{}, fmt.Errorf("no supported grant type")
|
||||
}
|
||||
|
||||
resp, err := th.client().PostForm(realm.String(), form)
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if !client.SuccessStatus(resp.StatusCode) {
|
||||
err := client.HandleErrorResponse(resp)
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
|
||||
var tr postTokenResponse
|
||||
if err = decoder.Decode(&tr); err != nil {
|
||||
return "", time.Time{}, fmt.Errorf("unable to decode token response: %s", err)
|
||||
}
|
||||
|
||||
if tr.RefreshToken != "" && tr.RefreshToken != refreshToken {
|
||||
th.creds.SetRefreshToken(realm, service, tr.RefreshToken)
|
||||
}
|
||||
|
||||
if tr.ExpiresIn < minimumTokenLifetimeSeconds {
|
||||
// The default/minimum lifetime.
|
||||
tr.ExpiresIn = minimumTokenLifetimeSeconds
|
||||
logrus.Debugf("Increasing token expiration to: %d seconds", tr.ExpiresIn)
|
||||
}
|
||||
|
||||
if tr.IssuedAt.IsZero() {
|
||||
// issued_at is optional in the token response.
|
||||
tr.IssuedAt = th.clock.Now().UTC()
|
||||
}
|
||||
|
||||
return tr.AccessToken, tr.IssuedAt.Add(time.Duration(tr.ExpiresIn) * time.Second), nil
|
||||
}
|
||||
|
||||
type getTokenResponse struct {
|
||||
Token string `json:"token"`
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
IssuedAt time.Time `json:"issued_at"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
func (th *tokenHandler) fetchTokenWithBasicAuth(realm *url.URL, service string, scopes []string) (token string, expiration time.Time, err error) {
|
||||
|
||||
req, err := http.NewRequest("GET", realm.String(), nil)
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
|
||||
reqParams := req.URL.Query()
|
||||
|
||||
if service != "" {
|
||||
reqParams.Add("service", service)
|
||||
}
|
||||
|
||||
for _, scope := range scopes {
|
||||
reqParams.Add("scope", scope)
|
||||
}
|
||||
|
||||
if th.offlineAccess {
|
||||
reqParams.Add("offline_token", "true")
|
||||
clientID := th.clientID
|
||||
if clientID == "" {
|
||||
clientID = defaultClientID
|
||||
}
|
||||
reqParams.Add("client_id", clientID)
|
||||
}
|
||||
|
||||
if th.creds != nil {
|
||||
username, password := th.creds.Basic(realm)
|
||||
if username != "" && password != "" {
|
||||
reqParams.Add("account", username)
|
||||
req.SetBasicAuth(username, password)
|
||||
}
|
||||
}
|
||||
|
||||
req.URL.RawQuery = reqParams.Encode()
|
||||
|
||||
resp, err := th.client().Do(req)
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if !client.SuccessStatus(resp.StatusCode) {
|
||||
err := client.HandleErrorResponse(resp)
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
|
||||
var tr getTokenResponse
|
||||
if err = decoder.Decode(&tr); err != nil {
|
||||
return "", time.Time{}, fmt.Errorf("unable to decode token response: %s", err)
|
||||
}
|
||||
|
||||
if tr.RefreshToken != "" && th.creds != nil {
|
||||
th.creds.SetRefreshToken(realm, service, tr.RefreshToken)
|
||||
}
|
||||
|
||||
// `access_token` is equivalent to `token` and if both are specified
|
||||
// the choice is undefined. Canonicalize `access_token` by sticking
|
||||
// things in `token`.
|
||||
if tr.AccessToken != "" {
|
||||
tr.Token = tr.AccessToken
|
||||
}
|
||||
|
||||
if tr.Token == "" {
|
||||
return "", time.Time{}, ErrNoToken
|
||||
}
|
||||
|
||||
if tr.ExpiresIn < minimumTokenLifetimeSeconds {
|
||||
// The default/minimum lifetime.
|
||||
tr.ExpiresIn = minimumTokenLifetimeSeconds
|
||||
logrus.Debugf("Increasing token expiration to: %d seconds", tr.ExpiresIn)
|
||||
}
|
||||
|
||||
if tr.IssuedAt.IsZero() {
|
||||
// issued_at is optional in the token response.
|
||||
tr.IssuedAt = th.clock.Now().UTC()
|
||||
}
|
||||
|
||||
return tr.Token, tr.IssuedAt.Add(time.Duration(tr.ExpiresIn) * time.Second), nil
|
||||
}
|
||||
|
||||
func (th *tokenHandler) fetchToken(params map[string]string, scopes []string) (token string, expiration time.Time, err error) {
|
||||
realm, ok := params["realm"]
|
||||
if !ok {
|
||||
return "", time.Time{}, errors.New("no realm specified for token auth challenge")
|
||||
}
|
||||
|
||||
// TODO(dmcgowan): Handle empty scheme and relative realm
|
||||
realmURL, err := url.Parse(realm)
|
||||
if err != nil {
|
||||
return "", time.Time{}, fmt.Errorf("invalid token auth challenge realm: %s", err)
|
||||
}
|
||||
|
||||
service := params["service"]
|
||||
|
||||
var refreshToken string
|
||||
|
||||
if th.creds != nil {
|
||||
refreshToken = th.creds.RefreshToken(realmURL, service)
|
||||
}
|
||||
|
||||
if refreshToken != "" || th.forceOAuth {
|
||||
return th.fetchTokenWithOAuth(realmURL, refreshToken, service, scopes)
|
||||
}
|
||||
|
||||
return th.fetchTokenWithBasicAuth(realmURL, service, scopes)
|
||||
}
|
||||
|
||||
type basicHandler struct {
|
||||
creds CredentialStore
|
||||
}
|
||||
|
||||
// NewBasicHandler creaters a new authentiation handler which adds
|
||||
// basic authentication credentials to a request.
|
||||
func NewBasicHandler(creds CredentialStore) AuthenticationHandler {
|
||||
return &basicHandler{
|
||||
creds: creds,
|
||||
}
|
||||
}
|
||||
|
||||
func (*basicHandler) Scheme() string {
|
||||
return "basic"
|
||||
}
|
||||
|
||||
func (bh *basicHandler) AuthorizeRequest(req *http.Request, params map[string]string) error {
|
||||
if bh.creds != nil {
|
||||
username, password := bh.creds.Basic(req.URL)
|
||||
if username != "" && password != "" {
|
||||
req.SetBasicAuth(username, password)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrNoBasicAuthCredentials
|
||||
}
|
866
gateway/vendor/github.com/docker/distribution/registry/client/auth/session_test.go
generated
vendored
Normal file
866
gateway/vendor/github.com/docker/distribution/registry/client/auth/session_test.go
generated
vendored
Normal file
@ -0,0 +1,866 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/registry/client/auth/challenge"
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
"github.com/docker/distribution/testutil"
|
||||
)
|
||||
|
||||
// An implementation of clock for providing fake time data.
|
||||
type fakeClock struct {
|
||||
current time.Time
|
||||
}
|
||||
|
||||
// Now implements clock
|
||||
func (fc *fakeClock) Now() time.Time { return fc.current }
|
||||
|
||||
func testServer(rrm testutil.RequestResponseMap) (string, func()) {
|
||||
h := testutil.NewHandler(rrm)
|
||||
s := httptest.NewServer(h)
|
||||
return s.URL, s.Close
|
||||
}
|
||||
|
||||
type testAuthenticationWrapper struct {
|
||||
headers http.Header
|
||||
authCheck func(string) bool
|
||||
next http.Handler
|
||||
}
|
||||
|
||||
func (w *testAuthenticationWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
auth := r.Header.Get("Authorization")
|
||||
if auth == "" || !w.authCheck(auth) {
|
||||
h := rw.Header()
|
||||
for k, values := range w.headers {
|
||||
h[k] = values
|
||||
}
|
||||
rw.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
w.next.ServeHTTP(rw, r)
|
||||
}
|
||||
|
||||
func testServerWithAuth(rrm testutil.RequestResponseMap, authenticate string, authCheck func(string) bool) (string, func()) {
|
||||
h := testutil.NewHandler(rrm)
|
||||
wrapper := &testAuthenticationWrapper{
|
||||
|
||||
headers: http.Header(map[string][]string{
|
||||
"X-API-Version": {"registry/2.0"},
|
||||
"X-Multi-API-Version": {"registry/2.0", "registry/2.1", "trust/1.0"},
|
||||
"WWW-Authenticate": {authenticate},
|
||||
}),
|
||||
authCheck: authCheck,
|
||||
next: h,
|
||||
}
|
||||
|
||||
s := httptest.NewServer(wrapper)
|
||||
return s.URL, s.Close
|
||||
}
|
||||
|
||||
// ping pings the provided endpoint to determine its required authorization challenges.
|
||||
// If a version header is provided, the versions will be returned.
|
||||
func ping(manager challenge.Manager, endpoint, versionHeader string) ([]APIVersion, error) {
|
||||
resp, err := http.Get(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err := manager.AddResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return APIVersions(resp, versionHeader), err
|
||||
}
|
||||
|
||||
type testCredentialStore struct {
|
||||
username string
|
||||
password string
|
||||
refreshTokens map[string]string
|
||||
}
|
||||
|
||||
func (tcs *testCredentialStore) Basic(*url.URL) (string, string) {
|
||||
return tcs.username, tcs.password
|
||||
}
|
||||
|
||||
func (tcs *testCredentialStore) RefreshToken(u *url.URL, service string) string {
|
||||
return tcs.refreshTokens[service]
|
||||
}
|
||||
|
||||
func (tcs *testCredentialStore) SetRefreshToken(u *url.URL, service string, token string) {
|
||||
if tcs.refreshTokens != nil {
|
||||
tcs.refreshTokens[service] = token
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndpointAuthorizeToken(t *testing.T) {
|
||||
service := "localhost.localdomain"
|
||||
repo1 := "some/registry"
|
||||
repo2 := "other/registry"
|
||||
scope1 := fmt.Sprintf("repository:%s:pull,push", repo1)
|
||||
scope2 := fmt.Sprintf("repository:%s:pull,push", repo2)
|
||||
tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: fmt.Sprintf("/token?scope=%s&service=%s", url.QueryEscape(scope1), service),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: []byte(`{"token":"statictoken"}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: fmt.Sprintf("/token?scope=%s&service=%s", url.QueryEscape(scope2), service),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: []byte(`{"token":"badtoken"}`),
|
||||
},
|
||||
},
|
||||
})
|
||||
te, tc := testServer(tokenMap)
|
||||
defer tc()
|
||||
|
||||
m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/hello",
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
authenicate := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
|
||||
validCheck := func(a string) bool {
|
||||
return a == "Bearer statictoken"
|
||||
}
|
||||
e, c := testServerWithAuth(m, authenicate, validCheck)
|
||||
defer c()
|
||||
|
||||
challengeManager1 := challenge.NewSimpleManager()
|
||||
versions, err := ping(challengeManager1, e+"/v2/", "x-api-version")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(versions) != 1 {
|
||||
t.Fatalf("Unexpected version count: %d, expected 1", len(versions))
|
||||
}
|
||||
if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
|
||||
t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
|
||||
}
|
||||
transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager1, NewTokenHandler(nil, nil, repo1, "pull", "push")))
|
||||
client := &http.Client{Transport: transport1}
|
||||
|
||||
req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Error sending get request: %s", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
|
||||
}
|
||||
|
||||
e2, c2 := testServerWithAuth(m, authenicate, validCheck)
|
||||
defer c2()
|
||||
|
||||
challengeManager2 := challenge.NewSimpleManager()
|
||||
versions, err = ping(challengeManager2, e2+"/v2/", "x-multi-api-version")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(versions) != 3 {
|
||||
t.Fatalf("Unexpected version count: %d, expected 3", len(versions))
|
||||
}
|
||||
if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
|
||||
t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
|
||||
}
|
||||
if check := (APIVersion{Type: "registry", Version: "2.1"}); versions[1] != check {
|
||||
t.Fatalf("Unexpected api version: %#v, expected %#v", versions[1], check)
|
||||
}
|
||||
if check := (APIVersion{Type: "trust", Version: "1.0"}); versions[2] != check {
|
||||
t.Fatalf("Unexpected api version: %#v, expected %#v", versions[2], check)
|
||||
}
|
||||
transport2 := transport.NewTransport(nil, NewAuthorizer(challengeManager2, NewTokenHandler(nil, nil, repo2, "pull", "push")))
|
||||
client2 := &http.Client{Transport: transport2}
|
||||
|
||||
req, _ = http.NewRequest("GET", e2+"/v2/hello", nil)
|
||||
resp, err = client2.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Error sending get request: %s", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusUnauthorized {
|
||||
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndpointAuthorizeRefreshToken(t *testing.T) {
|
||||
service := "localhost.localdomain"
|
||||
repo1 := "some/registry"
|
||||
repo2 := "other/registry"
|
||||
scope1 := fmt.Sprintf("repository:%s:pull,push", repo1)
|
||||
scope2 := fmt.Sprintf("repository:%s:pull,push", repo2)
|
||||
refreshToken1 := "0123456790abcdef"
|
||||
refreshToken2 := "0123456790fedcba"
|
||||
tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "POST",
|
||||
Route: "/token",
|
||||
Body: []byte(fmt.Sprintf("client_id=registry-client&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken1, url.QueryEscape(scope1), service)),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: []byte(fmt.Sprintf(`{"access_token":"statictoken","refresh_token":"%s"}`, refreshToken1)),
|
||||
},
|
||||
},
|
||||
{
|
||||
// In the future this test may fail and require using basic auth to get a different refresh token
|
||||
Request: testutil.Request{
|
||||
Method: "POST",
|
||||
Route: "/token",
|
||||
Body: []byte(fmt.Sprintf("client_id=registry-client&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken1, url.QueryEscape(scope2), service)),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: []byte(fmt.Sprintf(`{"access_token":"statictoken","refresh_token":"%s"}`, refreshToken2)),
|
||||
},
|
||||
},
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "POST",
|
||||
Route: "/token",
|
||||
Body: []byte(fmt.Sprintf("client_id=registry-client&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken2, url.QueryEscape(scope2), service)),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: []byte(`{"access_token":"badtoken","refresh_token":"%s"}`),
|
||||
},
|
||||
},
|
||||
})
|
||||
te, tc := testServer(tokenMap)
|
||||
defer tc()
|
||||
|
||||
m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/hello",
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
authenicate := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
|
||||
validCheck := func(a string) bool {
|
||||
return a == "Bearer statictoken"
|
||||
}
|
||||
e, c := testServerWithAuth(m, authenicate, validCheck)
|
||||
defer c()
|
||||
|
||||
challengeManager1 := challenge.NewSimpleManager()
|
||||
versions, err := ping(challengeManager1, e+"/v2/", "x-api-version")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(versions) != 1 {
|
||||
t.Fatalf("Unexpected version count: %d, expected 1", len(versions))
|
||||
}
|
||||
if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
|
||||
t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
|
||||
}
|
||||
creds := &testCredentialStore{
|
||||
refreshTokens: map[string]string{
|
||||
service: refreshToken1,
|
||||
},
|
||||
}
|
||||
transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager1, NewTokenHandler(nil, creds, repo1, "pull", "push")))
|
||||
client := &http.Client{Transport: transport1}
|
||||
|
||||
req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Error sending get request: %s", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
|
||||
}
|
||||
|
||||
// Try with refresh token setting
|
||||
e2, c2 := testServerWithAuth(m, authenicate, validCheck)
|
||||
defer c2()
|
||||
|
||||
challengeManager2 := challenge.NewSimpleManager()
|
||||
versions, err = ping(challengeManager2, e2+"/v2/", "x-api-version")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(versions) != 1 {
|
||||
t.Fatalf("Unexpected version count: %d, expected 1", len(versions))
|
||||
}
|
||||
if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
|
||||
t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
|
||||
}
|
||||
|
||||
transport2 := transport.NewTransport(nil, NewAuthorizer(challengeManager2, NewTokenHandler(nil, creds, repo2, "pull", "push")))
|
||||
client2 := &http.Client{Transport: transport2}
|
||||
|
||||
req, _ = http.NewRequest("GET", e2+"/v2/hello", nil)
|
||||
resp, err = client2.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Error sending get request: %s", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
if creds.refreshTokens[service] != refreshToken2 {
|
||||
t.Fatalf("Refresh token not set after change")
|
||||
}
|
||||
|
||||
// Try with bad token
|
||||
e3, c3 := testServerWithAuth(m, authenicate, validCheck)
|
||||
defer c3()
|
||||
|
||||
challengeManager3 := challenge.NewSimpleManager()
|
||||
versions, err = ping(challengeManager3, e3+"/v2/", "x-api-version")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
|
||||
t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
|
||||
}
|
||||
|
||||
transport3 := transport.NewTransport(nil, NewAuthorizer(challengeManager3, NewTokenHandler(nil, creds, repo2, "pull", "push")))
|
||||
client3 := &http.Client{Transport: transport3}
|
||||
|
||||
req, _ = http.NewRequest("GET", e3+"/v2/hello", nil)
|
||||
resp, err = client3.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Error sending get request: %s", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusUnauthorized {
|
||||
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndpointAuthorizeV2RefreshToken(t *testing.T) {
|
||||
service := "localhost.localdomain"
|
||||
scope1 := "registry:catalog:search"
|
||||
refreshToken1 := "0123456790abcdef"
|
||||
tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "POST",
|
||||
Route: "/token",
|
||||
Body: []byte(fmt.Sprintf("client_id=registry-client&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken1, url.QueryEscape(scope1), service)),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: []byte(fmt.Sprintf(`{"access_token":"statictoken","refresh_token":"%s"}`, refreshToken1)),
|
||||
},
|
||||
},
|
||||
})
|
||||
te, tc := testServer(tokenMap)
|
||||
defer tc()
|
||||
|
||||
m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v1/search",
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
authenicate := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
|
||||
validCheck := func(a string) bool {
|
||||
return a == "Bearer statictoken"
|
||||
}
|
||||
e, c := testServerWithAuth(m, authenicate, validCheck)
|
||||
defer c()
|
||||
|
||||
challengeManager1 := challenge.NewSimpleManager()
|
||||
versions, err := ping(challengeManager1, e+"/v2/", "x-api-version")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(versions) != 1 {
|
||||
t.Fatalf("Unexpected version count: %d, expected 1", len(versions))
|
||||
}
|
||||
if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
|
||||
t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
|
||||
}
|
||||
tho := TokenHandlerOptions{
|
||||
Credentials: &testCredentialStore{
|
||||
refreshTokens: map[string]string{
|
||||
service: refreshToken1,
|
||||
},
|
||||
},
|
||||
Scopes: []Scope{
|
||||
RegistryScope{
|
||||
Name: "catalog",
|
||||
Actions: []string{"search"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager1, NewTokenHandlerWithOptions(tho)))
|
||||
client := &http.Client{Transport: transport1}
|
||||
|
||||
req, _ := http.NewRequest("GET", e+"/v1/search", nil)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Error sending get request: %s", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
|
||||
}
|
||||
}
|
||||
|
||||
func basicAuth(username, password string) string {
|
||||
auth := username + ":" + password
|
||||
return base64.StdEncoding.EncodeToString([]byte(auth))
|
||||
}
|
||||
|
||||
func TestEndpointAuthorizeTokenBasic(t *testing.T) {
|
||||
service := "localhost.localdomain"
|
||||
repo := "some/fun/registry"
|
||||
scope := fmt.Sprintf("repository:%s:pull,push", repo)
|
||||
username := "tokenuser"
|
||||
password := "superSecretPa$$word"
|
||||
|
||||
tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: []byte(`{"access_token":"statictoken"}`),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
authenicate1 := fmt.Sprintf("Basic realm=localhost")
|
||||
basicCheck := func(a string) bool {
|
||||
return a == fmt.Sprintf("Basic %s", basicAuth(username, password))
|
||||
}
|
||||
te, tc := testServerWithAuth(tokenMap, authenicate1, basicCheck)
|
||||
defer tc()
|
||||
|
||||
m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/hello",
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
authenicate2 := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
|
||||
bearerCheck := func(a string) bool {
|
||||
return a == "Bearer statictoken"
|
||||
}
|
||||
e, c := testServerWithAuth(m, authenicate2, bearerCheck)
|
||||
defer c()
|
||||
|
||||
creds := &testCredentialStore{
|
||||
username: username,
|
||||
password: password,
|
||||
}
|
||||
|
||||
challengeManager := challenge.NewSimpleManager()
|
||||
_, err := ping(challengeManager, e+"/v2/", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager, NewTokenHandler(nil, creds, repo, "pull", "push"), NewBasicHandler(creds)))
|
||||
client := &http.Client{Transport: transport1}
|
||||
|
||||
req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Error sending get request: %s", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndpointAuthorizeTokenBasicWithExpiresIn(t *testing.T) {
|
||||
service := "localhost.localdomain"
|
||||
repo := "some/fun/registry"
|
||||
scope := fmt.Sprintf("repository:%s:pull,push", repo)
|
||||
username := "tokenuser"
|
||||
password := "superSecretPa$$word"
|
||||
|
||||
tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: []byte(`{"token":"statictoken", "expires_in": 3001}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: []byte(`{"access_token":"statictoken", "expires_in": 3001}`),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
authenicate1 := fmt.Sprintf("Basic realm=localhost")
|
||||
tokenExchanges := 0
|
||||
basicCheck := func(a string) bool {
|
||||
tokenExchanges = tokenExchanges + 1
|
||||
return a == fmt.Sprintf("Basic %s", basicAuth(username, password))
|
||||
}
|
||||
te, tc := testServerWithAuth(tokenMap, authenicate1, basicCheck)
|
||||
defer tc()
|
||||
|
||||
m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/hello",
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
},
|
||||
},
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/hello",
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
},
|
||||
},
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/hello",
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
},
|
||||
},
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/hello",
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
},
|
||||
},
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/hello",
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
authenicate2 := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
|
||||
bearerCheck := func(a string) bool {
|
||||
return a == "Bearer statictoken"
|
||||
}
|
||||
e, c := testServerWithAuth(m, authenicate2, bearerCheck)
|
||||
defer c()
|
||||
|
||||
creds := &testCredentialStore{
|
||||
username: username,
|
||||
password: password,
|
||||
}
|
||||
|
||||
challengeManager := challenge.NewSimpleManager()
|
||||
_, err := ping(challengeManager, e+"/v2/", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
clock := &fakeClock{current: time.Now()}
|
||||
options := TokenHandlerOptions{
|
||||
Transport: nil,
|
||||
Credentials: creds,
|
||||
Scopes: []Scope{
|
||||
RepositoryScope{
|
||||
Repository: repo,
|
||||
Actions: []string{"pull", "push"},
|
||||
},
|
||||
},
|
||||
}
|
||||
tHandler := NewTokenHandlerWithOptions(options)
|
||||
tHandler.(*tokenHandler).clock = clock
|
||||
transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager, tHandler, NewBasicHandler(creds)))
|
||||
client := &http.Client{Transport: transport1}
|
||||
|
||||
// First call should result in a token exchange
|
||||
// Subsequent calls should recycle the token from the first request, until the expiration has lapsed.
|
||||
timeIncrement := 1000 * time.Second
|
||||
for i := 0; i < 4; i++ {
|
||||
req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Error sending get request: %s", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
|
||||
}
|
||||
if tokenExchanges != 1 {
|
||||
t.Fatalf("Unexpected number of token exchanges, want: 1, got %d (iteration: %d)", tokenExchanges, i)
|
||||
}
|
||||
clock.current = clock.current.Add(timeIncrement)
|
||||
}
|
||||
|
||||
// After we've exceeded the expiration, we should see a second token exchange.
|
||||
req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Error sending get request: %s", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
|
||||
}
|
||||
if tokenExchanges != 2 {
|
||||
t.Fatalf("Unexpected number of token exchanges, want: 2, got %d", tokenExchanges)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndpointAuthorizeTokenBasicWithExpiresInAndIssuedAt(t *testing.T) {
|
||||
service := "localhost.localdomain"
|
||||
repo := "some/fun/registry"
|
||||
scope := fmt.Sprintf("repository:%s:pull,push", repo)
|
||||
username := "tokenuser"
|
||||
password := "superSecretPa$$word"
|
||||
|
||||
// This test sets things up such that the token was issued one increment
|
||||
// earlier than its sibling in TestEndpointAuthorizeTokenBasicWithExpiresIn.
|
||||
// This will mean that the token expires after 3 increments instead of 4.
|
||||
clock := &fakeClock{current: time.Now()}
|
||||
timeIncrement := 1000 * time.Second
|
||||
firstIssuedAt := clock.Now()
|
||||
clock.current = clock.current.Add(timeIncrement)
|
||||
secondIssuedAt := clock.current.Add(2 * timeIncrement)
|
||||
tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: []byte(`{"token":"statictoken", "issued_at": "` + firstIssuedAt.Format(time.RFC3339Nano) + `", "expires_in": 3001}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: []byte(`{"access_token":"statictoken", "issued_at": "` + secondIssuedAt.Format(time.RFC3339Nano) + `", "expires_in": 3001}`),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
authenicate1 := fmt.Sprintf("Basic realm=localhost")
|
||||
tokenExchanges := 0
|
||||
basicCheck := func(a string) bool {
|
||||
tokenExchanges = tokenExchanges + 1
|
||||
return a == fmt.Sprintf("Basic %s", basicAuth(username, password))
|
||||
}
|
||||
te, tc := testServerWithAuth(tokenMap, authenicate1, basicCheck)
|
||||
defer tc()
|
||||
|
||||
m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/hello",
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
},
|
||||
},
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/hello",
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
},
|
||||
},
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/hello",
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
},
|
||||
},
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/hello",
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
authenicate2 := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
|
||||
bearerCheck := func(a string) bool {
|
||||
return a == "Bearer statictoken"
|
||||
}
|
||||
e, c := testServerWithAuth(m, authenicate2, bearerCheck)
|
||||
defer c()
|
||||
|
||||
creds := &testCredentialStore{
|
||||
username: username,
|
||||
password: password,
|
||||
}
|
||||
|
||||
challengeManager := challenge.NewSimpleManager()
|
||||
_, err := ping(challengeManager, e+"/v2/", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
options := TokenHandlerOptions{
|
||||
Transport: nil,
|
||||
Credentials: creds,
|
||||
Scopes: []Scope{
|
||||
RepositoryScope{
|
||||
Repository: repo,
|
||||
Actions: []string{"pull", "push"},
|
||||
},
|
||||
},
|
||||
}
|
||||
tHandler := NewTokenHandlerWithOptions(options)
|
||||
tHandler.(*tokenHandler).clock = clock
|
||||
transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager, tHandler, NewBasicHandler(creds)))
|
||||
client := &http.Client{Transport: transport1}
|
||||
|
||||
// First call should result in a token exchange
|
||||
// Subsequent calls should recycle the token from the first request, until the expiration has lapsed.
|
||||
// We shaved one increment off of the equivalent logic in TestEndpointAuthorizeTokenBasicWithExpiresIn
|
||||
// so this loop should have one fewer iteration.
|
||||
for i := 0; i < 3; i++ {
|
||||
req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Error sending get request: %s", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
|
||||
}
|
||||
if tokenExchanges != 1 {
|
||||
t.Fatalf("Unexpected number of token exchanges, want: 1, got %d (iteration: %d)", tokenExchanges, i)
|
||||
}
|
||||
clock.current = clock.current.Add(timeIncrement)
|
||||
}
|
||||
|
||||
// After we've exceeded the expiration, we should see a second token exchange.
|
||||
req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Error sending get request: %s", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
|
||||
}
|
||||
if tokenExchanges != 2 {
|
||||
t.Fatalf("Unexpected number of token exchanges, want: 2, got %d", tokenExchanges)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndpointAuthorizeBasic(t *testing.T) {
|
||||
m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
||||
{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/hello",
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
username := "user1"
|
||||
password := "funSecretPa$$word"
|
||||
authenicate := fmt.Sprintf("Basic realm=localhost")
|
||||
validCheck := func(a string) bool {
|
||||
return a == fmt.Sprintf("Basic %s", basicAuth(username, password))
|
||||
}
|
||||
e, c := testServerWithAuth(m, authenicate, validCheck)
|
||||
defer c()
|
||||
creds := &testCredentialStore{
|
||||
username: username,
|
||||
password: password,
|
||||
}
|
||||
|
||||
challengeManager := challenge.NewSimpleManager()
|
||||
_, err := ping(challengeManager, e+"/v2/", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager, NewBasicHandler(creds)))
|
||||
client := &http.Client{Transport: transport1}
|
||||
|
||||
req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Error sending get request: %s", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user