Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
iamacarpet committed Apr 20, 2020
0 parents commit 281b68a
Show file tree
Hide file tree
Showing 7 changed files with 480 additions and 0 deletions.
32 changes: 32 additions & 0 deletions audience.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package validateiap

import (
"fmt"
"os"

"cloud.google.com/go/compute/metadata"
)

func getAudience() (string, error) {
// Return the audience set in the environment first,
// then fall back the auto-generating an App Engine compatible value.
if aud := os.Getenv("IAP_AUDIENCE"); aud != "" {
return aud, nil
} else {
return getAppEngineAudience()
}
}

func getAppEngineAudience() (string, error) {
projectNumber, err := metadata.NumericProjectID()
if err != nil {
return "", fmt.Errorf("metadata.NumericProjectID: %v", err)
}

projectID, err := metadata.ProjectID()
if err != nil {
return "", fmt.Errorf("metadata.ProjectID: %v", err)
}

return "/projects/" + projectNumber + "/apps/" + projectID, nil
}
16 changes: 16 additions & 0 deletions get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package validateiap

import (
"fmt"
"net/http"
"strings"
)

func GetUserEmail(r *http.Request) (string, error) {
email := r.Header.Get("X-Goog-Authenticated-User-Email")
if email == "" {
return "", fmt.Errorf("Authenticated email is blank")
}

return strings.Replace(email, "accounts.google.com:", "", 1), nil
}
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module github.com/a1comms/go-middleware-validate-iap

go 1.13

require (
cloud.google.com/go v0.56.0
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/imkira/gcp-iap-auth v0.0.4-0.20190125075610-2aea4f92016e
github.com/urfave/negroni v1.0.0
)
270 changes: 270 additions & 0 deletions go.sum

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package validateiap

import (
"net/http"
)

func LogoutHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/_gcp_iap/clear_login_cookie", 302)
}
78 changes: 78 additions & 0 deletions init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package validateiap

import (
"fmt"
"log"
"regexp"
"strings"

"github.com/imkira/gcp-iap-auth/jwt"
)

var cfg *jwt.Config

func init() {
cfg = &jwt.Config{}

aud, err := getAudience()
if err != nil {
log.Fatal(err)
}

err = initAudiences(aud)
if err != nil {
log.Fatal(err)
}

cfg.PublicKeys, err = jwt.FetchPublicKeys()
if err != nil {
log.Fatal(err)
}

if err := cfg.Validate(); err != nil {
log.Fatal(err)
}
}

func initAudiences(audiences string) error {
str, err := extractAudiencesRegexp(audiences)
if err != nil {
return err
}
re, err := regexp.Compile(str)
if err != nil {
return fmt.Errorf("Invalid audiences regular expression %q (%v)", str, err)
}
cfg.MatchAudiences = re
return nil
}

func extractAudiencesRegexp(audiences string) (string, error) {
var strs []string
for _, audience := range strings.Split(audiences, ",") {
str, err := extractAudienceRegexp(audience)
if err != nil {
return "", err
}
strs = append(strs, str)
}
return strings.Join(strs, "|"), nil
}

func extractAudienceRegexp(audience string) (string, error) {
if strings.HasPrefix(audience, "/") && strings.HasSuffix(audience, "/") {
if len(audience) < 3 {
return "", fmt.Errorf("Invalid audiences regular expression %q", audience)
}
return audience[1 : len(audience)-1], nil
}
return parseRawAudience(audience)
}

func parseRawAudience(audience string) (string, error) {
_, err := jwt.ParseAudience(audience)
if err != nil {
return "", fmt.Errorf("Invalid audience %q (%v)", audience, err)
}
return fmt.Sprintf("^%s$", regexp.QuoteMeta(audience)), nil
}
65 changes: 65 additions & 0 deletions middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package validateiap

import (
"context"
"log"
"net/http"

"github.com/imkira/gcp-iap-auth/jwt"
"github.com/urfave/negroni"
)

type emailValFunc func(context.Context, string) (bool, error)

var (
ValidateIAPMiddleware negroni.HandlerFunc = GetValidateIAPMiddleware(emailNotEmpty)
ValidateIAPAppEngineMiddleware negroni.HandlerFunc = GetValidateIAPAppEngineMiddleware(emailNotEmpty)
)

func GetValidateIAPMiddleware(emailVal emailValFunc) negroni.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if claims, err := jwt.RequestClaims(r, cfg); err != nil {
log.Printf("ValidateIAP: Failed to validate request claims: %s", err)
} else {
if ok, err := emailVal(r.Context(), claims.Email); err != nil {
log.Printf("ValidateIAP: Failed to call email validation function: %s", err)
} else if ok {
next(w, r)
return
}
}

http.Error(w, "Unauthorized", http.StatusUnauthorized)
}
}

func GetValidateIAPAppEngineMiddleware(emailVal emailValFunc) negroni.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if val := r.Header.Get("X-AppEngine-Cron"); val != "" {
next(w, r)
return
} else if val := r.Header.Get("X-AppEngine-QueueName"); val != "" {
next(w, r)
return
} else if claims, err := jwt.RequestClaims(r, cfg); err == nil {
if ok, err := emailVal(r.Context(), claims.Email); err != nil {
log.Printf("ValidateIAP: Failed to call email validation function: %s", err)
} else if ok {
next(w, r)
return
}
} else {
log.Printf("ValidateIAP: Failed to validate request claims: %s", err)
}

http.Error(w, "Unauthorized", http.StatusUnauthorized)
}
}

func emailNotEmpty(ctx context.Context, email string) (bool, error) {
if email != "" {
return true, nil
}

return false, nil
}

0 comments on commit 281b68a

Please sign in to comment.