<commit_before>package actions

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"

	"github.com/deis/helm/log"
	"github.com/gobuffalo/buffalo"
	"github.com/gobuffalo/buffalo/middleware"
	"github.com/gobuffalo/buffalo/middleware/ssl"
	"github.com/gobuffalo/envy"
	"github.com/gobuffalo/pop"
	"github.com/gobuffalo/x/sessions"
	"github.com/rs/cors"
	"github.com/unrolled/secure"
	jose "gopkg.in/square/go-jose.v2"
	jwt "gopkg.in/square/go-jose.v2/jwt"

	"github.com/kindlyops/mappamundi/havenapi/models"
)

// ENV is used to help switch settings based on where the
// application is being run. Default is "development".
var ENV = envy.Get("GO_ENV", "development")

// TODO mappamundi/postgrest/keycloak-dev-public-key.json should be
// the JWK and will need to be mounted into the havenapi container
var KEY = envy.Get("HAVEN_JWK_PATH", "")

var key jose.JSONWebKey
var app *buffalo.App

// App is where all routes and middleware for buffalo
// should be defined. This is the nerve center of your
// application.
func App() *buffalo.App {
	if app == nil {
		app = buffalo.New(buffalo.Options{
			Env:          ENV,
			SessionStore: sessions.Null{},
			PreWares: []buffalo.PreWare{
				cors.Default().Handler,
			},
			SessionName: "_havenapi_session",
		})

		rawKey, err := ioutil.ReadFile(KEY)
		if err != nil {
			panic("could not read the JWK")
		}

		err = key.UnmarshalJSON(rawKey)
		if err != nil {
			panic("could not unmarshal the JWK")
		}

		if ENV == "development" {
			app.Use(middleware.ParameterLogger)
		}

		app.GET("/healthz", HealthzHandler)

		api := app.Group("/api/")
		// Automatically redirect to SSL
		api.Use(ssl.ForceSSL(secure.Options{
			SSLRedirect:     ENV == "production",
			SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"},
		}))
		// TODO refactor to use dependency injection instead of a package global
		api.Use(middleware.PopTransaction(models.DB))
		api.Use(JwtMiddleware)
		api.POST("files", UploadHandler)

	}

	return app
}

// Token is for un marshalling {"resource_access":{"havendev":{"roles":["member"]}}
type Token struct {
	ResourceAccess struct {
		Client struct {
			Roles []string `json:"roles,omitempty"`
		} `json:"havendev,omitempty"`
	} `json:"resource_access,omitempty"`
}

// JwtMiddleware validates JWT and set context compatible with PostgREST
func JwtMiddleware(next buffalo.Handler) buffalo.Handler {

	return func(c buffalo.Context) error {
		header := c.Request().Header.Get("Authorization")
		parts := strings.Split(header, "Bearer ")

		if len(parts) < 2 {
			return c.Error(http.StatusUnauthorized, fmt.Errorf("Must provide Authorization token"))
		}

		token := parts[1]
		if len(token) == 0 {
			return c.Error(http.StatusUnauthorized, fmt.Errorf("Must provide Authorization token"))
		}

		tok, err := jwt.ParseSigned(token)
		if err != nil {
			return c.Error(http.StatusUnauthorized, err)
		}

		// build up a set of claims to set locally in DB transaction
		// at a minimum set 'request.jwt.claim.email' and 'request.jwt.claim.sub'
		validClaims := jwt.Claims{}
		allClaims := make(map[string]interface{})
		if err := tok.Claims(key, &validClaims, &allClaims); err != nil {
			return c.Error(http.StatusUnauthorized, err)
		}

		// check if token is expired and from a valid issuer
		// TODO: the issuer needs to be configurable to handle on-prem deployments
		iss := "http://localhost:2015/auth/realms/havendev"
		err = validClaims.Validate(jwt.Expected{Issuer: iss})
		if err != nil {
			return c.Error(401, fmt.Errorf("invalid token: %s", err.Error()))
		}

		// look for the role that we should assume
		// {"resource_access":{"havendev":{"roles":["member"]}}
		access := allClaims["resource_access"].(map[string]interface{})
		havendev := access["havendev"].(map[string]interface{})
		roles := havendev["roles"].([]interface{})
		var role string
		for _, r := range roles {
			switch r.(string) {
			case "member":
				role = "member"
			case "admin":
				role = "admin"
			default:
				log.Info("Got unexpected role %s", r)
				role = "anonymous"
			}
		}

		sub := allClaims["sub"]
		c.Set("sub", sub)

		email := allClaims["email"]
		c.Set("email", email)

		org := allClaims["org"]
		c.Set("org", org)
		enc, _ := json.Marshal(org)

		tx := c.Value("tx").(*pop.Connection)
		err = tx.RawQuery(models.Q["setemailclaim"], email).Exec()<commit_msg>CodeClimate cognitive complexity, extract getRole function<commit_after>package actions

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"

	"github.com/deis/helm/log"
	"github.com/gobuffalo/buffalo"
	"github.com/gobuffalo/buffalo/middleware"
	"github.com/gobuffalo/buffalo/middleware/ssl"
	"github.com/gobuffalo/envy"
	"github.com/gobuffalo/pop"
	"github.com/gobuffalo/x/sessions"
	"github.com/rs/cors"
	"github.com/unrolled/secure"
	jose "gopkg.in/square/go-jose.v2"
	jwt "gopkg.in/square/go-jose.v2/jwt"

	"github.com/kindlyops/mappamundi/havenapi/models"
)

// ENV is used to help switch settings based on where the
// application is being run. Default is "development".
var ENV = envy.Get("GO_ENV", "development")

// TODO mappamundi/postgrest/keycloak-dev-public-key.json should be
// the JWK and will need to be mounted into the havenapi container
var KEY = envy.Get("HAVEN_JWK_PATH", "")

var key jose.JSONWebKey
var app *buffalo.App

// App is where all routes and middleware for buffalo
// should be defined. This is the nerve center of your
// application.
func App() *buffalo.App {
	if app == nil {
		app = buffalo.New(buffalo.Options{
			Env:          ENV,
			SessionStore: sessions.Null{},
			PreWares: []buffalo.PreWare{
				cors.Default().Handler,
			},
			SessionName: "_havenapi_session",
		})

		rawKey, err := ioutil.ReadFile(KEY)
		if err != nil {
			panic("could not read the JWK")
		}

		err = key.UnmarshalJSON(rawKey)
		if err != nil {
			panic("could not unmarshal the JWK")
		}

		if ENV == "development" {
			app.Use(middleware.ParameterLogger)
		}

		app.GET("/healthz", HealthzHandler)

		api := app.Group("/api/")
		// Automatically redirect to SSL
		api.Use(ssl.ForceSSL(secure.Options{
			SSLRedirect:     ENV == "production",
			SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"},
		}))
		// TODO refactor to use dependency injection instead of a package global
		api.Use(middleware.PopTransaction(models.DB))
		api.Use(JwtMiddleware)
		api.POST("files", UploadHandler)

	}

	return app
}

// Token is for un marshalling {"resource_access":{"havendev":{"roles":["member"]}}
type Token struct {
	ResourceAccess struct {
		Client struct {
			Roles []string `json:"roles,omitempty"`
		} `json:"havendev,omitempty"`
	} `json:"resource_access,omitempty"`
}

func getRole(allClaims map[string]interface{}) string {
	// look for the role that we should assume
	// {"resource_access":{"havendev":{"roles":["member"]}}
	access := allClaims["resource_access"].(map[string]interface{})
	havendev := access["havendev"].(map[string]interface{})
	roles := havendev["roles"].([]interface{})
	var role string
	for _, r := range roles {
		switch r.(string) {
		case "member":
			role = "member"
		case "admin":
			role = "admin"
		default:
			log.Info("Got unexpected role %s", r)
			role = "anonymous"
		}
	}
	return role
}

// JwtMiddleware validates JWT and set context compatible with PostgREST
func JwtMiddleware(next buffalo.Handler) buffalo.Handler {

	return func(c buffalo.Context) error {
		header := c.Request().Header.Get("Authorization")
		parts := strings.Split(header, "Bearer ")

		if len(parts) < 2 {
			return c.Error(http.StatusUnauthorized, fmt.Errorf("Must provide Authorization token"))
		}

		token := parts[1]
		if len(token) == 0 {
			return c.Error(http.StatusUnauthorized, fmt.Errorf("Must provide Authorization token"))
		}

		tok, err := jwt.ParseSigned(token)
		if err != nil {
			return c.Error(http.StatusUnauthorized, err)
		}

		// build up a set of claims to set locally in DB transaction
		// at a minimum set 'request.jwt.claim.email' and 'request.jwt.claim.sub'
		validClaims := jwt.Claims{}
		allClaims := make(map[string]interface{})
		if err := tok.Claims(key, &validClaims, &allClaims); err != nil {
			return c.Error(http.StatusUnauthorized, err)
		}

		// check if token is expired and from a valid issuer
		// TODO: the issuer needs to be configurable to handle on-prem deployments
		iss := "http://localhost:2015/auth/realms/havendev"
		err = validClaims.Validate(jwt.Expected{Issuer: iss})
		if err != nil {
			return c.Error(401, fmt.Errorf("invalid token: %s", err.Error()))
		}

		sub := allClaims["sub"]
		c.Set("sub", sub)

		email := allClaims["email"]
		c.Set("email", email)

		org := allClaims["org"]
		c.Set("org", org)
		enc, _ := json.Marshal(org)

		role := getRole(allClaims)

		tx := c.Value("tx").(*pop.Connection)
		err = tx.RawQuery(models.Q["setemailclaim"], email).Exec()
{'fraction_non_alphanumeric': 0.14931615236803436, 'fraction_numerical': 0.0027127839945744322, 'mean_word_length': 3.921023359288098, 'pattern_counts': {'":': 18, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4404350', 'n_tokens_mistral': 3372, 'n_tokens_neox': 3057, 'n_words': 730}