|  | package goscaffold | 
|  |  | 
|  | import ( | 
|  | "context" | 
|  | "crypto/rsa" | 
|  | "encoding/json" | 
|  | "net/http" | 
|  | "sync" | 
|  | "time" | 
|  |  | 
|  | "github.com/SermoDigital/jose/crypto" | 
|  | "github.com/SermoDigital/jose/jws" | 
|  | "github.com/julienschmidt/httprouter" | 
|  | "github.com/justinas/alice" | 
|  | ) | 
|  |  | 
|  | const params = "params" | 
|  |  | 
|  | // Errors to return | 
|  | type Errors []string | 
|  |  | 
|  | /* | 
|  | The SSO key parameters | 
|  | */ | 
|  | type ssoKey struct { | 
|  | Alg   string `json:"alg"` | 
|  | Value string `json:"value"` | 
|  | Kty   string `json:"kty"` | 
|  | Use   string `json:"use"` | 
|  | N     string `json:"n"` | 
|  | E     string `json:"e"` | 
|  | } | 
|  |  | 
|  | /* | 
|  | oauth provides http an connection to the URL that has the public | 
|  | key for verifying the JWT token | 
|  | */ | 
|  | type oauth struct { | 
|  | gPkey   *rsa.PublicKey | 
|  | rwMutex *sync.RWMutex | 
|  | } | 
|  |  | 
|  | /* | 
|  | ErrorResponse delivers the errors back to the caller, once validation | 
|  | has failed | 
|  | */ | 
|  | type ErrorResponse struct { | 
|  | Status  string   `json:"status"` | 
|  | Message string   `json:"message"` | 
|  | Errors  []string `json:"errors"` | 
|  | } | 
|  |  | 
|  | /* | 
|  | OAuthService offers interface functions that act on OAuth param, | 
|  | used to verify JWT tokens for the Http handler functions client | 
|  | wishes to validate against (via SSOHandler). | 
|  | */ | 
|  | type OAuthService interface { | 
|  | SSOHandler(p string, h func(http.ResponseWriter, *http.Request)) (string, httprouter.Handle) | 
|  | } | 
|  |  | 
|  | /* | 
|  | CreateOAuth is a constructor that creates OAuth for OAuthService | 
|  | interface. OAuthService interface offers method:- | 
|  | (1) SSOHandler(): Offers the user to attach http handler for JWT | 
|  | verification. | 
|  | */ | 
|  | func (s *HTTPScaffold) CreateOAuth(keyURL string) OAuthService { | 
|  | pk, _ := getPublicKey(keyURL) | 
|  | oa := &oauth{ | 
|  | rwMutex: &sync.RWMutex{}, | 
|  | } | 
|  | oa.setPkSafe(pk) | 
|  | oa.updatePublicKeysPeriodic(keyURL) | 
|  | return oa | 
|  | } | 
|  |  | 
|  | /* | 
|  | SetParamsInRequest Sets the params and its values in the request | 
|  | */ | 
|  | func SetParamsInRequest(r *http.Request, ps httprouter.Params) *http.Request { | 
|  | newContext := context.WithValue(r.Context(), params, ps) | 
|  | return r.WithContext(newContext) | 
|  | } | 
|  |  | 
|  | /* | 
|  | FetchParams fetches the param values, given the params in the request | 
|  | */ | 
|  | func FetchParams(r *http.Request) httprouter.Params { | 
|  | ctx := r.Context() | 
|  | return ctx.Value(params).(httprouter.Params) | 
|  | } | 
|  |  | 
|  | /* | 
|  | SSOHandler offers the users the flexibility of choosing which http handlers | 
|  | need JWT validation. | 
|  | */ | 
|  | func (a *oauth) SSOHandler(p string, h func(http.ResponseWriter, *http.Request)) (string, httprouter.Handle) { | 
|  | return p, a.VerifyOAuth(alice.New().ThenFunc(h)) | 
|  | } | 
|  |  | 
|  | /* | 
|  | VerifyOAuth verifies the JWT token in the request using the public key configured | 
|  | via CreateOAuth constructor. | 
|  | */ | 
|  | func (a *oauth) VerifyOAuth(next http.Handler) httprouter.Handle { | 
|  |  | 
|  | return func(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) { | 
|  |  | 
|  | /* Parse the JWT from the input request */ | 
|  | jwt, err := jws.ParseJWTFromRequest(r) | 
|  | if err != nil { | 
|  | WriteErrorResponse(http.StatusBadRequest, err.Error(), rw) | 
|  | return | 
|  | } | 
|  |  | 
|  | /* Get the pulic key from cache */ | 
|  | pk := a.getPkSafe() | 
|  | if pk == nil { | 
|  | WriteErrorResponse(http.StatusBadRequest, "Public key not configured. Validation failed.", rw) | 
|  | return | 
|  | } | 
|  |  | 
|  | /* Validate the token */ | 
|  | err = jwt.Validate(pk, crypto.SigningMethodRS256) | 
|  | if err != nil { | 
|  | WriteErrorResponse(http.StatusBadRequest, err.Error(), rw) | 
|  | return | 
|  | } | 
|  |  | 
|  | /* Set the input params in the request */ | 
|  | r = SetParamsInRequest(r, ps) | 
|  | next.ServeHTTP(rw, r) | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | /* | 
|  | WriteErrorResponse write a non 200 error response | 
|  | */ | 
|  | func WriteErrorResponse(statusCode int, message string, w http.ResponseWriter) { | 
|  | var errstr []string | 
|  |  | 
|  | w.Header().Set("Content-Type", "application/json") | 
|  | w.WriteHeader(statusCode) | 
|  | errstr = append(errstr, http.StatusText(statusCode)) | 
|  | resp := ErrorResponse{ | 
|  | Status:  http.StatusText(statusCode), | 
|  | Message: message, | 
|  | Errors:  errstr, | 
|  | } | 
|  | json.NewEncoder(w).Encode(resp) | 
|  | } | 
|  |  | 
|  | /* | 
|  | updatePulicKeysPeriodic updates the cache periodically (every hour) | 
|  | */ | 
|  | func (a *oauth) updatePublicKeysPeriodic(keyURL string) { | 
|  |  | 
|  | ticker := time.NewTicker(time.Hour) | 
|  | quit := make(chan struct{}) | 
|  | go func() { | 
|  | for { | 
|  | select { | 
|  | case <-ticker.C: | 
|  | pk, err := getPublicKey(keyURL) | 
|  | if err == nil { | 
|  | a.setPkSafe(pk) | 
|  | } | 
|  | case <-quit: | 
|  | ticker.Stop() | 
|  | return | 
|  | } | 
|  | } | 
|  | }() | 
|  | } | 
|  |  | 
|  | /* | 
|  | getPubicKey: Loads the Public key in to memory and returns it. | 
|  | */ | 
|  | func getPublicKey(keyURL string) (*rsa.PublicKey, error) { | 
|  |  | 
|  | /* Connect to the server to fetch Key details */ | 
|  | r, err := http.Get(keyURL) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  |  | 
|  | defer r.Body.Close() | 
|  |  | 
|  | /* Decode the SSO Key */ | 
|  | ssoKey := &ssoKey{} | 
|  | err = json.NewDecoder(r.Body).Decode(ssoKey) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  |  | 
|  | /* Retrieve the Public Key from SSO Key */ | 
|  | publicKey, err := crypto.ParseRSAPublicKeyFromPEM([]byte(ssoKey.Value)) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | return publicKey, nil | 
|  |  | 
|  | } | 
|  |  | 
|  | /* | 
|  | setPkSafe Safely stores the Public Key (via a Write Lock) | 
|  | */ | 
|  | func (a *oauth) setPkSafe(pk *rsa.PublicKey) { | 
|  | a.rwMutex.Lock() | 
|  | a.gPkey = pk | 
|  | a.rwMutex.Unlock() | 
|  | } | 
|  |  | 
|  | /* | 
|  | getPkSafe returns the stored key (via a read lock) | 
|  | */ | 
|  | func (a *oauth) getPkSafe() *rsa.PublicKey { | 
|  | a.rwMutex.RLock() | 
|  | pk := a.gPkey | 
|  | a.rwMutex.RUnlock() | 
|  | return pk | 
|  | } |