| 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 | 
 | } |