OSDN Git Service

check error is false
[bytom/bytom-spv.git] / net / http / authn / authn.go
1 package authn
2
3 import (
4         "context"
5         "crypto/x509"
6         "net"
7         "net/http"
8         "strings"
9         "sync"
10         "time"
11
12         "github.com/bytom/accesstoken"
13         "github.com/bytom/errors"
14 )
15
16 const tokenExpiry = time.Minute * 5
17
18 var loopbackOn = true
19
20 var (
21         //ErrInvalidToken is returned when authenticate is called with invalid token.
22         ErrInvalidToken = errors.New("invalid token")
23         //ErrNoToken is returned when authenticate is called with no token.
24         ErrNoToken = errors.New("no token")
25 )
26
27 //API describe the token authenticate.
28 type API struct {
29         tokens             *accesstoken.CredentialStore
30         crosscoreRPCPrefix string
31         rootCAs            *x509.CertPool
32
33         tokenMu  sync.Mutex // protects the following
34         tokenMap map[string]tokenResult
35 }
36
37 type tokenResult struct {
38         lastLookup time.Time
39 }
40
41 //NewAPI create a token authenticate object.
42 func NewAPI(tokens *accesstoken.CredentialStore) *API {
43         return &API{
44                 tokens:   tokens,
45                 tokenMap: make(map[string]tokenResult),
46         }
47 }
48
49 // Authenticate returns the request, with added tokens and/or localhost
50 // flags in the context, as appropriate.
51 func (a *API) Authenticate(req *http.Request) (*http.Request, error) {
52         ctx := req.Context()
53
54         token, err := a.tokenAuthn(req)
55         if err == nil && token != "" {
56                 // if this request was successfully authenticated with a token, pass the token along
57                 ctx = newContextWithToken(ctx, token)
58         }
59         local := a.localhostAuthn(req)
60         if local {
61                 ctx = newContextWithLocalhost(ctx)
62         }
63
64         if !local && strings.HasPrefix(req.URL.Path, "/list-access-tokens") {
65                 return req.WithContext(ctx), errors.New("only local can get access token list")
66         }
67
68         // Temporary workaround. Dashboard is always ok.
69         // See loopbackOn comment above.
70         if strings.HasPrefix(req.URL.Path, "/dashboard/") || req.URL.Path == "/dashboard" {
71                 return req.WithContext(ctx), nil
72         }
73         if loopbackOn && local {
74                 return req.WithContext(ctx), nil
75         }
76
77         return req.WithContext(ctx), err
78 }
79
80 // checks the request for a valid client cert list.
81 // If found, it is added to the request's context.
82 // Note that an *invalid* client cert is treated the
83 // same as no client cert -- it is omitted from the
84 // returned context, but the connection may proceed.
85 func certAuthn(req *http.Request, rootCAs *x509.CertPool) context.Context {
86         if req.TLS != nil && len(req.TLS.PeerCertificates) > 0 {
87                 certs := req.TLS.PeerCertificates
88
89                 // Same logic as serverHandshakeState.processCertsFromClient
90                 // in $GOROOT/src/crypto/tls/handshake_server.go.
91                 opts := x509.VerifyOptions{
92                         Roots:         rootCAs,
93                         CurrentTime:   time.Now(),
94                         Intermediates: x509.NewCertPool(),
95                         KeyUsages:     []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
96                 }
97                 for _, cert := range certs[1:] {
98                         opts.Intermediates.AddCert(cert)
99                 }
100
101                 if _, err := certs[0].Verify(opts); err != nil {
102                         // crypto/tls treats this as an error:
103                         // errors.New("tls: failed to verify client's certificate: " + err.Error())
104                         // For us, it is ok; we want to treat it the same as if there
105                         // were no client cert presented.
106                         return req.Context()
107                 }
108
109                 return context.WithValue(req.Context(), x509CertsKey, certs)
110         }
111         return req.Context()
112 }
113
114 // returns true if this request is coming from a loopback address
115 func (a *API) localhostAuthn(req *http.Request) bool {
116         h, _, err := net.SplitHostPort(req.RemoteAddr)
117         if err != nil {
118                 return false
119         }
120         if !net.ParseIP(h).IsLoopback() {
121                 return false
122         }
123         return true
124 }
125
126 func (a *API) tokenAuthn(req *http.Request) (string, error) {
127         user, pw, ok := req.BasicAuth()
128         if !ok {
129                 return "", ErrNoToken
130         }
131         return user, a.cachedTokenAuthnCheck(req.Context(), user, pw)
132 }
133
134 func (a *API) cachedTokenAuthnCheck(ctx context.Context, user, pw string) error {
135         a.tokenMu.Lock()
136         res, ok := a.tokenMap[user+pw]
137         a.tokenMu.Unlock()
138         if !ok || time.Now().After(res.lastLookup.Add(tokenExpiry)) {
139                 err := a.tokens.Check(ctx, user, pw)
140                 if err != nil {
141                         return ErrInvalidToken
142                 }
143                 res = tokenResult{lastLookup: time.Now()}
144                 a.tokenMu.Lock()
145                 a.tokenMap[user+pw] = res
146                 a.tokenMu.Unlock()
147         }
148         return nil
149 }