authchallenge.go 5.7 KB

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