Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
server.go 7.99 KiB
package main

import (
	"encoding/json"
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/golang-jwt/jwt/v4"
	"github.com/gorilla/mux"
	"github.com/yalp/jsonpath"
	"go.uber.org/zap"
)

func RequestLogger(targetMux http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()

		targetMux.ServeHTTP(w, r)

		Logger.Infow("",
			zap.String("method", string(r.Method)),
			zap.String("uri", string(r.RequestURI)),
			zap.Duration("duration", time.Since(start)*1000),
		)
	})
}

func startServer(port *int) {
	router := mux.NewRouter().StrictSlash(true)

	router.HandleFunc("/sendInvitation", sendInvitationPOST).Methods("POST")
	router.HandleFunc("/createCredential", createCredentialPOST).Methods("POST")
	router.HandleFunc("/options", optionsGET).Methods("GET")
	router.HandleFunc("/isAlive", isAliveGet).Methods("GET")

	portString := ":" + strconv.Itoa(*port)
	log.Fatal(http.ListenAndServe(portString, RequestLogger(router)))
}

func sendInvitationPOST(w http.ResponseWriter, r *http.Request) {
	// Get config
	config, _ := getConfig()

	// Authentication check
	token, err := GetToken(r, config.inviteIdentityProviderOidURL)
	if err != nil {
		Logger.Error(err)
		w.WriteHeader(401)
		json.NewEncoder(w).Encode(err.Error())

		return
	}

	// Authorization check
	err = Authorize(r, config.inviteIdentityProviderOidURL, config.claimMappingURL, config.inviteAdminRolePath, config.inviteTokenContextPath, config.inviteRequiredClaims)
	if err != nil {
		Logger.Error(err)
		w.WriteHeader(401)
		json.NewEncoder(w).Encode(err.Error())

		return
	}

	// Check if admin
	adminRolePath := config.inviteAdminRolePath
	adminRoles := config.adminRoles

	tokenPayload, _ := json.Marshal(token.Claims.(jwt.MapClaims))
	var tokenData interface{}
	err = json.Unmarshal(tokenPayload, &tokenData)
	roles, err := jsonpath.Read(tokenData, adminRolePath)

	var adminRolesObjects []interface{}
	err = json.Unmarshal([]byte(adminRoles), &adminRolesObjects)

	isAdmin := false
	for _, adminRoleObject := range adminRolesObjects {
		for _, role := range roles.([]interface{}) {
			if role.(string) == adminRoleObject.(string) {
				isAdmin = true
			}
		}
	}
	if !isAdmin {
		http.Error(w, "Missing admin role in token payload.", http.StatusBadRequest)
		return
	}

	// Get body params
	var jsonBody map[string]interface{}
	err = json.NewDecoder(r.Body).Decode(&jsonBody)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	var bodyTemplateKeys map[string]interface{}
	bodyTemplateKeys = jsonBody["templateKeyMap"].(map[string]interface{})
	var selectedRoles ([]string)
	for _, value := range jsonBody["selectedRoles"].(([]interface{})) {
		selectedRoles = append(selectedRoles, string(value.(string)))
	}

	// Create data
	var tokenDataMapping map[string]interface{}
	json.Unmarshal([]byte(config.credentialMapping), &tokenDataMapping)
	var data map[string]interface{}
	json.Unmarshal([]byte(config.credentialDataTemplate), &data)

	for key, value := range tokenDataMapping {
		if tokenValue, found := tokenData.(map[string]interface{})[value.(string)]; found {
			data[key] = tokenValue
		} else {
			data[key] = bodyTemplateKeys[key]
		}
	}

	data["roles"] = selectedRoles
	payload := make(map[string]interface{})
	payload["userData"] = data

	dataString, _ := json.Marshal(payload)

	// Create invitation
	credentialEndpoint := config.credentialEndpoint
	qrCodeData, err := createInvitation(credentialEndpoint, string(dataString), token.Raw)
	if err != nil {
		Logger.Error(err)
		w.WriteHeader(500)
		json.NewEncoder(w).Encode(err.Error())

		return
	}
	qrCodeContent, _ := json.Marshal(qrCodeData)

	// Generate QR Code
	qrCode, err := generateQR(string(qrCodeContent))
	if err != nil {
		Logger.Error(err)
		w.WriteHeader(500)
		json.NewEncoder(w).Encode(err.Error())

		return
	}

	emailRecipient := bodyTemplateKeys["emailRecipient"]

	// Send email
	templateKeys := []string{}
	var emailKeysJson []interface{}
	json.Unmarshal([]byte(config.mailTemplateKeys), &emailKeysJson)
	for _, keyObject := range emailKeysJson {
		object := keyObject.(map[string]interface{})
		templateKeys = append(templateKeys, object["key"].(string))
	}
	emailTemplate := config.mailTemplate
	valueMap := make(map[string]string)
	for _, key := range templateKeys {
		if key == "qr" {
			valueMap[key] = string(qrCode)
		} else if _, ok := bodyTemplateKeys[key]; ok {
			valueMap[key] = bodyTemplateKeys[key].(string)
		}
	}

	err = sendEmail(emailTemplate, valueMap, emailRecipient.(string))

	w.WriteHeader(http.StatusOK)

	return
}

func createCredentialPOST(w http.ResponseWriter, r *http.Request) {
	// Get config
	config, _ := getConfig()

	// Authentication check
	token, err := GetToken(r, config.createIdentityProviderOidURL)
	if err != nil {
		Logger.Error(err)
		w.WriteHeader(401)
		json.NewEncoder(w).Encode(err.Error())

		return
	}

	// Authorization check
	err = Authorize(r, config.createIdentityProviderOidURL, config.claimMappingURL, config.createAdminRolePath, config.createTokenContextPath, config.createRequiredClaims)
	if err != nil {
		Logger.Error(err)
		w.WriteHeader(401)
		json.NewEncoder(w).Encode(err.Error())

		return
	}

	// Get body params
	var jsonBody map[string]interface{}
	err = json.NewDecoder(r.Body).Decode(&jsonBody)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	var selectedRoles ([]string)
	for _, value := range jsonBody["selectedRoles"].(([]interface{})) {
		selectedRoles = append(selectedRoles, string(value.(string)))
	}
	// Check roles
	adminRolePath := config.createAdminRolePath

	tokenPayload, _ := json.Marshal(token.Claims.(jwt.MapClaims))
	var tokenData interface{}
	err = json.Unmarshal(tokenPayload, &tokenData)
	roles, err := jsonpath.Read(tokenData, adminRolePath)
	var finalRoles []string
	var hasContent = false
	for _, role := range roles.([]interface{}) {
		for _, selectedRole := range selectedRoles {
			if role.(string) == selectedRole {
				finalRoles = append(finalRoles, role.(string))
				hasContent = true
			}
		}
	}

	if !hasContent {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	// Create data
	var tokenDataMapping map[string]interface{}
	json.Unmarshal([]byte(config.credentialMapping), &tokenDataMapping)
	var data map[string]interface{}
	json.Unmarshal([]byte(config.credentialDataTemplate), &data)

	for key, value := range tokenDataMapping {
		if tokenValue, found := tokenData.(map[string]interface{})[value.(string)]; found {
			data[key] = tokenValue
		}
	}

	data["roles"] = finalRoles

	payload := make(map[string]interface{})
	payload["userData"] = data

	dataString, _ := json.Marshal(payload)
	// Create invitation
	credentialEndpoint := config.credentialEndpoint
	qrCodeData, err := createInvitation(credentialEndpoint, string(dataString), token.Raw)
	if err != nil {
		Logger.Error(err)
		w.WriteHeader(500)
		json.NewEncoder(w).Encode(err.Error())

		return
	}
	qrCodeContent, _ := json.Marshal(qrCodeData)

	// Generate QR Code
	qrCode, err := generateQR(string(qrCodeContent))
	if err != nil {
		Logger.Error(err)
		w.WriteHeader(500)
		json.NewEncoder(w).Encode(err.Error())

		return
	}

	w.Header().Set("Content-Type", "application/octet-stream")
	w.Write(qrCode)

	return
}

func optionsGET(w http.ResponseWriter, r *http.Request) {
	// Get config
	config, _ := getConfig()

	w.Header().Set("Content-Type", "application/json")

	options := make(map[string]interface{})

	// Get roles
	var roles []string
	roleObjects, err := listRoles(config.claimMappingServiceURL)
	if err != nil {
		Logger.Error(err)
		w.WriteHeader(500)
		json.NewEncoder(w).Encode(err.Error())

		return
	}
	for _, roleObject := range roleObjects {
		role := roleObject.(map[string]interface{})
		roles = append(roles, role["Role"].(string))
	}
	options["roleKeys"] = roles

	// Get template email keys
	var emailKeysJson []interface{}
	json.Unmarshal([]byte(config.mailTemplateKeys), &emailKeysJson)
	options["templateEmailKeys"] = emailKeysJson

	json.NewEncoder(w).Encode(options)

	return
}

func isAliveGet(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)

	return
}