2012-12-11 04:59:23 +03:00
package main
import (
2019-01-17 22:49:14 +02:00
"context"
2023-02-23 21:00:19 +02:00
"embed"
2019-11-08 00:38:36 +02:00
"encoding/json"
2012-12-11 04:59:23 +03:00
"errors"
"fmt"
2015-03-19 21:59:48 +02:00
"net"
2012-12-11 04:59:23 +03:00
"net/http"
"net/url"
2021-02-14 19:08:04 +02:00
"os"
"os/signal"
2015-01-19 18:10:37 +02:00
"regexp"
2012-12-11 04:59:23 +03:00
"strings"
2021-02-14 19:08:04 +02:00
"syscall"
2012-12-11 04:59:23 +03:00
"time"
2014-08-07 23:16:39 +03:00
2021-03-23 21:34:06 +02:00
"github.com/gorilla/mux"
2020-07-18 01:42:51 +02:00
"github.com/justinas/alice"
2020-09-29 18:44:42 +02:00
ipapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/ip"
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
2021-02-13 13:38:33 +02:00
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/pagewriter"
2021-06-05 13:34:31 +02:00
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/redirect"
2020-09-29 18:44:42 +02:00
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/basic"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
2022-03-13 12:08:33 +02:00
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
2021-02-14 19:08:04 +02:00
proxyhttp "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/http"
2021-07-28 10:12:00 +02:00
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
2021-04-21 11:33:27 +02:00
2020-09-29 18:44:42 +02:00
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/middleware"
2021-01-02 23:46:05 +02:00
requestutil "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests/util"
2020-09-29 18:44:42 +02:00
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/upstream"
"github.com/oauth2-proxy/oauth2-proxy/v7/providers"
2012-12-11 04:59:23 +03:00
)
2018-11-29 16:26:41 +02:00
const (
2021-02-14 13:38:20 +02:00
schemeHTTP = "http"
2021-01-03 00:20:48 +02:00
schemeHTTPS = "https"
2019-01-31 17:22:30 +02:00
applicationJSON = "application/json"
2021-03-23 21:34:06 +02:00
robotsPath = "/robots.txt"
signInPath = "/sign_in"
signOutPath = "/sign_out"
oauthStartPath = "/start"
oauthCallbackPath = "/callback"
authOnlyPath = "/auth"
userInfoPath = "/userinfo"
2023-02-23 21:00:19 +02:00
staticPathPrefix = "/static/"
2018-11-29 16:26:41 +02:00
)
2019-06-07 05:50:44 +02:00
var (
// ErrNeedsLogin means the user should be redirected to the login page
ErrNeedsLogin = errors . New ( "redirect to login page" )
2020-05-06 13:42:02 +02:00
2020-10-29 03:40:58 +02:00
// ErrAccessDenied means the user should receive a 401 Unauthorized response
ErrAccessDenied = errors . New ( "access denied" )
2023-02-23 21:00:19 +02:00
//go:embed static/*
staticFiles embed . FS
2019-06-07 05:50:44 +02:00
)
2020-09-23 03:54:32 +02:00
// allowedRoute manages method + path based allowlists
type allowedRoute struct {
method string
2022-08-19 12:46:25 +02:00
negate bool
2020-09-23 03:54:32 +02:00
pathRegex * regexp . Regexp
}
2022-09-11 17:09:32 +02:00
type apiRoute struct {
pathRegex * regexp . Regexp
}
2018-12-20 11:30:42 +02:00
// OAuthProxy is the main authentication proxy
2015-11-09 01:57:01 +02:00
type OAuthProxy struct {
2021-04-21 11:33:27 +02:00
CookieOptions * options . Cookie
Validator func ( string ) bool
2012-12-11 04:59:23 +03:00
2021-03-23 21:34:06 +02:00
SignInPath string
2015-05-30 00:47:40 +02:00
2021-02-12 19:53:01 +02:00
allowedRoutes [ ] allowedRoute
2022-09-11 17:09:32 +02:00
apiRoutes [ ] apiRoute
2021-02-12 19:53:01 +02:00
redirectURL * url . URL // the url to receive requests at
2023-10-25 11:25:01 +02:00
relativeRedirectURL bool
2021-02-12 19:53:01 +02:00
whitelistDomains [ ] string
provider providers . Provider
sessionStore sessionsapi . SessionStore
ProxyPrefix string
basicAuthValidator basic . Validator
2021-09-26 22:25:34 +02:00
basicAuthGroups [ ] string
2021-02-12 19:53:01 +02:00
SkipProviderButton bool
skipAuthPreflight bool
skipJwtBearerTokens bool
2021-10-05 11:24:47 +02:00
forceJSONErrors bool
2021-02-12 19:53:01 +02:00
realClientIPParser ipapi . RealClientIPParser
trustedIPs * ip . NetSet
2020-07-18 01:42:51 +02:00
2021-06-05 13:34:31 +02:00
sessionChain alice . Chain
headersChain alice . Chain
preAuthChain alice . Chain
pageWriter pagewriter . Writer
server proxyhttp . Server
upstreamProxy http . Handler
serveMux * mux . Router
redirectValidator redirect . Validator
appDirector redirect . AppDirector
2012-12-11 04:59:23 +03:00
}
2019-12-20 16:44:59 +02:00
// NewOAuthProxy creates a new instance of OAuthProxy from the options provided
2020-05-25 15:00:49 +02:00
func NewOAuthProxy ( opts * options . Options , validator func ( string ) bool ) ( * OAuthProxy , error ) {
sessionStore , err := sessions . NewSessionStore ( & opts . Session , & opts . Cookie )
if err != nil {
return nil , fmt . Errorf ( "error initialising session store: %v" , err )
}
2021-02-12 20:25:46 +02:00
var basicAuthValidator basic . Validator
if opts . HtpasswdFile != "" {
logger . Printf ( "using htpasswd file: %s" , opts . HtpasswdFile )
var err error
basicAuthValidator , err = basic . NewHTPasswdValidator ( opts . HtpasswdFile )
if err != nil {
2022-09-01 20:46:00 +02:00
return nil , fmt . Errorf ( "could not validate htpasswd: %v" , err )
2021-02-12 20:25:46 +02:00
}
2021-02-06 20:56:31 +02:00
}
2021-02-07 00:17:59 +02:00
2022-02-15 14:00:06 +02:00
provider , err := providers . NewProvider ( opts . Providers [ 0 ] )
if err != nil {
2023-02-20 12:21:59 +02:00
return nil , fmt . Errorf ( "error initialising provider: %v" , err )
2022-02-15 14:00:06 +02:00
}
2021-02-13 13:38:33 +02:00
pageWriter , err := pagewriter . NewWriter ( pagewriter . Opts {
2021-02-12 20:25:46 +02:00
TemplatesPath : opts . Templates . Path ,
2021-02-18 21:13:27 +02:00
CustomLogo : opts . Templates . CustomLogo ,
2021-02-12 20:25:46 +02:00
ProxyPrefix : opts . ProxyPrefix ,
Footer : opts . Templates . Footer ,
Version : VERSION ,
Debug : opts . Templates . Debug ,
2022-02-15 14:00:06 +02:00
ProviderName : buildProviderName ( provider , opts . Providers [ 0 ] . Name ) ,
2021-02-12 20:25:46 +02:00
SignInMessage : buildSignInMessage ( opts ) ,
DisplayLoginForm : basicAuthValidator != nil && opts . Templates . DisplayLoginForm ,
} )
if err != nil {
return nil , fmt . Errorf ( "error initialising page writer: %v" , err )
2021-02-07 00:17:59 +02:00
}
2021-03-31 11:30:42 +02:00
upstreamProxy , err := upstream . NewProxy ( opts . UpstreamServers , opts . GetSignatureData ( ) , pageWriter )
2020-05-26 21:06:27 +02:00
if err != nil {
return nil , fmt . Errorf ( "error initialising upstream proxy: %v" , err )
2015-11-16 05:08:30 +02:00
}
2019-10-04 15:07:31 +02:00
2019-01-17 22:49:14 +02:00
if opts . SkipJwtBearerTokens {
2021-04-03 18:06:30 +02:00
logger . Printf ( "Skipping JWT tokens from configured OIDC issuer: %q" , opts . Providers [ 0 ] . OIDCConfig . IssuerURL )
2019-01-17 22:49:14 +02:00
for _ , issuer := range opts . ExtraJwtIssuers {
logger . Printf ( "Skipping JWT tokens from extra JWT issuer: %q" , issuer )
}
}
2020-04-13 14:50:34 +02:00
redirectURL := opts . GetRedirectURL ( )
2019-03-20 18:25:04 +02:00
if redirectURL . Path == "" {
2019-03-05 16:58:26 +02:00
redirectURL . Path = fmt . Sprintf ( "%s/callback" , opts . ProxyPrefix )
}
2012-12-11 04:59:23 +03:00
2022-02-15 14:00:06 +02:00
logger . Printf ( "OAuthProxy configured for %s Client ID: %s" , provider . Data ( ) . ProviderName , opts . Providers [ 0 ] . ClientID )
2015-06-22 21:10:08 +02:00
refresh := "disabled"
2020-04-12 15:00:59 +02:00
if opts . Cookie . Refresh != time . Duration ( 0 ) {
refresh = fmt . Sprintf ( "after %s" , opts . Cookie . Refresh )
2015-06-22 21:10:08 +02:00
}
2015-03-18 05:13:45 +02:00
2020-04-12 15:00:59 +02:00
logger . Printf ( "Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domains:%s path:%s samesite:%s refresh:%s" , opts . Cookie . Name , opts . Cookie . Secure , opts . Cookie . HTTPOnly , opts . Cookie . Expire , strings . Join ( opts . Cookie . Domains , "," ) , opts . Cookie . Path , opts . Cookie . SameSite , refresh )
2015-03-18 05:13:45 +02:00
2020-07-11 12:10:58 +02:00
trustedIPs := ip . NewNetSet ( )
for _ , ipStr := range opts . TrustedIPs {
if ipNet := ip . ParseIPNet ( ipStr ) ; ipNet != nil {
trustedIPs . AddIPNet ( * ipNet )
} else {
return nil , fmt . Errorf ( "could not parse IP network (%s)" , ipStr )
}
}
2020-09-23 03:54:32 +02:00
allowedRoutes , err := buildRoutesAllowlist ( opts )
if err != nil {
return nil , err
}
2022-09-11 17:09:32 +02:00
apiRoutes , err := buildAPIRoutes ( opts )
if err != nil {
return nil , err
}
2022-12-23 11:08:12 +02:00
preAuthChain , err := buildPreAuthChain ( opts , sessionStore )
2020-07-29 21:10:14 +02:00
if err != nil {
return nil , fmt . Errorf ( "could not build pre-auth chain: %v" , err )
}
2022-02-15 14:00:06 +02:00
sessionChain := buildSessionChain ( opts , provider , sessionStore , basicAuthValidator )
2020-07-29 21:10:14 +02:00
headersChain , err := buildHeadersChain ( opts )
if err != nil {
return nil , fmt . Errorf ( "could not build headers chain: %v" , err )
}
2020-07-18 01:42:51 +02:00
2021-06-05 13:34:31 +02:00
redirectValidator := redirect . NewValidator ( opts . WhitelistDomains )
appDirector := redirect . NewAppDirector ( redirect . AppDirectorOpts {
ProxyPrefix : opts . ProxyPrefix ,
Validator : redirectValidator ,
} )
2021-02-14 19:08:04 +02:00
p := & OAuthProxy {
2021-04-21 11:33:27 +02:00
CookieOptions : & opts . Cookie ,
Validator : validator ,
2014-11-09 21:51:10 +02:00
2021-03-23 21:34:06 +02:00
SignInPath : fmt . Sprintf ( "%s/sign_in" , opts . ProxyPrefix ) ,
2015-05-30 00:47:40 +02:00
2021-02-12 19:53:01 +02:00
ProxyPrefix : opts . ProxyPrefix ,
2022-02-15 14:00:06 +02:00
provider : provider ,
2021-02-12 19:53:01 +02:00
sessionStore : sessionStore ,
redirectURL : redirectURL ,
2023-10-25 11:25:01 +02:00
relativeRedirectURL : opts . RelativeRedirectURL ,
2022-09-11 17:09:32 +02:00
apiRoutes : apiRoutes ,
2021-02-12 19:53:01 +02:00
allowedRoutes : allowedRoutes ,
whitelistDomains : opts . WhitelistDomains ,
skipAuthPreflight : opts . SkipAuthPreflight ,
skipJwtBearerTokens : opts . SkipJwtBearerTokens ,
realClientIPParser : opts . GetRealClientIPParser ( ) ,
SkipProviderButton : opts . SkipProviderButton ,
2021-10-05 11:24:47 +02:00
forceJSONErrors : opts . ForceJSONErrors ,
2021-02-12 19:53:01 +02:00
trustedIPs : trustedIPs ,
basicAuthValidator : basicAuthValidator ,
2021-09-26 22:25:34 +02:00
basicAuthGroups : opts . HtpasswdUserGroups ,
2021-02-12 19:53:01 +02:00
sessionChain : sessionChain ,
headersChain : headersChain ,
preAuthChain : preAuthChain ,
2021-02-12 20:25:46 +02:00
pageWriter : pageWriter ,
2021-03-23 21:34:06 +02:00
upstreamProxy : upstreamProxy ,
2021-06-05 13:34:31 +02:00
redirectValidator : redirectValidator ,
appDirector : appDirector ,
2021-02-14 19:08:04 +02:00
}
2021-03-23 21:34:06 +02:00
p . buildServeMux ( opts . ProxyPrefix )
2021-02-14 19:08:04 +02:00
if err := p . setupServer ( opts ) ; err != nil {
return nil , fmt . Errorf ( "error setting up server: %v" , err )
}
return p , nil
}
func ( p * OAuthProxy ) Start ( ) error {
if p . server == nil {
// We have to call setupServer before Start is called.
// If this doesn't happen it's a programming error.
panic ( "server has not been initialised" )
}
ctx , cancel := context . WithCancel ( context . Background ( ) )
// Observe signals in background goroutine.
go func ( ) {
sigint := make ( chan os . Signal , 1 )
signal . Notify ( sigint , os . Interrupt , syscall . SIGTERM )
<- sigint
cancel ( ) // cancel the context
} ( )
return p . server . Start ( ctx )
}
func ( p * OAuthProxy ) setupServer ( opts * options . Options ) error {
serverOpts := proxyhttp . Opts {
Handler : p ,
BindAddress : opts . Server . BindAddress ,
SecureBindAddress : opts . Server . SecureBindAddress ,
TLS : opts . Server . TLS ,
}
appServer , err := proxyhttp . NewServer ( serverOpts )
if err != nil {
return fmt . Errorf ( "could not build app server: %v" , err )
}
metricsServer , err := proxyhttp . NewServer ( proxyhttp . Opts {
Handler : middleware . DefaultMetricsHandler ,
BindAddress : opts . MetricsServer . BindAddress ,
2021-03-26 11:55:20 +02:00
SecureBindAddress : opts . MetricsServer . SecureBindAddress ,
2021-02-14 19:08:04 +02:00
TLS : opts . MetricsServer . TLS ,
} )
if err != nil {
return fmt . Errorf ( "could not build metrics server: %v" , err )
}
p . server = proxyhttp . NewServerGroup ( appServer , metricsServer )
return nil
2012-12-11 04:59:23 +03:00
}
2021-03-23 21:34:06 +02:00
func ( p * OAuthProxy ) buildServeMux ( proxyPrefix string ) {
2021-08-09 14:46:26 +02:00
// Use the encoded path here so we can have the option to pass it on in the upstream mux.
// Otherwise something like /%2F/ would be redirected to / here already.
r := mux . NewRouter ( ) . UseEncodedPath ( )
2021-03-23 21:34:06 +02:00
// Everything served by the router must go through the preAuthChain first.
r . Use ( p . preAuthChain . Then )
// Register the robots path writer
r . Path ( robotsPath ) . HandlerFunc ( p . pageWriter . WriteRobotsTxt )
// The authonly path should be registered separately to prevent it from getting no-cache headers.
// We do this to allow users to have a short cache (via nginx) of the response to reduce the
2023-08-22 18:25:30 +02:00
// likelihood of multiple requests trying to refresh sessions simultaneously.
2021-03-23 21:34:06 +02:00
r . Path ( proxyPrefix + authOnlyPath ) . Handler ( p . sessionChain . ThenFunc ( p . AuthOnly ) )
// This will register all of the paths under the proxy prefix, except the auth only path so that no cache headers
// are not applied.
p . buildProxySubrouter ( r . PathPrefix ( proxyPrefix ) . Subrouter ( ) )
// Register serveHTTP last so it catches anything that isn't already caught earlier.
// Anything that got to this point needs to have a session loaded.
r . PathPrefix ( "/" ) . Handler ( p . sessionChain . ThenFunc ( p . Proxy ) )
p . serveMux = r
}
func ( p * OAuthProxy ) buildProxySubrouter ( s * mux . Router ) {
s . Use ( prepareNoCacheMiddleware )
s . Path ( signInPath ) . HandlerFunc ( p . SignIn )
s . Path ( signOutPath ) . HandlerFunc ( p . SignOut )
s . Path ( oauthStartPath ) . HandlerFunc ( p . OAuthStart )
s . Path ( oauthCallbackPath ) . HandlerFunc ( p . OAuthCallback )
2023-02-23 21:00:19 +02:00
// Static file paths
s . PathPrefix ( staticPathPrefix ) . Handler ( http . StripPrefix ( p . ProxyPrefix , http . FileServer ( http . FS ( staticFiles ) ) ) )
2021-03-23 21:34:06 +02:00
// The userinfo endpoint needs to load sessions before handling the request
s . Path ( userInfoPath ) . Handler ( p . sessionChain . ThenFunc ( p . UserInfo ) )
}
2020-07-29 21:10:14 +02:00
// buildPreAuthChain constructs a chain that should process every request before
// the OAuth2 Proxy authentication logic kicks in.
// For example forcing HTTPS or health checks.
2022-12-23 11:08:12 +02:00
func buildPreAuthChain ( opts * options . Options , sessionStore sessionsapi . SessionStore ) ( alice . Chain , error ) {
2021-03-21 20:20:57 +02:00
chain := alice . New ( middleware . NewScope ( opts . ReverseProxy , opts . Logging . RequestIDHeader ) )
2020-07-18 01:42:51 +02:00
2020-07-29 21:10:14 +02:00
if opts . ForceHTTPS {
2021-02-14 19:08:04 +02:00
_ , httpsPort , err := net . SplitHostPort ( opts . Server . SecureBindAddress )
2020-07-29 21:10:14 +02:00
if err != nil {
2021-02-14 19:08:04 +02:00
return alice . Chain { } , fmt . Errorf ( "invalid HTTPS address %q: %v" , opts . Server . SecureBindAddress , err )
2020-07-29 21:10:14 +02:00
}
chain = chain . Append ( middleware . NewRedirectToHTTPS ( httpsPort ) )
}
healthCheckPaths := [ ] string { opts . PingPath }
healthCheckUserAgents := [ ] string { opts . PingUserAgent }
if opts . GCPHealthChecks {
2021-03-21 20:59:17 +02:00
logger . Printf ( "WARNING: GCP HealthChecks are now deprecated: Reconfigure apps to use the ping path for liveness and readiness checks, set the ping user agent to \"GoogleHC/1.0\" to preserve existing behaviour" )
2020-07-29 21:10:14 +02:00
healthCheckPaths = append ( healthCheckPaths , "/liveness_check" , "/readiness_check" )
healthCheckUserAgents = append ( healthCheckUserAgents , "GoogleHC/1.0" )
}
// To silence logging of health checks, register the health check handler before
// the logging handler
if opts . Logging . SilencePing {
2021-03-06 19:27:16 +02:00
chain = chain . Append (
middleware . NewHealthCheck ( healthCheckPaths , healthCheckUserAgents ) ,
2022-12-23 11:08:12 +02:00
middleware . NewReadynessCheck ( opts . ReadyPath , sessionStore ) ,
2021-03-06 19:27:16 +02:00
middleware . NewRequestLogger ( ) ,
)
2020-07-29 21:10:14 +02:00
} else {
2021-03-06 19:27:16 +02:00
chain = chain . Append (
middleware . NewRequestLogger ( ) ,
middleware . NewHealthCheck ( healthCheckPaths , healthCheckUserAgents ) ,
2022-12-23 11:08:12 +02:00
middleware . NewReadynessCheck ( opts . ReadyPath , sessionStore ) ,
2021-03-06 19:27:16 +02:00
)
2020-07-29 21:10:14 +02:00
}
2021-01-07 13:52:50 +02:00
chain = chain . Append ( middleware . NewRequestMetricsWithDefaultRegistry ( ) )
2020-07-29 21:10:14 +02:00
return chain , nil
}
2022-02-15 14:00:06 +02:00
func buildSessionChain ( opts * options . Options , provider providers . Provider , sessionStore sessionsapi . SessionStore , validator basic . Validator ) alice . Chain {
2020-07-29 21:10:14 +02:00
chain := alice . New ( )
2020-07-18 01:42:51 +02:00
if opts . SkipJwtBearerTokens {
2020-11-16 04:57:48 +02:00
sessionLoaders := [ ] middlewareapi . TokenToSessionFunc {
2022-02-15 14:00:06 +02:00
provider . CreateSessionFromToken ,
2020-07-18 01:42:51 +02:00
}
for _ , verifier := range opts . GetJWTBearerVerifiers ( ) {
2020-11-16 04:57:48 +02:00
sessionLoaders = append ( sessionLoaders ,
middlewareapi . CreateTokenToSessionFunc ( verifier . Verify ) )
2020-07-18 01:42:51 +02:00
}
chain = chain . Append ( middleware . NewJwtSessionLoader ( sessionLoaders ) )
}
if validator != nil {
2021-03-21 20:49:30 +02:00
chain = chain . Append ( middleware . NewBasicAuthSessionLoader ( validator , opts . HtpasswdUserGroups , opts . LegacyPreferEmailToUser ) )
2020-07-18 01:42:51 +02:00
}
chain = chain . Append ( middleware . NewStoredSessionLoader ( & middleware . StoredSessionLoaderOptions {
2021-03-07 01:33:13 +02:00
SessionStore : sessionStore ,
RefreshPeriod : opts . Cookie . Refresh ,
2022-02-15 14:00:06 +02:00
RefreshSession : provider . RefreshSession ,
ValidateSession : provider . ValidateSession ,
2020-07-18 01:42:51 +02:00
} ) )
return chain
}
2020-07-29 21:10:14 +02:00
func buildHeadersChain ( opts * options . Options ) ( alice . Chain , error ) {
requestInjector , err := middleware . NewRequestHeaderInjector ( opts . InjectRequestHeaders )
if err != nil {
return alice . Chain { } , fmt . Errorf ( "error constructing request header injector: %v" , err )
}
responseInjector , err := middleware . NewResponseHeaderInjector ( opts . InjectResponseHeaders )
if err != nil {
return alice . Chain { } , fmt . Errorf ( "error constructing request header injector: %v" , err )
}
return alice . New ( requestInjector , responseInjector ) , nil
}
2020-07-21 06:01:54 +02:00
func buildSignInMessage ( opts * options . Options ) string {
var msg string
2021-02-06 19:40:51 +02:00
if len ( opts . Templates . Banner ) >= 1 {
if opts . Templates . Banner == "-" {
2020-07-21 06:01:54 +02:00
msg = ""
} else {
2021-02-06 19:40:51 +02:00
msg = opts . Templates . Banner
2020-07-21 06:01:54 +02:00
}
} else if len ( opts . EmailDomains ) != 0 && opts . AuthenticatedEmailsFile == "" {
if len ( opts . EmailDomains ) > 1 {
msg = fmt . Sprintf ( "Authenticate using one of the following domains: %v" , strings . Join ( opts . EmailDomains , ", " ) )
} else if opts . EmailDomains [ 0 ] != "*" {
msg = fmt . Sprintf ( "Authenticate using %v" , opts . EmailDomains [ 0 ] )
}
}
return msg
}
2021-02-12 19:53:01 +02:00
func buildProviderName ( p providers . Provider , override string ) string {
if override != "" {
return override
}
return p . Data ( ) . ProviderName
}
2020-09-23 03:54:32 +02:00
// buildRoutesAllowlist builds an []allowedRoute list from either the legacy
// SkipAuthRegex option (paths only support) or newer SkipAuthRoutes option
// (method=path support)
2020-10-05 21:39:44 +02:00
func buildRoutesAllowlist ( opts * options . Options ) ( [ ] allowedRoute , error ) {
routes := make ( [ ] allowedRoute , 0 , len ( opts . SkipAuthRegex ) + len ( opts . SkipAuthRoutes ) )
2020-09-23 03:54:32 +02:00
for _ , path := range opts . SkipAuthRegex {
compiledRegex , err := regexp . Compile ( path )
if err != nil {
return nil , err
}
2020-09-26 21:38:01 +02:00
logger . Printf ( "Skipping auth - Method: ALL | Path: %s" , path )
2020-10-05 21:39:44 +02:00
routes = append ( routes , allowedRoute {
2020-09-23 03:54:32 +02:00
method : "" ,
pathRegex : compiledRegex ,
} )
}
for _ , methodPath := range opts . SkipAuthRoutes {
var (
method string
path string
2022-08-19 12:46:25 +02:00
negate = strings . Contains ( methodPath , "!=" )
2020-09-23 03:54:32 +02:00
)
2022-08-19 12:46:25 +02:00
parts := regexp . MustCompile ( "!?=" ) . Split ( methodPath , 2 )
2020-09-23 03:54:32 +02:00
if len ( parts ) == 1 {
method = ""
path = parts [ 0 ]
} else {
method = strings . ToUpper ( parts [ 0 ] )
2020-10-05 21:39:44 +02:00
path = parts [ 1 ]
2020-09-23 03:54:32 +02:00
}
compiledRegex , err := regexp . Compile ( path )
if err != nil {
return nil , err
}
2020-09-26 21:38:01 +02:00
logger . Printf ( "Skipping auth - Method: %s | Path: %s" , method , path )
2020-10-05 21:39:44 +02:00
routes = append ( routes , allowedRoute {
2020-09-23 03:54:32 +02:00
method : method ,
2022-08-19 12:46:25 +02:00
negate : negate ,
2020-09-23 03:54:32 +02:00
pathRegex : compiledRegex ,
} )
}
return routes , nil
}
2022-09-11 17:09:32 +02:00
// buildAPIRoutes builds an []apiRoute from ApiRoutes option
func buildAPIRoutes ( opts * options . Options ) ( [ ] apiRoute , error ) {
routes := make ( [ ] apiRoute , 0 , len ( opts . APIRoutes ) )
for _ , path := range opts . APIRoutes {
compiledRegex , err := regexp . Compile ( path )
if err != nil {
return nil , err
}
logger . Printf ( "API route - Path: %s" , path )
routes = append ( routes , apiRoute {
pathRegex : compiledRegex ,
} )
}
return routes , nil
}
2018-12-20 11:30:42 +02:00
// ClearSessionCookie creates a cookie to unset the user's authentication cookie
// stored in the user's session
2019-05-07 17:13:55 +02:00
func ( p * OAuthProxy ) ClearSessionCookie ( rw http . ResponseWriter , req * http . Request ) error {
return p . sessionStore . Clear ( rw , req )
2017-03-28 03:14:38 +02:00
}
2018-12-20 11:30:42 +02:00
// LoadCookiedSession reads the user's authentication details from the request
2019-05-07 17:13:55 +02:00
func ( p * OAuthProxy ) LoadCookiedSession ( req * http . Request ) ( * sessionsapi . SessionState , error ) {
return p . sessionStore . Load ( req )
2015-06-23 13:23:39 +02:00
}
2018-12-20 11:30:42 +02:00
// SaveSession creates a new session cookie value and sets this on the response
2019-05-07 15:27:09 +02:00
func ( p * OAuthProxy ) SaveSession ( rw http . ResponseWriter , req * http . Request , s * sessionsapi . SessionState ) error {
2019-05-07 17:13:55 +02:00
return p . sessionStore . Save ( rw , req , s )
2012-12-26 18:35:02 +03:00
}
2021-01-03 00:20:48 +02:00
func ( p * OAuthProxy ) ServeHTTP ( rw http . ResponseWriter , req * http . Request ) {
2021-03-23 21:34:06 +02:00
p . serveMux . ServeHTTP ( rw , req )
2015-05-10 21:15:52 +02:00
}
2018-12-20 11:30:42 +02:00
// ErrorPage writes an error response
2021-02-10 21:34:19 +02:00
func ( p * OAuthProxy ) ErrorPage ( rw http . ResponseWriter , req * http . Request , code int , appError string , messages ... interface { } ) {
2021-06-05 13:34:31 +02:00
redirectURL , err := p . appDirector . GetRedirect ( req )
2021-02-06 19:20:30 +02:00
if err != nil {
logger . Errorf ( "Error obtaining redirect: %v" , err )
}
if redirectURL == p . SignInPath || redirectURL == "" {
redirectURL = "/"
}
2021-03-21 20:20:57 +02:00
scope := middlewareapi . GetRequestScope ( req )
p . pageWriter . WriteErrorPage ( rw , pagewriter . ErrorPageOpts {
Status : code ,
RedirectURL : redirectURL ,
RequestID : scope . RequestID ,
AppError : appError ,
Messages : messages ,
} )
2012-12-17 21:15:23 +03:00
}
2021-01-03 00:20:48 +02:00
// IsAllowedRequest is used to check if auth should be skipped for this request
func ( p * OAuthProxy ) IsAllowedRequest ( req * http . Request ) bool {
isPreflightRequestAllowed := p . skipAuthPreflight && req . Method == "OPTIONS"
return isPreflightRequestAllowed || p . isAllowedRoute ( req ) || p . isTrustedIP ( req )
}
2022-08-19 12:46:25 +02:00
func isAllowedMethod ( req * http . Request , route allowedRoute ) bool {
return route . method == "" || req . Method == route . method
}
func isAllowedPath ( req * http . Request , route allowedRoute ) bool {
2023-06-14 17:49:08 +02:00
matches := route . pathRegex . MatchString ( requestutil . GetRequestURI ( req ) )
2022-08-19 12:46:25 +02:00
if route . negate {
return ! matches
}
return matches
}
2021-01-03 00:20:48 +02:00
// IsAllowedRoute is used to check if the request method & path is allowed without auth
func ( p * OAuthProxy ) isAllowedRoute ( req * http . Request ) bool {
for _ , route := range p . allowedRoutes {
2022-08-19 12:46:25 +02:00
if isAllowedMethod ( req , route ) && isAllowedPath ( req , route ) {
2021-01-03 00:20:48 +02:00
return true
}
}
return false
}
2022-09-11 17:09:32 +02:00
func ( p * OAuthProxy ) isAPIPath ( req * http . Request ) bool {
for _ , route := range p . apiRoutes {
2023-06-14 17:49:08 +02:00
if route . pathRegex . MatchString ( requestutil . GetRequestURI ( req ) ) {
2022-09-11 17:09:32 +02:00
return true
}
}
return false
}
2021-01-03 00:20:48 +02:00
// isTrustedIP is used to check if a request comes from a trusted client IP address.
func ( p * OAuthProxy ) isTrustedIP ( req * http . Request ) bool {
if p . trustedIPs == nil {
return false
}
remoteAddr , err := ip . GetClientIP ( p . realClientIPParser , req )
if err != nil {
logger . Errorf ( "Error obtaining real IP for trusted IP list: %v" , err )
// Possibly spoofed X-Real-IP header
return false
}
if remoteAddr == nil {
return false
}
return p . trustedIPs . Has ( remoteAddr )
}
2021-09-26 22:25:34 +02:00
// SignInPage writes the sign in template to the response
2015-11-09 01:57:01 +02:00
func ( p * OAuthProxy ) SignInPage ( rw http . ResponseWriter , req * http . Request , code int ) {
2020-04-09 16:39:07 +02:00
prepareNoCache ( rw )
2020-07-20 07:24:18 +02:00
err := p . ClearSessionCookie ( rw , req )
if err != nil {
2020-07-21 03:34:37 +02:00
logger . Printf ( "Error clearing session cookie: %v" , err )
2021-02-07 00:05:45 +02:00
p . ErrorPage ( rw , req , http . StatusInternalServerError , err . Error ( ) )
2020-07-20 07:24:18 +02:00
return
}
2012-12-17 21:15:23 +03:00
rw . WriteHeader ( code )
2012-12-26 18:35:02 +03:00
2021-06-05 13:34:31 +02:00
redirectURL , err := p . appDirector . GetRedirect ( req )
2020-02-28 11:59:27 +02:00
if err != nil {
2020-08-10 12:44:08 +02:00
logger . Errorf ( "Error obtaining redirect: %v" , err )
2021-02-07 00:05:45 +02:00
p . ErrorPage ( rw , req , http . StatusInternalServerError , err . Error ( ) )
2020-02-28 11:59:27 +02:00
return
2016-11-16 08:36:18 +02:00
}
2020-02-28 11:59:27 +02:00
if redirectURL == p . SignInPath {
redirectURL = "/"
2015-04-07 04:10:03 +02:00
}
2022-06-30 17:10:02 +02:00
p . pageWriter . WriteSignInPage ( rw , req , redirectURL , code )
2012-12-11 04:59:23 +03:00
}
2018-12-20 11:30:42 +02:00
// ManualSignIn handles basic auth logins to the proxy
2022-06-30 17:10:02 +02:00
func ( p * OAuthProxy ) ManualSignIn ( req * http . Request ) ( string , bool , int ) {
2020-07-18 11:18:47 +02:00
if req . Method != "POST" || p . basicAuthValidator == nil {
2022-06-30 17:10:02 +02:00
return "" , false , http . StatusOK
2012-12-26 18:55:41 +03:00
}
user := req . FormValue ( "username" )
passwd := req . FormValue ( "password" )
if user == "" {
2022-06-30 17:10:02 +02:00
return "" , false , http . StatusBadRequest
2012-12-26 18:55:41 +03:00
}
// check auth
2020-07-18 11:18:47 +02:00
if p . basicAuthValidator . Validate ( user , passwd ) {
2019-02-10 19:01:13 +02:00
logger . PrintAuthf ( user , req , logger . AuthSuccess , "Authenticated via HtpasswdFile" )
2022-06-30 17:10:02 +02:00
return user , true , http . StatusOK
2012-12-26 18:55:41 +03:00
}
2019-02-10 19:01:13 +02:00
logger . PrintAuthf ( user , req , logger . AuthFailure , "Invalid authentication via HtpasswdFile" )
2022-06-30 17:10:02 +02:00
return "" , false , http . StatusUnauthorized
2012-12-26 18:55:41 +03:00
}
2021-01-03 00:20:48 +02:00
// SignIn serves a page prompting users to sign in
func ( p * OAuthProxy ) SignIn ( rw http . ResponseWriter , req * http . Request ) {
2021-06-05 13:34:31 +02:00
redirect , err := p . appDirector . GetRedirect ( req )
2013-10-24 18:31:08 +03:00
if err != nil {
2021-01-03 00:20:48 +02:00
logger . Errorf ( "Error obtaining redirect: %v" , err )
2021-02-07 00:05:45 +02:00
p . ErrorPage ( rw , req , http . StatusInternalServerError , err . Error ( ) )
2021-01-03 00:20:48 +02:00
return
2021-01-02 23:46:05 +02:00
}
2022-06-30 17:10:02 +02:00
user , ok , statusCode := p . ManualSignIn ( req )
2021-01-03 00:20:48 +02:00
if ok {
2021-09-26 22:25:34 +02:00
session := & sessionsapi . SessionState { User : user , Groups : p . basicAuthGroups }
2021-01-03 00:20:48 +02:00
err = p . SaveSession ( rw , req , session )
if err != nil {
logger . Printf ( "Error saving session: %v" , err )
2021-02-07 00:05:45 +02:00
p . ErrorPage ( rw , req , http . StatusInternalServerError , err . Error ( ) )
2021-01-03 00:20:48 +02:00
return
}
http . Redirect ( rw , req , redirect , http . StatusFound )
} else {
if p . SkipProviderButton {
p . OAuthStart ( rw , req )
} else {
2022-02-16 18:18:51 +02:00
// TODO - should we pass on /oauth2/sign_in query params to /oauth2/start?
2022-06-30 17:10:02 +02:00
p . SignInPage ( rw , req , statusCode )
2021-01-02 23:46:05 +02:00
}
2013-10-24 18:31:08 +03:00
}
2021-01-02 23:46:05 +02:00
}
2021-01-02 01:23:11 +02:00
2021-02-17 22:15:45 +02:00
// UserInfo endpoint outputs session email and preferred username in JSON format
2021-01-03 00:20:48 +02:00
func ( p * OAuthProxy ) UserInfo ( rw http . ResponseWriter , req * http . Request ) {
session , err := p . getAuthenticatedSession ( rw , req )
if err != nil {
http . Error ( rw , http . StatusText ( http . StatusUnauthorized ) , http . StatusUnauthorized )
return
}
2021-01-02 23:46:05 +02:00
2021-03-23 21:34:06 +02:00
rw . Header ( ) . Set ( "Content-Type" , "application/json" )
rw . WriteHeader ( http . StatusOK )
if session == nil {
if _ , err := rw . Write ( [ ] byte ( "{}" ) ) ; err != nil {
logger . Printf ( "Error encoding empty user info: %v" , err )
p . ErrorPage ( rw , req , http . StatusInternalServerError , err . Error ( ) )
}
return
}
2021-01-03 00:20:48 +02:00
userInfo := struct {
User string ` json:"user" `
Email string ` json:"email" `
Groups [ ] string ` json:"groups,omitempty" `
PreferredUsername string ` json:"preferredUsername,omitempty" `
} {
User : session . User ,
Email : session . Email ,
Groups : session . Groups ,
PreferredUsername : session . PreferredUsername ,
2013-10-24 18:31:08 +03:00
}
2021-03-23 21:34:06 +02:00
if err := json . NewEncoder ( rw ) . Encode ( userInfo ) ; err != nil {
2021-01-03 00:20:48 +02:00
logger . Printf ( "Error encoding user info: %v" , err )
2021-02-07 00:05:45 +02:00
p . ErrorPage ( rw , req , http . StatusInternalServerError , err . Error ( ) )
2021-01-02 23:46:05 +02:00
}
2013-10-24 18:31:08 +03:00
}
2021-01-03 00:20:48 +02:00
// SignOut sends a response to clear the authentication cookie
func ( p * OAuthProxy ) SignOut ( rw http . ResponseWriter , req * http . Request ) {
2021-06-05 13:34:31 +02:00
redirect , err := p . appDirector . GetRedirect ( req )
2021-01-03 00:20:48 +02:00
if err != nil {
logger . Errorf ( "Error obtaining redirect: %v" , err )
2021-02-07 00:05:45 +02:00
p . ErrorPage ( rw , req , http . StatusInternalServerError , err . Error ( ) )
2021-01-03 00:20:48 +02:00
return
2021-01-02 23:46:05 +02:00
}
2021-01-03 00:20:48 +02:00
err = p . ClearSessionCookie ( rw , req )
if err != nil {
logger . Errorf ( "Error clearing session cookie: %v" , err )
2021-02-07 00:05:45 +02:00
p . ErrorPage ( rw , req , http . StatusInternalServerError , err . Error ( ) )
2021-01-03 00:20:48 +02:00
return
2021-01-02 01:23:11 +02:00
}
2021-01-03 00:20:48 +02:00
http . Redirect ( rw , req , redirect , http . StatusFound )
}
2017-03-21 18:39:26 +02:00
2018-12-20 11:30:42 +02:00
// OAuthStart starts the OAuth2 authentication flow
2015-11-09 01:57:01 +02:00
func ( p * OAuthProxy ) OAuthStart ( rw http . ResponseWriter , req * http . Request ) {
2022-02-16 18:18:51 +02:00
// start the flow permitting login URL query parameters to be overridden from the request URL
p . doOAuthStart ( rw , req , req . URL . Query ( ) )
}
func ( p * OAuthProxy ) doOAuthStart ( rw http . ResponseWriter , req * http . Request , overrides url . Values ) {
extraParams := p . provider . Data ( ) . LoginURLParams ( overrides )
2020-04-09 16:39:07 +02:00
prepareNoCache ( rw )
2021-04-21 11:33:27 +02:00
2022-11-19 02:43:31 +02:00
var (
err error
codeChallenge , codeVerifier , codeChallengeMethod string
)
2022-03-13 12:08:33 +02:00
if p . provider . Data ( ) . CodeChallengeMethod != "" {
codeChallengeMethod = p . provider . Data ( ) . CodeChallengeMethod
2022-11-19 02:43:31 +02:00
codeVerifier , err = encryption . GenerateRandomASCIIString ( 96 )
2022-03-13 12:08:33 +02:00
if err != nil {
2022-11-19 02:43:31 +02:00
logger . Errorf ( "Unable to build random ASCII string for code verifier: %v" , err )
2022-03-13 12:08:33 +02:00
p . ErrorPage ( rw , req , http . StatusInternalServerError , err . Error ( ) )
return
}
codeChallenge , err = encryption . GenerateCodeChallenge ( p . provider . Data ( ) . CodeChallengeMethod , codeVerifier )
if err != nil {
logger . Errorf ( "Error creating code challenge: %v" , err )
p . ErrorPage ( rw , req , http . StatusInternalServerError , err . Error ( ) )
return
}
extraParams . Add ( "code_challenge" , codeChallenge )
extraParams . Add ( "code_challenge_method" , codeChallengeMethod )
}
csrf , err := cookies . NewCSRF ( p . CookieOptions , codeVerifier )
2017-03-28 03:14:38 +02:00
if err != nil {
2021-04-21 11:33:27 +02:00
logger . Errorf ( "Error creating CSRF nonce: %v" , err )
2021-02-07 00:05:45 +02:00
p . ErrorPage ( rw , req , http . StatusInternalServerError , err . Error ( ) )
2017-03-28 03:14:38 +02:00
return
}
2021-04-21 11:33:27 +02:00
2021-06-05 13:34:31 +02:00
appRedirect , err := p . appDirector . GetRedirect ( req )
2015-06-23 13:23:39 +02:00
if err != nil {
2021-04-21 11:33:27 +02:00
logger . Errorf ( "Error obtaining application redirect: %v" , err )
2022-05-20 15:26:21 +02:00
p . ErrorPage ( rw , req , http . StatusBadRequest , err . Error ( ) )
2015-06-23 13:23:39 +02:00
return
2015-01-12 11:18:41 +02:00
}
2021-04-21 11:33:27 +02:00
callbackRedirect := p . getOAuthRedirectURI ( req )
loginURL := p . provider . GetLoginURL (
callbackRedirect ,
encodeState ( csrf . HashOAuthState ( ) , appRedirect ) ,
csrf . HashOIDCNonce ( ) ,
2022-02-16 18:18:51 +02:00
extraParams ,
2021-04-21 11:33:27 +02:00
)
if _ , err := csrf . SetCookie ( rw , req ) ; err != nil {
logger . Errorf ( "Error setting CSRF cookie: %v" , err )
p . ErrorPage ( rw , req , http . StatusInternalServerError , err . Error ( ) )
return
}
http . Redirect ( rw , req , loginURL , http . StatusFound )
2015-06-23 13:23:39 +02:00
}
2015-01-12 11:18:41 +02:00
2018-12-20 11:30:42 +02:00
// OAuthCallback is the OAuth2 authentication flow callback that finishes the
// OAuth2 authentication flow
2015-11-09 01:57:01 +02:00
func ( p * OAuthProxy ) OAuthCallback ( rw http . ResponseWriter , req * http . Request ) {
2020-05-23 16:17:41 +02:00
remoteAddr := ip . GetClientString ( p . realClientIPParser , req , true )
2013-10-24 18:31:08 +03:00
2015-06-23 13:23:39 +02:00
// finish the oauth cycle
err := req . ParseForm ( )
if err != nil {
2020-08-10 12:44:08 +02:00
logger . Errorf ( "Error while parsing OAuth2 callback: %v" , err )
2021-02-07 00:05:45 +02:00
p . ErrorPage ( rw , req , http . StatusInternalServerError , err . Error ( ) )
2012-12-11 04:59:23 +03:00
return
}
2015-06-23 13:23:39 +02:00
errorString := req . Form . Get ( "error" )
if errorString != "" {
2020-08-10 12:44:08 +02:00
logger . Errorf ( "Error while parsing OAuth2 callback: %s" , errorString )
2021-02-10 21:34:19 +02:00
message := fmt . Sprintf ( "Login Failed: The upstream identity provider returned an error: %s" , errorString )
// Set the debug message and override the non debug message to be the same for this case
p . ErrorPage ( rw , req , http . StatusForbidden , message , message )
2012-12-11 04:59:23 +03:00
return
}
2022-03-13 12:08:33 +02:00
csrf , err := cookies . LoadCSRFCookie ( req , p . CookieOptions )
2015-06-23 13:23:39 +02:00
if err != nil {
2022-03-13 12:08:33 +02:00
logger . Println ( req , logger . AuthFailure , "Invalid authentication via OAuth2: unable to obtain CSRF cookie" )
p . ErrorPage ( rw , req , http . StatusForbidden , err . Error ( ) , "Login Failed: Unable to find a valid CSRF token. Please try again." )
2015-06-23 13:23:39 +02:00
return
}
2022-03-13 12:08:33 +02:00
session , err := p . redeemCode ( req , csrf . GetCodeVerifier ( ) )
2020-09-26 22:19:08 +02:00
if err != nil {
2022-03-13 12:08:33 +02:00
logger . Errorf ( "Error redeeming code during OAuth2 callback: %v" , err )
2021-02-10 21:34:19 +02:00
p . ErrorPage ( rw , req , http . StatusInternalServerError , err . Error ( ) )
2020-09-26 22:19:08 +02:00
return
}
2022-03-13 12:08:33 +02:00
err = p . enrichSessionState ( req . Context ( ) , session )
2017-03-28 03:14:38 +02:00
if err != nil {
2022-03-13 12:08:33 +02:00
logger . Errorf ( "Error creating session during OAuth2 callback: %v" , err )
p . ErrorPage ( rw , req , http . StatusInternalServerError , err . Error ( ) )
2017-03-28 03:14:38 +02:00
return
}
2021-04-21 11:33:27 +02:00
csrf . ClearCookie ( rw , req )
nonce , appRedirect , err := decodeState ( req )
if err != nil {
logger . Errorf ( "Error while parsing OAuth2 state: %v" , err )
p . ErrorPage ( rw , req , http . StatusInternalServerError , err . Error ( ) )
return
}
if ! csrf . CheckOAuthState ( nonce ) {
2020-07-21 03:34:37 +02:00
logger . PrintAuthf ( session . Email , req , logger . AuthFailure , "Invalid authentication via OAuth2: CSRF token mismatch, potential attack" )
2021-02-10 21:34:19 +02:00
p . ErrorPage ( rw , req , http . StatusForbidden , "CSRF token mismatch, potential attack" , "Login Failed: Unable to find a valid CSRF token. Please try again." )
2017-03-28 03:14:38 +02:00
return
}
2021-04-21 11:33:27 +02:00
csrf . SetSessionNonce ( session )
2021-11-12 20:36:29 +02:00
if ! p . provider . ValidateSession ( req . Context ( ) , session ) {
logger . PrintAuthf ( session . Email , req , logger . AuthFailure , "Session validation failed: %s" , session )
p . ErrorPage ( rw , req , http . StatusForbidden , "Session validation failed" )
return
}
2021-04-21 11:33:27 +02:00
2021-06-05 13:34:31 +02:00
if ! p . redirectValidator . IsValidRedirect ( appRedirect ) {
2021-04-21 11:33:27 +02:00
appRedirect = "/"
2015-06-23 13:23:39 +02:00
}
// set cookie, or deny
2020-10-24 05:53:38 +02:00
authorized , err := p . provider . Authorize ( req . Context ( ) , session )
if err != nil {
logger . Errorf ( "Error with authorization: %v" , err )
}
if p . Validator ( session . Email ) && authorized {
2019-04-23 18:36:18 +02:00
logger . PrintAuthf ( session . Email , req , logger . AuthSuccess , "Authenticated via OAuth2: %s" , session )
2015-06-23 13:23:39 +02:00
err := p . SaveSession ( rw , req , session )
2012-12-11 04:59:23 +03:00
if err != nil {
2020-10-24 05:53:38 +02:00
logger . Errorf ( "Error saving session state for %s: %v" , remoteAddr , err )
2021-02-07 00:05:45 +02:00
p . ErrorPage ( rw , req , http . StatusInternalServerError , err . Error ( ) )
2012-12-11 04:59:23 +03:00
return
}
2021-04-21 11:33:27 +02:00
http . Redirect ( rw , req , appRedirect , http . StatusFound )
2015-06-23 13:23:39 +02:00
} else {
2019-06-20 22:40:04 +02:00
logger . PrintAuthf ( session . Email , req , logger . AuthFailure , "Invalid authentication via OAuth2: unauthorized" )
2021-02-10 21:34:19 +02:00
p . ErrorPage ( rw , req , http . StatusForbidden , "Invalid session: unauthorized" )
2015-06-23 13:23:39 +02:00
}
}
2012-12-11 04:59:23 +03:00
2022-03-13 12:08:33 +02:00
func ( p * OAuthProxy ) redeemCode ( req * http . Request , codeVerifier string ) ( * sessionsapi . SessionState , error ) {
2021-01-03 00:20:48 +02:00
code := req . Form . Get ( "code" )
if code == "" {
return nil , providers . ErrMissingCode
}
redirectURI := p . getOAuthRedirectURI ( req )
2022-03-13 12:08:33 +02:00
s , err := p . provider . Redeem ( req . Context ( ) , redirectURI , code , codeVerifier )
2021-01-03 00:20:48 +02:00
if err != nil {
return nil , err
}
2021-03-07 01:33:40 +02:00
// Force setting these in case the Provider didn't
if s . CreatedAt == nil {
s . CreatedAtNow ( )
}
if s . ExpiresOn == nil {
s . ExpiresIn ( p . CookieOptions . Expire )
}
2021-01-03 00:20:48 +02:00
return s , nil
}
func ( p * OAuthProxy ) enrichSessionState ( ctx context . Context , s * sessionsapi . SessionState ) error {
var err error
if s . Email == "" {
2021-02-17 22:15:45 +02:00
// TODO(@NickMeves): Remove once all provider are updated to implement EnrichSession
// nolint:staticcheck
2021-01-03 00:20:48 +02:00
s . Email , err = p . provider . GetEmailAddress ( ctx , s )
if err != nil && ! errors . Is ( err , providers . ErrNotImplemented ) {
return err
}
}
return p . provider . EnrichSession ( ctx , s )
}
2020-10-19 03:14:32 +02:00
// AuthOnly checks whether the user is currently logged in (both authentication
2020-11-20 06:27:28 +02:00
// and optional authorization).
2020-10-19 03:14:32 +02:00
func ( p * OAuthProxy ) AuthOnly ( rw http . ResponseWriter , req * http . Request ) {
2019-06-07 06:25:12 +02:00
session , err := p . getAuthenticatedSession ( rw , req )
2019-06-15 10:48:27 +02:00
if err != nil {
2020-11-18 05:03:41 +02:00
http . Error ( rw , http . StatusText ( http . StatusUnauthorized ) , http . StatusUnauthorized )
2019-06-15 10:48:27 +02:00
return
2015-10-08 15:27:00 +02:00
}
2019-06-15 10:48:27 +02:00
2020-11-20 06:27:28 +02:00
// Unauthorized cases need to return 403 to prevent infinite redirects with
// subrequest architectures
if ! authOnlyAuthorize ( req , session ) {
2020-11-18 05:03:41 +02:00
http . Error ( rw , http . StatusText ( http . StatusForbidden ) , http . StatusForbidden )
2020-10-19 03:14:32 +02:00
return
}
2019-06-15 10:48:27 +02:00
// we are authenticated
2021-01-03 00:20:48 +02:00
p . addHeadersForProxying ( rw , session )
2020-07-29 21:10:14 +02:00
p . headersChain . Then ( http . HandlerFunc ( func ( rw http . ResponseWriter , req * http . Request ) {
rw . WriteHeader ( http . StatusAccepted )
} ) ) . ServeHTTP ( rw , req )
2015-10-08 15:27:00 +02:00
}
2018-12-20 11:30:42 +02:00
// Proxy proxies the user request if the user is authenticated else it prompts
// them to authenticate
2015-11-09 01:57:01 +02:00
func ( p * OAuthProxy ) Proxy ( rw http . ResponseWriter , req * http . Request ) {
2019-06-07 05:50:44 +02:00
session , err := p . getAuthenticatedSession ( rw , req )
switch err {
case nil :
// we are authenticated
2021-01-03 00:20:48 +02:00
p . addHeadersForProxying ( rw , session )
2021-03-23 21:34:06 +02:00
p . headersChain . Then ( p . upstreamProxy ) . ServeHTTP ( rw , req )
2019-06-07 05:50:44 +02:00
case ErrNeedsLogin :
// we need to send the user to a login screen
2022-09-11 17:09:32 +02:00
if p . forceJSONErrors || isAjax ( req ) || p . isAPIPath ( req ) {
2021-10-13 19:48:09 +02:00
logger . Printf ( "No valid authentication in request. Access Denied." )
2019-06-07 05:50:44 +02:00
// no point redirecting an AJAX request
2021-01-03 00:20:48 +02:00
p . errorJSON ( rw , http . StatusUnauthorized )
2019-06-07 05:50:44 +02:00
return
}
2021-10-13 19:48:09 +02:00
logger . Printf ( "No valid authentication in request. Initiating login." )
2015-11-11 02:42:35 +02:00
if p . SkipProviderButton {
2022-02-16 18:18:51 +02:00
// start OAuth flow, but only with the default login URL params - do not
// consider this request's query params as potential overrides, since
// the user did not explicitly start the login flow
p . doOAuthStart ( rw , req , nil )
2015-11-11 02:42:35 +02:00
} else {
p . SignInPage ( rw , req , http . StatusForbidden )
}
2019-06-07 05:50:44 +02:00
2020-10-29 03:40:58 +02:00
case ErrAccessDenied :
2021-10-05 11:24:47 +02:00
if p . forceJSONErrors {
p . errorJSON ( rw , http . StatusForbidden )
} else {
p . ErrorPage ( rw , req , http . StatusForbidden , "The session failed authorization checks" )
}
2020-10-29 03:40:58 +02:00
2019-06-07 05:50:44 +02:00
default :
// unknown error
2020-08-10 12:44:08 +02:00
logger . Errorf ( "Unexpected internal error: %v" , err )
2021-02-10 21:34:19 +02:00
p . ErrorPage ( rw , req , http . StatusInternalServerError , err . Error ( ) )
2015-10-08 20:10:28 +02:00
}
2021-01-03 00:20:48 +02:00
}
// See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=en
var noCacheHeaders = map [ string ] string {
2021-03-07 01:48:31 +02:00
"Expires" : time . Unix ( 0 , 0 ) . Format ( time . RFC1123 ) ,
"Cache-Control" : "no-cache, no-store, must-revalidate, max-age=0" ,
"X-Accel-Expires" : "0" , // https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/
2021-01-03 00:20:48 +02:00
}
// prepareNoCache prepares headers for preventing browser caching.
func prepareNoCache ( w http . ResponseWriter ) {
// Set NoCache headers
for k , v := range noCacheHeaders {
w . Header ( ) . Set ( k , v )
}
}
2021-03-23 21:34:06 +02:00
func prepareNoCacheMiddleware ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( rw http . ResponseWriter , req * http . Request ) {
prepareNoCache ( rw )
next . ServeHTTP ( rw , req )
} )
}
2021-01-03 00:20:48 +02:00
// getOAuthRedirectURI returns the redirectURL that the upstream OAuth Provider will
// redirect clients to once authenticated.
// This is usually the OAuthProxy callback URL.
func ( p * OAuthProxy ) getOAuthRedirectURI ( req * http . Request ) string {
// if `p.redirectURL` already has a host, return it
2023-10-25 11:25:01 +02:00
if p . relativeRedirectURL || p . redirectURL . Host != "" {
2021-01-03 00:20:48 +02:00
return p . redirectURL . String ( )
}
// Otherwise figure out the scheme + host from the request
rd := * p . redirectURL
rd . Host = requestutil . GetRequestHost ( req )
rd . Scheme = requestutil . GetRequestProto ( req )
2021-02-14 13:38:20 +02:00
// If there's no scheme in the request, we should still include one
if rd . Scheme == "" {
rd . Scheme = schemeHTTP
}
2021-01-03 00:20:48 +02:00
// If CookieSecure is true, return `https` no matter what
// Not all reverse proxies set X-Forwarded-Proto
2021-04-21 11:33:27 +02:00
if p . CookieOptions . Secure {
2021-01-03 00:20:48 +02:00
rd . Scheme = schemeHTTPS
}
return rd . String ( )
}
2019-06-07 05:50:44 +02:00
// getAuthenticatedSession checks whether a user is authenticated and returns a session object and nil error if so
2020-10-29 03:40:58 +02:00
// Returns:
// - `nil, ErrNeedsLogin` if user needs to login.
// - `nil, ErrAccessDenied` if the authenticated user is not authorized
2019-06-07 05:50:44 +02:00
// Set-Cookie headers may be set on the response as a side-effect of calling this method.
func ( p * OAuthProxy ) getAuthenticatedSession ( rw http . ResponseWriter , req * http . Request ) ( * sessionsapi . SessionState , error ) {
2021-03-23 21:34:06 +02:00
session := middlewareapi . GetRequestScope ( req ) . Session
2015-06-23 13:23:39 +02:00
2021-03-23 21:34:06 +02:00
// Check this after loading the session so that if a valid session exists, we can add headers from it
if p . IsAllowedRequest ( req ) {
return session , nil
}
2015-06-23 13:23:39 +02:00
2019-01-17 22:49:14 +02:00
if session == nil {
2020-07-18 01:42:51 +02:00
return nil , ErrNeedsLogin
2015-06-23 13:23:39 +02:00
}
2013-10-22 22:56:29 +03:00
2020-09-27 02:29:34 +02:00
invalidEmail := session . Email != "" && ! p . Validator ( session . Email )
authorized , err := p . provider . Authorize ( req . Context ( ) , session )
if err != nil {
logger . Errorf ( "Error with authorization: %v" , err )
}
2020-07-28 20:42:09 +02:00
2020-09-27 04:00:44 +02:00
if invalidEmail || ! authorized {
2023-03-05 20:28:56 +02:00
cause := "unauthorized"
if invalidEmail {
cause = "invalid email"
}
logger . PrintAuthf ( session . Email , req , logger . AuthFailure , "Invalid authorization via session (%s): removing session %s" , cause , session )
2020-07-18 01:42:51 +02:00
// Invalid session, clear it
2020-07-20 07:24:18 +02:00
err := p . ClearSessionCookie ( rw , req )
if err != nil {
2020-09-27 02:29:34 +02:00
logger . Errorf ( "Error clearing session cookie: %v" , err )
2020-07-20 07:24:18 +02:00
}
2020-10-29 03:40:58 +02:00
return nil , ErrAccessDenied
2012-12-11 04:59:23 +03:00
}
2019-06-07 05:50:44 +02:00
return session , nil
}
2020-11-20 06:27:28 +02:00
// authOnlyAuthorize handles special authorization logic that is only done
// on the AuthOnly endpoint for use with Nginx subrequest architectures.
func authOnlyAuthorize ( req * http . Request , s * sessionsapi . SessionState ) bool {
2022-02-08 22:40:40 +02:00
// Allow requests previously allowed to be bypassed
if s == nil {
return true
}
2021-07-28 10:12:00 +02:00
constraints := [ ] func ( * http . Request , * sessionsapi . SessionState ) bool {
checkAllowedGroups ,
checkAllowedEmailDomains ,
2022-04-24 03:11:38 +02:00
checkAllowedEmails ,
2021-07-28 10:12:00 +02:00
}
for _ , constraint := range constraints {
if ! constraint ( req , s ) {
return false
}
2020-11-20 06:27:28 +02:00
}
return true
}
2021-07-28 10:12:00 +02:00
// extractAllowedEntities aims to extract and split allowed entities linked by a key,
// from an HTTP request query. Output is a map[string]struct{} where keys are valuable,
// the goal is to avoid time complexity O(N^2) while finding matches during membership checks.
func extractAllowedEntities ( req * http . Request , key string ) map [ string ] struct { } {
entities := map [ string ] struct { } { }
query := req . URL . Query ( )
for _ , allowedEntities := range query [ key ] {
for _ , entity := range strings . Split ( allowedEntities , "," ) {
if entity != "" {
entities [ entity ] = struct { } { }
}
}
}
return entities
}
// checkAllowedEmailDomains allow email domain restrictions based on the `allowed_email_domains`
// querystring parameter
func checkAllowedEmailDomains ( req * http . Request , s * sessionsapi . SessionState ) bool {
allowedEmailDomains := extractAllowedEntities ( req , "allowed_email_domains" )
if len ( allowedEmailDomains ) == 0 {
return true
}
splitEmail := strings . Split ( s . Email , "@" )
if len ( splitEmail ) != 2 {
return false
}
endpoint , _ := url . Parse ( "" )
endpoint . Host = splitEmail [ 1 ]
allowedEmailDomainsList := [ ] string { }
for ed := range allowedEmailDomains {
allowedEmailDomainsList = append ( allowedEmailDomainsList , ed )
}
return util . IsEndpointAllowed ( endpoint , allowedEmailDomainsList )
}
// checkAllowedGroups allow secondary group restrictions based on the `allowed_groups`
// querystring parameter
2020-11-20 06:27:28 +02:00
func checkAllowedGroups ( req * http . Request , s * sessionsapi . SessionState ) bool {
2021-07-28 10:12:00 +02:00
allowedGroups := extractAllowedEntities ( req , "allowed_groups" )
2020-10-19 03:14:32 +02:00
if len ( allowedGroups ) == 0 {
return true
}
2020-11-20 06:27:28 +02:00
for _ , group := range s . Groups {
2020-10-19 03:14:32 +02:00
if _ , ok := allowedGroups [ group ] ; ok {
return true
}
}
return false
}
2022-04-24 03:11:38 +02:00
// checkAllowedEmails allow email restrictions based on the `allowed_emails`
// querystring parameter
func checkAllowedEmails ( req * http . Request , s * sessionsapi . SessionState ) bool {
allowedEmails := extractAllowedEntities ( req , "allowed_emails" )
if len ( allowedEmails ) == 0 {
return true
}
allowed := false
for email := range allowedEmails {
if email == s . Email {
allowed = true
break
}
}
return allowed
}
2021-04-21 11:33:27 +02:00
// encodedState builds the OAuth state param out of our nonce and
// original application redirect
func encodeState ( nonce string , redirect string ) string {
return fmt . Sprintf ( "%v:%v" , nonce , redirect )
}
// decodeState splits the reflected OAuth state response back into
// the nonce and original application redirect
func decodeState ( req * http . Request ) ( string , string , error ) {
state := strings . SplitN ( req . Form . Get ( "state" ) , ":" , 2 )
if len ( state ) != 2 {
return "" , "" , errors . New ( "invalid length" )
}
return state [ 0 ] , state [ 1 ] , nil
}
2019-06-07 05:50:44 +02:00
// addHeadersForProxying adds the appropriate headers the request / response for proxying
2021-01-03 00:20:48 +02:00
func ( p * OAuthProxy ) addHeadersForProxying ( rw http . ResponseWriter , session * sessionsapi . SessionState ) {
2021-03-23 21:34:06 +02:00
if session == nil {
return
}
2015-06-23 13:23:39 +02:00
if session . Email == "" {
rw . Header ( ) . Set ( "GAP-Auth" , session . User )
2015-03-19 22:37:16 +02:00
} else {
2015-06-23 13:23:39 +02:00
rw . Header ( ) . Set ( "GAP-Auth" , session . Email )
2015-03-19 22:37:16 +02:00
}
2012-12-11 04:59:23 +03:00
}
2019-01-30 12:13:12 +02:00
// isAjax checks if a request is an ajax request
2019-06-07 05:50:44 +02:00
func isAjax ( req * http . Request ) bool {
2020-04-14 10:36:44 +02:00
acceptValues := req . Header . Values ( "Accept" )
2019-01-31 17:22:30 +02:00
const ajaxReq = applicationJSON
2021-01-12 17:40:14 +02:00
// Iterate over multiple Accept headers, i.e.
// Accept: application/json
// Accept: text/plain
for _ , mimeTypes := range acceptValues {
// Iterate over multiple mimetypes in a single header, i.e.
// Accept: application/json, text/plain, */*
for _ , mimeType := range strings . Split ( mimeTypes , "," ) {
mimeType = strings . TrimSpace ( mimeType )
if mimeType == ajaxReq {
return true
}
2019-01-30 12:13:12 +02:00
}
}
return false
}
2021-01-03 00:20:48 +02:00
// errorJSON returns the error code with an application/json mime type
func ( p * OAuthProxy ) errorJSON ( rw http . ResponseWriter , code int ) {
2019-01-31 17:22:30 +02:00
rw . Header ( ) . Set ( "Content-Type" , applicationJSON )
2019-01-30 12:13:12 +02:00
rw . WriteHeader ( code )
2021-10-05 11:24:47 +02:00
// we need to send some JSON response because we set the Content-Type to
// application/json
rw . Write ( [ ] byte ( "{}" ) )
2019-01-30 12:13:12 +02:00
}