Merge branch 'eb/cookiename-configurable' of _OKE5H2PQKOUfzFFDuD4FA/default/CODE/gitness (#573)

This commit is contained in:
Enver Bisevac 2023-09-20 15:00:18 +00:00 committed by Harness
commit 138f47dd92
11 changed files with 44 additions and 39 deletions

View File

@ -79,6 +79,7 @@ ENV GITNESS_DATABASE_DRIVER sqlite3
ENV GITNESS_DATABASE_DATASOURCE /data/database.sqlite ENV GITNESS_DATABASE_DATASOURCE /data/database.sqlite
ENV GITNESS_METRIC_ENABLED=true ENV GITNESS_METRIC_ENABLED=true
ENV GITNESS_METRIC_ENDPOINT=https://stats.drone.ci/api/v1/gitness ENV GITNESS_METRIC_ENDPOINT=https://stats.drone.ci/api/v1/gitness
ENV GITNESS_TOKEN_COOKIE_NAME=token
COPY --from=builder /app/gitness /app/gitness COPY --from=builder /app/gitness /app/gitness
COPY --from=cert-image /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=cert-image /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt

View File

@ -94,7 +94,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
controller := user.ProvideController(db, principalUID, authorizer, principalStore, tokenStore, membershipStore) controller := user.ProvideController(db, principalUID, authorizer, principalStore, tokenStore, membershipStore)
serviceController := service.NewController(principalUID, authorizer, principalStore) serviceController := service.NewController(principalUID, authorizer, principalStore)
bootstrapBootstrap := bootstrap.ProvideBootstrap(config, controller, serviceController) bootstrapBootstrap := bootstrap.ProvideBootstrap(config, controller, serviceController)
authenticator := authn.ProvideAuthenticator(principalStore, tokenStore) authenticator := authn.ProvideAuthenticator(config, principalStore, tokenStore)
provider, err := url.ProvideURLProvider(config) provider, err := url.ProvideURLProvider(config)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -9,12 +9,15 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
) )
func includeTokenCookie(r *http.Request, w http.ResponseWriter, tokenResponse *types.TokenResponse) { func includeTokenCookie(
cookie := newEmptyTokenCookie(r) r *http.Request, w http.ResponseWriter,
tokenResponse *types.TokenResponse,
cookieName string,
) {
cookie := newEmptyTokenCookie(r, cookieName)
cookie.Value = tokenResponse.AccessToken cookie.Value = tokenResponse.AccessToken
if tokenResponse.Token.ExpiresAt != nil { if tokenResponse.Token.ExpiresAt != nil {
cookie.Expires = time.UnixMilli(*tokenResponse.Token.ExpiresAt) cookie.Expires = time.UnixMilli(*tokenResponse.Token.ExpiresAt)
@ -23,24 +26,24 @@ func includeTokenCookie(r *http.Request, w http.ResponseWriter, tokenResponse *t
http.SetCookie(w, cookie) http.SetCookie(w, cookie)
} }
func deleteTokenCookieIfPresent(r *http.Request, w http.ResponseWriter) { func deleteTokenCookieIfPresent(r *http.Request, w http.ResponseWriter, cookieName string) {
// if no token is present in the cookies, nothing todo. // if no token is present in the cookies, nothing todo.
// No other error type expected here - and even if there is, let's try best effort deletion. // No other error type expected here - and even if there is, let's try best effort deletion.
_, err := r.Cookie(request.CookieToken) _, err := r.Cookie(cookieName)
if errors.Is(err, http.ErrNoCookie) { if errors.Is(err, http.ErrNoCookie) {
return return
} }
cookie := newEmptyTokenCookie(r) cookie := newEmptyTokenCookie(r, cookieName)
cookie.Value = "" cookie.Value = ""
cookie.Expires = time.UnixMilli(0) // this effectively tells the browser to delete the cookie cookie.Expires = time.UnixMilli(0) // this effectively tells the browser to delete the cookie
http.SetCookie(w, cookie) http.SetCookie(w, cookie)
} }
func newEmptyTokenCookie(r *http.Request) *http.Cookie { func newEmptyTokenCookie(r *http.Request, cookieName string) *http.Cookie {
return &http.Cookie{ return &http.Cookie{
Name: request.CookieToken, Name: cookieName,
SameSite: http.SameSiteStrictMode, SameSite: http.SameSiteStrictMode,
HttpOnly: true, HttpOnly: true,
Path: "/", Path: "/",

View File

@ -15,19 +15,13 @@ import (
// HandleLogin returns an http.HandlerFunc that authenticates // HandleLogin returns an http.HandlerFunc that authenticates
// the user and returns an authentication token on success. // the user and returns an authentication token on success.
func HandleLogin(userCtrl *user.Controller) http.HandlerFunc { func HandleLogin(userCtrl *user.Controller, cookieName string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx) session, _ := request.AuthSessionFrom(ctx)
includeCookie, err := request.GetIncludeCookieFromQueryOrDefault(r, false)
if err != nil {
render.TranslatedUserError(w, err)
return
}
in := new(user.LoginInput) in := new(user.LoginInput)
err = json.NewDecoder(r.Body).Decode(in) err := json.NewDecoder(r.Body).Decode(in)
if err != nil { if err != nil {
render.BadRequestf(w, "Invalid request body: %s.", err) render.BadRequestf(w, "Invalid request body: %s.", err)
return return
@ -39,8 +33,8 @@ func HandleLogin(userCtrl *user.Controller) http.HandlerFunc {
return return
} }
if includeCookie { if cookieName != "" {
includeTokenCookie(r, w, tokenResponse) includeTokenCookie(r, w, tokenResponse, cookieName)
} }
render.JSON(w, http.StatusOK, tokenResponse) render.JSON(w, http.StatusOK, tokenResponse)

View File

@ -14,7 +14,7 @@ import (
// HandleLogout returns a http.HandlerFunc that deletes the // HandleLogout returns a http.HandlerFunc that deletes the
// user token being used in the respective request and logs the user out. // user token being used in the respective request and logs the user out.
func HandleLogout(userCtrl *user.Controller) http.HandlerFunc { func HandleLogout(userCtrl *user.Controller, cookieName string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx) session, _ := request.AuthSessionFrom(ctx)
@ -24,7 +24,7 @@ func HandleLogout(userCtrl *user.Controller) http.HandlerFunc {
// best effort delete cookie even in case of errors, to avoid clients being unable to remove the cookie. // best effort delete cookie even in case of errors, to avoid clients being unable to remove the cookie.
// WARNING: It could be that the cookie is removed even though the token is still there in the DB. // WARNING: It could be that the cookie is removed even though the token is still there in the DB.
// However, we have APIs to list and delete session tokens, and expiry time is usually short. // However, we have APIs to list and delete session tokens, and expiry time is usually short.
deleteTokenCookieIfPresent(r, w) deleteTokenCookieIfPresent(r, w, cookieName)
if err != nil { if err != nil {
render.TranslatedUserError(w, err) render.TranslatedUserError(w, err)

View File

@ -16,7 +16,7 @@ import (
// HandleRegister returns an http.HandlerFunc that processes an http.Request // HandleRegister returns an http.HandlerFunc that processes an http.Request
// to register the named user account with the system. // to register the named user account with the system.
func HandleRegister(userCtrl *user.Controller, sysCtrl *system.Controller) http.HandlerFunc { func HandleRegister(userCtrl *user.Controller, sysCtrl *system.Controller, cookieName string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
@ -40,7 +40,7 @@ func HandleRegister(userCtrl *user.Controller, sysCtrl *system.Controller) http.
} }
if includeCookie { if includeCookie {
includeTokenCookie(r, w, tokenResponse) includeTokenCookie(r, w, tokenResponse, cookieName)
} }
render.JSON(w, http.StatusOK, tokenResponse) render.JSON(w, http.StatusOK, tokenResponse)

View File

@ -11,7 +11,6 @@ import (
const ( const (
QueryParamAccessToken = "access_token" QueryParamAccessToken = "access_token"
QueryParamIncludeCookie = "include_cookie" QueryParamIncludeCookie = "include_cookie"
CookieToken = "token"
) )
func GetAccessTokenFromQuery(r *http.Request) (string, bool) { func GetAccessTokenFromQuery(r *http.Request) (string, bool) {
@ -22,6 +21,6 @@ func GetIncludeCookieFromQueryOrDefault(r *http.Request, dflt bool) (bool, error
return QueryParamAsBoolOrDefault(r, QueryParamIncludeCookie, dflt) return QueryParamAsBoolOrDefault(r, QueryParamIncludeCookie, dflt)
} }
func GetTokenFromCookie(r *http.Request) (string, bool) { func GetTokenFromCookie(r *http.Request, cookieName string) (string, bool) {
return GetCookie(r, CookieToken) return GetCookie(r, cookieName)
} }

View File

@ -24,14 +24,18 @@ var _ Authenticator = (*JWTAuthenticator)(nil)
// JWTAuthenticator uses the provided JWT to authenticate the caller. // JWTAuthenticator uses the provided JWT to authenticate the caller.
type JWTAuthenticator struct { type JWTAuthenticator struct {
cookieName string
principalStore store.PrincipalStore principalStore store.PrincipalStore
tokenStore store.TokenStore tokenStore store.TokenStore
} }
func NewTokenAuthenticator( func NewTokenAuthenticator(
principalStore store.PrincipalStore, principalStore store.PrincipalStore,
tokenStore store.TokenStore) *JWTAuthenticator { tokenStore store.TokenStore,
cookieName string,
) *JWTAuthenticator {
return &JWTAuthenticator{ return &JWTAuthenticator{
cookieName: cookieName,
principalStore: principalStore, principalStore: principalStore,
tokenStore: tokenStore, tokenStore: tokenStore,
} }
@ -39,7 +43,7 @@ func NewTokenAuthenticator(
func (a *JWTAuthenticator) Authenticate(r *http.Request, sourceRouter SourceRouter) (*auth.Session, error) { func (a *JWTAuthenticator) Authenticate(r *http.Request, sourceRouter SourceRouter) (*auth.Session, error) {
ctx := r.Context() ctx := r.Context()
str := extractToken(r) str := extractToken(r, a.cookieName)
if len(str) == 0 { if len(str) == 0 {
return nil, ErrNoAuthData return nil, ErrNoAuthData
@ -122,7 +126,7 @@ func (a *JWTAuthenticator) metadataFromMembershipClaims(
}, nil }, nil
} }
func extractToken(r *http.Request) string { func extractToken(r *http.Request, cookieName string) string {
// Check query param first (as that's most immediately visible to caller) // Check query param first (as that's most immediately visible to caller)
if queryToken, ok := request.GetAccessTokenFromQuery(r); ok { if queryToken, ok := request.GetAccessTokenFromQuery(r); ok {
return queryToken return queryToken
@ -145,7 +149,7 @@ func extractToken(r *http.Request) string {
} }
// check cookies last (as that's least visible to caller) // check cookies last (as that's least visible to caller)
if cookieToken, ok := request.GetTokenFromCookie(r); ok { if cookieToken, ok := request.GetTokenFromCookie(r, cookieName); ok {
return cookieToken return cookieToken
} }

View File

@ -6,6 +6,7 @@ package authn
import ( import (
"github.com/harness/gitness/internal/store" "github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
"github.com/google/wire" "github.com/google/wire"
) )
@ -15,6 +16,6 @@ var WireSet = wire.NewSet(
ProvideAuthenticator, ProvideAuthenticator,
) )
func ProvideAuthenticator(principalStore store.PrincipalStore, tokenStore store.TokenStore) Authenticator { func ProvideAuthenticator(config *types.Config, principalStore store.PrincipalStore, tokenStore store.TokenStore) Authenticator {
return NewTokenAuthenticator(principalStore, tokenStore) return NewTokenAuthenticator(principalStore, tokenStore, config.Token.CookieName)
} }

View File

@ -119,7 +119,7 @@ func NewAPIHandler(
r.Use(middlewareauthn.Attempt(authenticator, authn.SourceRouterAPI)) r.Use(middlewareauthn.Attempt(authenticator, authn.SourceRouterAPI))
r.Route("/v1", func(r chi.Router) { r.Route("/v1", func(r chi.Router) {
setupRoutesV1(r, repoCtrl, executionCtrl, triggerCtrl, logCtrl, pipelineCtrl, setupRoutesV1(r, config, repoCtrl, executionCtrl, triggerCtrl, logCtrl, pipelineCtrl,
connectorCtrl, templateCtrl, pluginCtrl, secretCtrl, spaceCtrl, pullreqCtrl, connectorCtrl, templateCtrl, pluginCtrl, secretCtrl, spaceCtrl, pullreqCtrl,
webhookCtrl, githookCtrl, saCtrl, userCtrl, principalCtrl, checkCtrl, sysCtrl) webhookCtrl, githookCtrl, saCtrl, userCtrl, principalCtrl, checkCtrl, sysCtrl)
}) })
@ -142,6 +142,7 @@ func corsHandler(config *types.Config) func(http.Handler) http.Handler {
} }
func setupRoutesV1(r chi.Router, func setupRoutesV1(r chi.Router,
config *types.Config,
repoCtrl *repo.Controller, repoCtrl *repo.Controller,
executionCtrl *execution.Controller, executionCtrl *execution.Controller,
triggerCtrl *trigger.Controller, triggerCtrl *trigger.Controller,
@ -171,7 +172,7 @@ func setupRoutesV1(r chi.Router,
setupPrincipals(r, principalCtrl) setupPrincipals(r, principalCtrl)
setupInternal(r, githookCtrl) setupInternal(r, githookCtrl)
setupAdmin(r, userCtrl) setupAdmin(r, userCtrl)
setupAccount(r, userCtrl, sysCtrl) setupAccount(r, userCtrl, sysCtrl, config)
setupSystem(r, sysCtrl) setupSystem(r, sysCtrl)
setupResources(r) setupResources(r)
setupPlugins(r, pluginCtrl) setupPlugins(r, pluginCtrl)
@ -599,8 +600,9 @@ func setupAdmin(r chi.Router, userCtrl *user.Controller) {
}) })
} }
func setupAccount(r chi.Router, userCtrl *user.Controller, sysCtrl *system.Controller) { func setupAccount(r chi.Router, userCtrl *user.Controller, sysCtrl *system.Controller, config *types.Config) {
r.Post("/login", account.HandleLogin(userCtrl)) cookieName := config.Token.CookieName
r.Post("/register", account.HandleRegister(userCtrl, sysCtrl)) r.Post("/login", account.HandleLogin(userCtrl, cookieName))
r.Post("/logout", account.HandleLogout(userCtrl)) r.Post("/register", account.HandleRegister(userCtrl, sysCtrl, cookieName))
r.Post("/logout", account.HandleLogout(userCtrl, cookieName))
} }

View File

@ -102,6 +102,7 @@ type Config struct {
// Token defines token configuration parameters. // Token defines token configuration parameters.
Token struct { Token struct {
CookieName string `envconfig:"GITNESS_TOKEN_COOKIE_NAME" default:"token"`
Expire time.Duration `envconfig:"GITNESS_TOKEN_EXPIRE" default:"720h"` Expire time.Duration `envconfig:"GITNESS_TOKEN_EXPIRE" default:"720h"`
} }