authchallenge.go 5.5 KB

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