http_authenticator.go 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. /*
  2. * Copyright (c) 2015, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package upstreamproxy
  20. import (
  21. "fmt"
  22. "net/http"
  23. "strings"
  24. )
  25. type HttpAuthState int
  26. const (
  27. HTTP_AUTH_STATE_UNCHALLENGED HttpAuthState = iota
  28. HTTP_AUTH_STATE_CHALLENGED
  29. HTTP_AUTH_STATE_FAILURE
  30. HTTP_AUTH_STATE_SUCCESS
  31. )
  32. type HttpAuthenticator interface {
  33. PreAuthenticate(req *http.Request) error
  34. Authenticate(req *http.Request, resp *http.Response) error
  35. IsConnectionBased() bool
  36. IsComplete() bool
  37. Reset()
  38. }
  39. func parseAuthChallenge(resp *http.Response) (map[string]string, error) {
  40. challenges := make(map[string]string)
  41. headers := resp.Header[http.CanonicalHeaderKey("proxy-authenticate")]
  42. for _, val := range headers {
  43. s := strings.SplitN(val, " ", 2)
  44. if len(s) == 2 {
  45. challenges[s[0]] = s[1]
  46. }
  47. if len(s) == 1 && s[0] != "" {
  48. challenges[s[0]] = ""
  49. }
  50. }
  51. if len(challenges) == 0 {
  52. return nil, proxyError(fmt.Errorf("no valid challenges in the Proxy-Authenticate header"))
  53. }
  54. return challenges, nil
  55. }
  56. func NewHttpAuthenticator(resp *http.Response, username, password string) (HttpAuthenticator, error) {
  57. challenges, err := parseAuthChallenge(resp)
  58. if err != nil {
  59. // Already wrapped in proxyError
  60. return nil, err
  61. }
  62. // NTLM > Digest > Basic
  63. if _, ok := challenges["NTLM"]; ok {
  64. return newNTLMAuthenticator(username, password), nil
  65. } else if _, ok := challenges["Digest"]; ok {
  66. return newDigestAuthenticator(username, password), nil
  67. } else if _, ok := challenges["Basic"]; ok {
  68. return newBasicAuthenticator(username, password), nil
  69. }
  70. // Unsupported scheme
  71. schemes := make([]string, 0, len(challenges))
  72. for scheme := range challenges {
  73. schemes = append(schemes, scheme)
  74. }
  75. return nil, proxyError(fmt.Errorf("unsupported proxy authentication scheme in %v", schemes))
  76. }