41e20cecb9
summary of changes: registry/auth.go - More logging around the login functions - split Login() out to handle different code paths for v1 (unchanged logic) and v2 (does not currently do account creation) - handling for either basic or token based login attempts registry/authchallenge.go - New File - credit to Brian Bland <brian.bland@docker.com> (github: BrianBland) - handles parsing of WWW-Authenticate response headers registry/endpoint.go - EVEN MOAR LOGGING - Many edits throught to make the coad less dense. Sparse code is more readable code. - slit Ping() out to handle different code paths for v1 (unchanged logic) and v2. - Updated Endpoint struct type to include an entry for authorization challenges discovered during ping of a v2 registry. - If registry endpoint version is unknown, v2 code path is first attempted, then fallback to v1 upon failure. registry/service.go - STILL MOAR LOGGING - simplified the logic around starting the 'auth' job. registry/session.go - updated use of a registry.Endpoint struct field. registry/token.go - New File - Handles getting token from the parameters of a token auth challenge. - Modified from function written by Brian Bland (see above credit). registry/types.go - Removed 'DefaultAPIVersion' in lieu of 'APIVersionUnknown = 0'` Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)
150 lines
3.3 KiB
Go
150 lines
3.3 KiB
Go
package registry
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// Octet types from RFC 2616.
|
|
type octetType byte
|
|
|
|
// AuthorizationChallenge carries information
|
|
// from a WWW-Authenticate response header.
|
|
type AuthorizationChallenge struct {
|
|
Scheme string
|
|
Parameters map[string]string
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
func parseAuthHeader(header http.Header) []*AuthorizationChallenge {
|
|
var challenges []*AuthorizationChallenge
|
|
for _, h := range header[http.CanonicalHeaderKey("WWW-Authenticate")] {
|
|
v, p := parseValueAndParams(h)
|
|
if v != "" {
|
|
challenges = append(challenges, &AuthorizationChallenge{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 + i; 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 "", ""
|
|
}
|