// Vikunja is a to-do list application to facilitate your life. // Copyright 2018-2021 Vikunja and contributors. All rights reserved. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public Licensee as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public Licensee for more details. // // You should have received a copy of the GNU Affero General Public Licensee // along with this program. If not, see . package identityawareproxy import ( "fmt" "time" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/modules/auth" "code.vikunja.io/api/pkg/user" "github.com/dgrijalva/jwt-go" "github.com/labstack/echo/v4" ) const IAPClaimsContextKey string = "iapClaims" // IAPClaims represents the claims made by the authentication JWT // passed in by the identity-aware proxy type IAPClaims struct { Email string `json:"email"` Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` jwt.StandardClaims } // Auth provider used to allow auth to get a web.Auth from the IAP provided identity type IAPAuthProvider struct{} func Init() { auth.RegisterAuthProvider(auth.AuthTypeIAPUser, IAPAuthProvider{}) auth.RegisterAuthMiddleware(auth.AuthTypeIAPUser, Middleware()) } // Get or generate a new user for the claims made by the IAP func userForIAPClaims(cl *IAPClaims) (u *user.User, err error) { s := db.NewSession() defer s.Close() // Check if we have seen this user before u, err = auth.GetOrCreateUserFromExternalAuth(s, cl.Issuer, cl.Subject, cl.Email, cl.Name, cl.PreferredUsername) if err != nil { _ = s.Rollback() return nil, err } err = s.Commit() if err != nil { return nil, err } return u, nil } // Create an AuthClaims object for a user dervived from an IAP identity func newIAPUserJWTAuthClaims(u *user.User) (claims *auth.AuthClaims) { return &auth.AuthClaims{ Type: auth.AuthTypeIAPUser, UserID: u.ID, UserUsername: u.Username, UserEmail: u.Email, UserName: u.Name, } } // Get a web.Auth object from the identity that the IAP provides func (p IAPAuthProvider) GetUser(c echo.Context, authClaims *auth.AuthClaims) (*user.User, error) { // The IAP middleware already checked and created a user if needed, no need to regenerate them // Just use the authClaims provided by the middleware u := &user.User{ ID: authClaims.UserID, Email: authClaims.UserEmail, Username: authClaims.UserUsername, Name: authClaims.UserName, } return u, nil } // Validates the claims in the IAP jwt // Matches the jwt-go Claims interface func (c *IAPClaims) Valid() error { // Validate that expiresAt and issuedAt are set and valid (with up to 1 minute of skew) now := TimeFunc() skew := time.Minute if !c.VerifyExpiresAt(now.Add(-skew).Unix(), true) { delta := now.Sub(time.Unix(c.ExpiresAt, 0)) return fmt.Errorf("token is expired by %v", delta) } if !c.VerifyIssuedAt(now.Add(skew).Unix(), true) { return fmt.Errorf("token used before issued") } // Validate that subject, email, and issuer are all set if c.Subject == "" { return fmt.Errorf("token missing subject") } if c.Email == "" { return fmt.Errorf("token missing email") } if c.Issuer == "" { return fmt.Errorf("token missing issuer") } return nil }