...

Source file src/github.com/docker/distribution/registry/client/auth/challenge/authchallenge.go

Documentation: github.com/docker/distribution/registry/client/auth/challenge

     1  package challenge
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"net/url"
     7  	"strings"
     8  	"sync"
     9  )
    10  
    11  // Challenge carries information from a WWW-Authenticate response header.
    12  // See RFC 2617.
    13  type Challenge struct {
    14  	// Scheme is the auth-scheme according to RFC 2617
    15  	Scheme string
    16  
    17  	// Parameters are the auth-params according to RFC 2617
    18  	Parameters map[string]string
    19  }
    20  
    21  // Manager manages the challenges for endpoints.
    22  // The challenges are pulled out of HTTP responses. Only
    23  // responses which expect challenges should be added to
    24  // the manager, since a non-unauthorized request will be
    25  // viewed as not requiring challenges.
    26  type Manager interface {
    27  	// GetChallenges returns the challenges for the given
    28  	// endpoint URL.
    29  	GetChallenges(endpoint url.URL) ([]Challenge, error)
    30  
    31  	// AddResponse adds the response to the challenge
    32  	// manager. The challenges will be parsed out of
    33  	// the WWW-Authenicate headers and added to the
    34  	// URL which was produced the response. If the
    35  	// response was authorized, any challenges for the
    36  	// endpoint will be cleared.
    37  	AddResponse(resp *http.Response) error
    38  }
    39  
    40  // NewSimpleManager returns an instance of
    41  // Manger which only maps endpoints to challenges
    42  // based on the responses which have been added the
    43  // manager. The simple manager will make no attempt to
    44  // perform requests on the endpoints or cache the responses
    45  // to a backend.
    46  func NewSimpleManager() Manager {
    47  	return &simpleManager{
    48  		Challenges: make(map[string][]Challenge),
    49  	}
    50  }
    51  
    52  type simpleManager struct {
    53  	sync.RWMutex
    54  	Challenges map[string][]Challenge
    55  }
    56  
    57  func normalizeURL(endpoint *url.URL) {
    58  	endpoint.Host = strings.ToLower(endpoint.Host)
    59  	endpoint.Host = canonicalAddr(endpoint)
    60  }
    61  
    62  func (m *simpleManager) GetChallenges(endpoint url.URL) ([]Challenge, error) {
    63  	normalizeURL(&endpoint)
    64  
    65  	m.RLock()
    66  	defer m.RUnlock()
    67  	challenges := m.Challenges[endpoint.String()]
    68  	return challenges, nil
    69  }
    70  
    71  func (m *simpleManager) AddResponse(resp *http.Response) error {
    72  	challenges := ResponseChallenges(resp)
    73  	if resp.Request == nil {
    74  		return fmt.Errorf("missing request reference")
    75  	}
    76  	urlCopy := url.URL{
    77  		Path:   resp.Request.URL.Path,
    78  		Host:   resp.Request.URL.Host,
    79  		Scheme: resp.Request.URL.Scheme,
    80  	}
    81  	normalizeURL(&urlCopy)
    82  
    83  	m.Lock()
    84  	defer m.Unlock()
    85  	m.Challenges[urlCopy.String()] = challenges
    86  	return nil
    87  }
    88  
    89  // Octet types from RFC 2616.
    90  type octetType byte
    91  
    92  var octetTypes [256]octetType
    93  
    94  const (
    95  	isToken octetType = 1 << iota
    96  	isSpace
    97  )
    98  
    99  func init() {
   100  	// OCTET      = <any 8-bit sequence of data>
   101  	// CHAR       = <any US-ASCII character (octets 0 - 127)>
   102  	// CTL        = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
   103  	// CR         = <US-ASCII CR, carriage return (13)>
   104  	// LF         = <US-ASCII LF, linefeed (10)>
   105  	// SP         = <US-ASCII SP, space (32)>
   106  	// HT         = <US-ASCII HT, horizontal-tab (9)>
   107  	// <">        = <US-ASCII double-quote mark (34)>
   108  	// CRLF       = CR LF
   109  	// LWS        = [CRLF] 1*( SP | HT )
   110  	// TEXT       = <any OCTET except CTLs, but including LWS>
   111  	// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
   112  	//              | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
   113  	// token      = 1*<any CHAR except CTLs or separators>
   114  	// qdtext     = <any TEXT except <">>
   115  
   116  	for c := 0; c < 256; c++ {
   117  		var t octetType
   118  		isCtl := c <= 31 || c == 127
   119  		isChar := 0 <= c && c <= 127
   120  		isSeparator := strings.ContainsRune(" \t\"(),/:;<=>?@[]\\{}", rune(c))
   121  		if strings.ContainsRune(" \t\r\n", rune(c)) {
   122  			t |= isSpace
   123  		}
   124  		if isChar && !isCtl && !isSeparator {
   125  			t |= isToken
   126  		}
   127  		octetTypes[c] = t
   128  	}
   129  }
   130  
   131  // ResponseChallenges returns a list of authorization challenges
   132  // for the given http Response. Challenges are only checked if
   133  // the response status code was a 401.
   134  func ResponseChallenges(resp *http.Response) []Challenge {
   135  	if resp.StatusCode == http.StatusUnauthorized {
   136  		// Parse the WWW-Authenticate Header and store the challenges
   137  		// on this endpoint object.
   138  		return parseAuthHeader(resp.Header)
   139  	}
   140  
   141  	return nil
   142  }
   143  
   144  func parseAuthHeader(header http.Header) []Challenge {
   145  	challenges := []Challenge{}
   146  	for _, h := range header[http.CanonicalHeaderKey("WWW-Authenticate")] {
   147  		v, p := parseValueAndParams(h)
   148  		if v != "" {
   149  			challenges = append(challenges, Challenge{Scheme: v, Parameters: p})
   150  		}
   151  	}
   152  	return challenges
   153  }
   154  
   155  func parseValueAndParams(header string) (value string, params map[string]string) {
   156  	params = make(map[string]string)
   157  	value, s := expectToken(header)
   158  	if value == "" {
   159  		return
   160  	}
   161  	value = strings.ToLower(value)
   162  	s = "," + skipSpace(s)
   163  	for strings.HasPrefix(s, ",") {
   164  		var pkey string
   165  		pkey, s = expectToken(skipSpace(s[1:]))
   166  		if pkey == "" {
   167  			return
   168  		}
   169  		if !strings.HasPrefix(s, "=") {
   170  			return
   171  		}
   172  		var pvalue string
   173  		pvalue, s = expectTokenOrQuoted(s[1:])
   174  		if pvalue == "" {
   175  			return
   176  		}
   177  		pkey = strings.ToLower(pkey)
   178  		params[pkey] = pvalue
   179  		s = skipSpace(s)
   180  	}
   181  	return
   182  }
   183  
   184  func skipSpace(s string) (rest string) {
   185  	i := 0
   186  	for ; i < len(s); i++ {
   187  		if octetTypes[s[i]]&isSpace == 0 {
   188  			break
   189  		}
   190  	}
   191  	return s[i:]
   192  }
   193  
   194  func expectToken(s string) (token, rest string) {
   195  	i := 0
   196  	for ; i < len(s); i++ {
   197  		if octetTypes[s[i]]&isToken == 0 {
   198  			break
   199  		}
   200  	}
   201  	return s[:i], s[i:]
   202  }
   203  
   204  func expectTokenOrQuoted(s string) (value string, rest string) {
   205  	if !strings.HasPrefix(s, "\"") {
   206  		return expectToken(s)
   207  	}
   208  	s = s[1:]
   209  	for i := 0; i < len(s); i++ {
   210  		switch s[i] {
   211  		case '"':
   212  			return s[:i], s[i+1:]
   213  		case '\\':
   214  			p := make([]byte, len(s)-1)
   215  			j := copy(p, s[:i])
   216  			escape := true
   217  			for i = i + 1; i < len(s); i++ {
   218  				b := s[i]
   219  				switch {
   220  				case escape:
   221  					escape = false
   222  					p[j] = b
   223  					j++
   224  				case b == '\\':
   225  					escape = true
   226  				case b == '"':
   227  					return string(p[:j]), s[i+1:]
   228  				default:
   229  					p[j] = b
   230  					j++
   231  				}
   232  			}
   233  			return "", ""
   234  		}
   235  	}
   236  	return "", ""
   237  }
   238  

View as plain text