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