palourde/uchiwa

View on GitHub
uchiwa/authentication/jwt.go

Summary

Maintainability
A
35 mins
Test Coverage
package authentication

import (
    "crypto/rand"
    "crypto/rsa"
    "errors"
    "fmt"
    "io/ioutil"
    "net/http"

    "github.com/dgrijalva/jwt-go"
    "github.com/gorilla/context"
    "github.com/mitchellh/mapstructure"
    "github.com/sensu/uchiwa/uchiwa/logger"
    "github.com/sensu/uchiwa/uchiwa/structs"
)

// JWTToken constant
const JWTToken = "jwtToken"

var (
    privateKey *rsa.PrivateKey
    publicKey  *rsa.PublicKey
)

// GetJWTFromContext retrieves the JWT Token from the request
func GetJWTFromContext(r *http.Request) *jwt.Token {
    if value := context.Get(r, JWTToken); value != nil {
        return value.(*jwt.Token)
    }
    return nil
}

// GetRoleFromToken ...
func GetRoleFromToken(token *jwt.Token) (*Role, error) {
    r, ok := token.Claims["role"]
    if !ok {
        return &Role{}, errors.New("Could not retrieve the user Role from the JWT")
    }
    var role Role
    err := mapstructure.Decode(r, &role)
    if err != nil {
        return &Role{}, err
    }
    return &role, nil
}

// GetToken returns a string that contain the token
func GetToken(user *User, xsfrToken string) (string, error) {
    if user.Username == "" {
        return "", errors.New("Could not generate a token for the user. Invalid username")
    }

    t := jwt.New(jwt.GetSigningMethod("RS256"))
    t.Claims["email"] = user.Email
    t.Claims["fullname"] = user.FullName
    t.Claims["role"] = user.Role
    t.Claims["username"] = user.Username
    t.Claims["xsrfToken"] = xsfrToken

    if privateKey == nil {
        return "", errors.New("Could not generate a token for the user. Invalid private key")
    }

    tokenString, err := t.SignedString(privateKey)
    return tokenString, err
}

// generateKeyPair generates an RSA keypair of 2048 bits using a random rand.Reader
func generateKeyPair() *rsa.PrivateKey {
    keypair, err := rsa.GenerateKey(rand.Reader, 2048)
    if err != nil {
        logger.Fatalf("Could not generate an RSA keypair: %s", err)
    }

    return keypair
}

// generateToken generates a private and public RSA keys
// in order to be used for the JWT signature
func generateToken() (*rsa.PrivateKey, *rsa.PublicKey) {
    logger.Debug("Generating new temporary RSA keys")
    privateKey := generateKeyPair()
    // Precompute some calculations
    privateKey.Precompute()
    publicKey := &privateKey.PublicKey

    return privateKey, publicKey
}

// initToken initializes the token by weither loading the keys from the
// filesystem with the loadToken() function or by generating temporarily
// ones with the generateToken() function
func initToken(a structs.Auth) {
    var err error
    privateKey, publicKey, err = loadToken(a)
    if err != nil {
        // At this point we need to generate temporary RSA keys
        logger.Debug(err)
        privateKey, publicKey = generateToken()
    }
}

// loadToken loads a private and public RSA keys from the filesystem
// in order to be used for the JWT signature
func loadToken(a structs.Auth) (*rsa.PrivateKey, *rsa.PublicKey, error) {
    logger.Debug("Attempting to load the RSA keys from the filesystem")

    if a.PrivateKey == "" || a.PublicKey == "" {
        return nil, nil, errors.New("The paths to the private and public RSA keys were not provided")
    }

    // Read the files from the filesystem
    prv, err := ioutil.ReadFile(a.PrivateKey)
    if err != nil {
        logger.Fatalf("Unable to open the private key file: %v", err)
    }
    pub, err := ioutil.ReadFile(a.PublicKey)
    if err != nil {
        logger.Fatalf("Unable to open the public key file: %v", err)
    }

    // Parse the RSA keys
    privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(prv)
    if err != nil {
        logger.Fatalf("Unable to parse the private key: %v", err)
    }
    publicKey, err := jwt.ParseRSAPublicKeyFromPEM(pub)
    if err != nil {
        logger.Fatalf("Unable to parse the public key: %v", err)
    }

    logger.Info("Provided RSA keys successfully loaded")
    return privateKey, publicKey, nil
}

// setJWTIntoContext injects the JWT Token into the request for later use
func setJWTInContext(r *http.Request, token *jwt.Token) {
    context.Set(r, JWTToken, token)
}

// verifyJWT extracts and verifies the validity of the JWT
func verifyJWT(tokenString string) (*jwt.Token, error) {
    token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        // Don't forget to validate the alg is what you expect:
        if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
            logger.Debugf("Unexpected signing method: %v", token.Header["alg"])
            return nil, fmt.Errorf("")
        }
        return publicKey, nil
    })

    if token == nil || err != nil {
        logger.Debug(err)
        return nil, errors.New("")
    }

    if !token.Valid {
        logger.Debug("Invalid JWT")
        return nil, errors.New("")
    }

    return token, nil
}