blob: 986fd40fe924785dbf1e6928a92d80e642e55943 [file] [log] [blame]
package goscaffold
import (
"context"
"crypto/rsa"
"encoding/json"
"github.com/SermoDigital/jose/crypto"
"github.com/SermoDigital/jose/jws"
"github.com/julienschmidt/httprouter"
"github.com/justinas/alice"
"net/http"
"sync"
"time"
)
var (
gPkey *rsa.PublicKey = nil
rwMutex sync.RWMutex
)
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 structure that provides http connection to the URL that has the public
key for verifying the JWT token
*/
type OAuth struct {
}
/*
The interface functions offered to clients 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, err := getPublicKey(keyURL)
if err != nil {
setPkSafe(pk)
}
/*
Routine that will fetch & update the public keys in safe manner
*/
updatePublicKeysPeriodic(keyURL)
return &OAuth{}
}
/*
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 := 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) {
errors := Errors{message}
WriteErrorResponses(statusCode, errors, w)
}
/*
WriteErrorResponses write our error responses
*/
func WriteErrorResponses(statusCode int, errors Errors, w http.ResponseWriter) {
w.WriteHeader(statusCode)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(errors)
}
/*
updatePulicKeysPeriodic updates the cache periodically (every hour)
*/
func updatePublicKeysPeriodic(keyURL string) {
ticker := time.NewTicker(3600 * time.Second)
quit := make(chan struct{})
go func() {
for {
select {
case <-ticker.C:
pk, err := getPublicKey(keyURL)
if err == nil {
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) {
client := &http.Client{}
/* Connect to the server to fetch Key details */
r, err := client.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 setPkSafe(pk *rsa.PublicKey) {
rwMutex.Lock()
gPkey = pk
rwMutex.Unlock()
}
/*
getPkSafe returns the stored key (via a read lock)
*/
func getPkSafe() *rsa.PublicKey {
rwMutex.RLock()
pk := gPkey
rwMutex.RUnlock()
return pk
}