2020-11-26 02:25:57 +00:00
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 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 General Public License 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package identityawareproxy
import (
"fmt"
"time"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/modules/auth"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/web"
"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 { } )
}
// NewIAPUserJWTAuthtoken generates and signes a new jwt token for a user
// These are intentionally short lived because they can be regenerated at
// any time from the IAP authn information. They are not related to
// session length and are only used to provide user info to the frontend
2020-12-28 21:30:00 +00:00
// and a hint to auth.go to retrieve auth data from the IAP.
2020-11-26 02:25:57 +00:00
func NewIAPUserJWTAuthtoken ( u * user . User ) ( token string , err error ) {
// Set claims
claims := & auth . AuthClaims {
Type : auth . AuthTypeIAPUser ,
UserID : u . ID ,
UserUsername : u . Username ,
UserEmail : u . Email ,
UserName : u . Name ,
StandardClaims : jwt . StandardClaims {
ExpiresAt : time . Now ( ) . Add ( time . Minute * 5 ) . Unix ( ) ,
} ,
}
t := jwt . NewWithClaims ( jwt . SigningMethodHS256 , claims )
// Generate encoded token and send it as response.
return t . SignedString ( [ ] byte ( config . ServiceJWTSecret . GetString ( ) ) )
}
// Token generates a local, short-lived JWT based on the identity from the identity-aware proxy.
// See also the docs for NewIAPUserJWTAuthtoken
// @Summary Authenticate a user from the Identity-Aware Proxy
// @Description Generates a short-lived JWT based on the identity from the identity-aware proxy in order to provide the front-end with user id and username info
// @tags auth
// @Accept N/A
// @Produce json
// @Success 200 {object} auth.Token
// @Failure 500 {object} models.Message "Internal error"
// @Router /auth/identityawareproxy/token [get]
func GetToken ( c echo . Context ) error {
cl := c . Get ( IAPClaimsContextKey ) . ( * IAPClaims )
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 err
}
err = s . Commit ( )
if err != nil {
return err
}
// Create token
userToken , err := NewIAPUserJWTAuthtoken ( u )
if err != nil {
return err
}
return auth . NewTokenResponse ( userToken , c )
}
// Get a web.Auth object from the identity that the IAP provides
func ( p IAPAuthProvider ) GetWebAuth ( c echo . Context , authClaims * auth . AuthClaims ) ( web . Auth , error ) {
s := db . NewSession ( )
defer s . Close ( )
// Get the user from the IAP identity
cl := c . Get ( IAPClaimsContextKey ) . ( * IAPClaims )
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
}
// Sanity check that the user the frontend thinks it has (the authClaims from the JWT it passed in)
// is the same as the user provided by the IAP.
if authClaims . UserID != u . ID {
return nil , ErrIAPUserFrontendMismatch { }
}
return u , nil
}
// Validates the claims in the 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
2020-12-28 21:30:00 +00:00
if ! c . VerifyExpiresAt ( now . Add ( - skew ) . Unix ( ) , true ) {
2020-11-26 02:25:57 +00:00
delta := now . Sub ( time . Unix ( c . ExpiresAt , 0 ) )
return fmt . Errorf ( "token is expired by %v" , delta )
}
2020-12-28 21:30:00 +00:00
if ! c . VerifyIssuedAt ( now . Add ( skew ) . Unix ( ) , true ) {
2020-11-26 02:25:57 +00:00
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
}