2012-12-11 05:59:23 +04:00
package main
import (
2019-01-17 23:49:14 +03:00
"context"
2019-11-08 01:38:36 +03:00
"encoding/json"
2012-12-11 05:59:23 +04:00
"errors"
"fmt"
2015-03-18 01:06:06 +03:00
"html/template"
2015-03-19 22:59:48 +03:00
"net"
2012-12-11 05:59:23 +04:00
"net/http"
"net/url"
2015-01-19 19:10:37 +03:00
"regexp"
2012-12-11 05:59:23 +04:00
"strings"
"time"
2014-08-08 00:16:39 +04:00
2019-01-17 23:49:14 +03:00
"github.com/coreos/go-oidc"
2020-07-18 02:42:51 +03:00
"github.com/justinas/alice"
2020-09-29 19:44:42 +03: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"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/basic"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
"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"
"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/pkg/util"
"github.com/oauth2-proxy/oauth2-proxy/v7/providers"
2012-12-11 05:59:23 +04:00
)
2018-11-29 17:26:41 +03:00
const (
httpScheme = "http"
httpsScheme = "https"
2018-01-28 01:48:52 +03:00
2019-01-31 18:22:30 +03:00
applicationJSON = "application/json"
2018-11-29 17:26:41 +03:00
)
2019-06-07 06:50:44 +03:00
var (
// ErrNeedsLogin means the user should be redirected to the login page
ErrNeedsLogin = errors . New ( "redirect to login page" )
2020-05-06 14:42:02 +03:00
2020-10-29 04:40:58 +03:00
// ErrAccessDenied means the user should receive a 401 Unauthorized response
ErrAccessDenied = errors . New ( "access denied" )
2020-05-06 14:42:02 +03:00
// Used to check final redirects are not susceptible to open redirects.
// Matches //, /\ and both of these with whitespace in between (eg / / or / \).
2020-06-27 14:07:24 +03:00
invalidRedirectRegex = regexp . MustCompile ( ` [/\\](?:[\s\v]*|\. { 1,2})[/\\] ` )
2019-06-07 06:50:44 +03:00
)
2020-09-23 04:54:32 +03:00
// allowedRoute manages method + path based allowlists
type allowedRoute struct {
method string
pathRegex * regexp . Regexp
}
2018-12-20 12:30:42 +03:00
// OAuthProxy is the main authentication proxy
2015-11-09 02:57:01 +03:00
type OAuthProxy struct {
2015-03-18 06:13:45 +03:00
CookieSeed string
2015-06-08 06:52:28 +03:00
CookieName string
2017-03-28 04:14:38 +03:00
CSRFCookieName string
2020-04-12 14:00:44 +03:00
CookieDomains [ ] string
2019-04-10 00:36:35 +03:00
CookiePath string
2015-03-18 06:13:45 +03:00
CookieSecure bool
2018-11-29 17:26:41 +03:00
CookieHTTPOnly bool
2015-03-18 06:13:45 +03:00
CookieExpire time . Duration
2015-05-08 17:00:57 +03:00
CookieRefresh time . Duration
2019-12-16 21:10:04 +03:00
CookieSameSite string
2015-03-18 06:13:45 +03:00
Validator func ( string ) bool
2012-12-11 05:59:23 +04:00
2015-05-30 01:47:40 +03:00
RobotsPath string
SignInPath string
2017-03-21 19:39:26 +03:00
SignOutPath string
2015-11-09 02:57:01 +03:00
OAuthStartPath string
OAuthCallbackPath string
2015-10-08 16:27:00 +03:00
AuthOnlyPath string
2019-11-08 01:38:36 +03:00
UserInfoPath string
2015-05-30 01:47:40 +03:00
2020-10-05 22:39:44 +03:00
allowedRoutes [ ] allowedRoute
2020-05-31 00:16:26 +03:00
redirectURL * url . URL // the url to receive requests at
whitelistDomains [ ] string
provider providers . Provider
providerNameOverride string
sessionStore sessionsapi . SessionStore
ProxyPrefix string
SignInMessage string
2020-07-18 12:18:47 +03:00
basicAuthValidator basic . Validator
displayHtpasswdForm bool
2020-05-31 00:16:26 +03:00
serveMux http . Handler
SetXAuthRequest bool
PassBasicAuth bool
SetBasicAuth bool
SkipProviderButton bool
PassUserHeaders bool
BasicAuthPassword string
PassAccessToken bool
SetAuthorization bool
PassAuthorization bool
PreferEmailToUser bool
skipAuthPreflight bool
skipJwtBearerTokens bool
mainJwtBearerVerifier * oidc . IDTokenVerifier
extraJwtBearerVerifiers [ ] * oidc . IDTokenVerifier
templates * template . Template
realClientIPParser ipapi . RealClientIPParser
2020-07-11 13:10:58 +03:00
trustedIPs * ip . NetSet
2020-05-31 00:16:26 +03:00
Banner string
Footer string
2020-07-18 02:42:51 +03:00
sessionChain alice . Chain
2020-07-29 22:10:14 +03:00
headersChain alice . Chain
preAuthChain alice . Chain
2012-12-11 05:59:23 +04:00
}
2019-12-20 17:44:59 +03:00
// NewOAuthProxy creates a new instance of OAuthProxy from the options provided
2020-05-25 16:00:49 +03: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 )
}
2020-05-26 22:06:27 +03:00
templates := loadTemplates ( opts . CustomTemplatesDir )
proxyErrorHandler := upstream . NewProxyErrorHandler ( templates . Lookup ( "error.html" ) , opts . ProxyPrefix )
upstreamProxy , err := upstream . NewProxy ( opts . UpstreamServers , opts . GetSignatureData ( ) , proxyErrorHandler )
if err != nil {
return nil , fmt . Errorf ( "error initialising upstream proxy: %v" , err )
2015-11-16 06:08:30 +03:00
}
2019-10-04 16:07:31 +03:00
2019-01-17 23:49:14 +03:00
if opts . SkipJwtBearerTokens {
logger . Printf ( "Skipping JWT tokens from configured OIDC issuer: %q" , opts . OIDCIssuerURL )
for _ , issuer := range opts . ExtraJwtIssuers {
logger . Printf ( "Skipping JWT tokens from extra JWT issuer: %q" , issuer )
}
}
2020-04-13 15:50:34 +03:00
redirectURL := opts . GetRedirectURL ( )
2019-03-20 19:25:04 +03:00
if redirectURL . Path == "" {
2019-03-05 17:58:26 +03:00
redirectURL . Path = fmt . Sprintf ( "%s/callback" , opts . ProxyPrefix )
}
2012-12-11 05:59:23 +04:00
2020-04-13 15:50:34 +03:00
logger . Printf ( "OAuthProxy configured for %s Client ID: %s" , opts . GetProvider ( ) . Data ( ) . ProviderName , opts . ClientID )
2015-06-22 22:10:08 +03:00
refresh := "disabled"
2020-04-12 16:00:59 +03:00
if opts . Cookie . Refresh != time . Duration ( 0 ) {
refresh = fmt . Sprintf ( "after %s" , opts . Cookie . Refresh )
2015-06-22 22:10:08 +03:00
}
2015-03-18 06:13:45 +03:00
2020-04-12 16:00:59 +03: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 06:13:45 +03:00
2020-07-11 13:10:58 +03: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-07-18 12:18:47 +03: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 {
return nil , fmt . Errorf ( "could not load htpasswdfile: %v" , err )
}
}
2020-09-23 04:54:32 +03:00
allowedRoutes , err := buildRoutesAllowlist ( opts )
if err != nil {
return nil , err
}
2020-07-29 22:10:14 +03:00
preAuthChain , err := buildPreAuthChain ( opts )
if err != nil {
return nil , fmt . Errorf ( "could not build pre-auth chain: %v" , err )
}
2020-07-18 02:42:51 +03:00
sessionChain := buildSessionChain ( opts , sessionStore , basicAuthValidator )
2020-07-29 22:10:14 +03:00
headersChain , err := buildHeadersChain ( opts )
if err != nil {
return nil , fmt . Errorf ( "could not build headers chain: %v" , err )
}
2020-07-18 02:42:51 +03:00
2015-11-09 02:57:01 +03:00
return & OAuthProxy {
2020-04-12 16:00:59 +03:00
CookieName : opts . Cookie . Name ,
CSRFCookieName : fmt . Sprintf ( "%v_%v" , opts . Cookie . Name , "csrf" ) ,
CookieSeed : opts . Cookie . Secret ,
CookieDomains : opts . Cookie . Domains ,
CookiePath : opts . Cookie . Path ,
CookieSecure : opts . Cookie . Secure ,
CookieHTTPOnly : opts . Cookie . HTTPOnly ,
CookieExpire : opts . Cookie . Expire ,
CookieRefresh : opts . Cookie . Refresh ,
CookieSameSite : opts . Cookie . SameSite ,
2015-03-18 06:13:45 +03:00
Validator : validator ,
2014-11-09 22:51:10 +03:00
2015-05-30 01:47:40 +03:00
RobotsPath : "/robots.txt" ,
SignInPath : fmt . Sprintf ( "%s/sign_in" , opts . ProxyPrefix ) ,
2017-03-21 19:39:26 +03:00
SignOutPath : fmt . Sprintf ( "%s/sign_out" , opts . ProxyPrefix ) ,
2015-11-09 02:57:01 +03:00
OAuthStartPath : fmt . Sprintf ( "%s/start" , opts . ProxyPrefix ) ,
2019-03-12 19:46:37 +03:00
OAuthCallbackPath : fmt . Sprintf ( "%s/callback" , opts . ProxyPrefix ) ,
2015-10-08 16:27:00 +03:00
AuthOnlyPath : fmt . Sprintf ( "%s/auth" , opts . ProxyPrefix ) ,
2019-11-08 01:38:36 +03:00
UserInfoPath : fmt . Sprintf ( "%s/userinfo" , opts . ProxyPrefix ) ,
2015-05-30 01:47:40 +03:00
2020-05-31 00:16:26 +03:00
ProxyPrefix : opts . ProxyPrefix ,
provider : opts . GetProvider ( ) ,
providerNameOverride : opts . ProviderName ,
2020-05-25 16:00:49 +03:00
sessionStore : sessionStore ,
2020-05-26 22:06:27 +03:00
serveMux : upstreamProxy ,
2020-05-31 00:16:26 +03:00
redirectURL : redirectURL ,
2020-09-23 04:54:32 +03:00
allowedRoutes : allowedRoutes ,
2020-05-31 00:16:26 +03:00
whitelistDomains : opts . WhitelistDomains ,
skipAuthPreflight : opts . SkipAuthPreflight ,
skipJwtBearerTokens : opts . SkipJwtBearerTokens ,
mainJwtBearerVerifier : opts . GetOIDCVerifier ( ) ,
extraJwtBearerVerifiers : opts . GetJWTBearerVerifiers ( ) ,
realClientIPParser : opts . GetRealClientIPParser ( ) ,
SkipProviderButton : opts . SkipProviderButton ,
2020-05-26 22:06:27 +03:00
templates : templates ,
2020-07-11 13:10:58 +03:00
trustedIPs : trustedIPs ,
2020-05-31 00:16:26 +03:00
Banner : opts . Banner ,
Footer : opts . Footer ,
2020-07-21 07:01:54 +03:00
SignInMessage : buildSignInMessage ( opts ) ,
2020-07-18 12:18:47 +03:00
basicAuthValidator : basicAuthValidator ,
2020-11-08 21:48:01 +03:00
displayHtpasswdForm : basicAuthValidator != nil && opts . DisplayHtpasswdForm ,
2020-07-18 02:42:51 +03:00
sessionChain : sessionChain ,
2020-07-29 22:10:14 +03:00
headersChain : headersChain ,
preAuthChain : preAuthChain ,
2020-05-25 16:00:49 +03:00
} , nil
2012-12-11 05:59:23 +04:00
}
2020-07-29 22:10:14 +03: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.
func buildPreAuthChain ( opts * options . Options ) ( alice . Chain , error ) {
2020-07-18 02:42:51 +03:00
chain := alice . New ( middleware . NewScope ( ) )
2020-07-29 22:10:14 +03:00
if opts . ForceHTTPS {
_ , httpsPort , err := net . SplitHostPort ( opts . HTTPSAddress )
if err != nil {
return alice . Chain { } , fmt . Errorf ( "invalid HTTPS address %q: %v" , opts . HTTPAddress , err )
}
chain = chain . Append ( middleware . NewRedirectToHTTPS ( httpsPort ) )
}
healthCheckPaths := [ ] string { opts . PingPath }
healthCheckUserAgents := [ ] string { opts . PingUserAgent }
if opts . GCPHealthChecks {
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 {
chain = chain . Append ( middleware . NewHealthCheck ( healthCheckPaths , healthCheckUserAgents ) , LoggingHandler )
} else {
chain = chain . Append ( LoggingHandler , middleware . NewHealthCheck ( healthCheckPaths , healthCheckUserAgents ) )
}
return chain , nil
}
func buildSessionChain ( opts * options . Options , sessionStore sessionsapi . SessionStore , validator basic . Validator ) alice . Chain {
chain := alice . New ( )
2020-07-18 02:42:51 +03:00
if opts . SkipJwtBearerTokens {
sessionLoaders := [ ] middlewareapi . TokenToSessionLoader { }
if opts . GetOIDCVerifier ( ) != nil {
sessionLoaders = append ( sessionLoaders , middlewareapi . TokenToSessionLoader {
Verifier : opts . GetOIDCVerifier ( ) ,
TokenToSession : opts . GetProvider ( ) . CreateSessionStateFromBearerToken ,
} )
}
for _ , verifier := range opts . GetJWTBearerVerifiers ( ) {
sessionLoaders = append ( sessionLoaders , middlewareapi . TokenToSessionLoader {
Verifier : verifier ,
} )
}
chain = chain . Append ( middleware . NewJwtSessionLoader ( sessionLoaders ) )
}
if validator != nil {
chain = chain . Append ( middleware . NewBasicAuthSessionLoader ( validator ) )
}
chain = chain . Append ( middleware . NewStoredSessionLoader ( & middleware . StoredSessionLoaderOptions {
SessionStore : sessionStore ,
RefreshPeriod : opts . Cookie . Refresh ,
RefreshSessionIfNeeded : opts . GetProvider ( ) . RefreshSessionIfNeeded ,
ValidateSessionState : opts . GetProvider ( ) . ValidateSessionState ,
} ) )
return chain
}
2020-07-29 22:10:14 +03: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 07:01:54 +03:00
func buildSignInMessage ( opts * options . Options ) string {
var msg string
if len ( opts . Banner ) >= 1 {
if opts . Banner == "-" {
msg = ""
} else {
msg = opts . Banner
}
} 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
}
2020-09-23 04:54:32 +03: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 22:39:44 +03:00
func buildRoutesAllowlist ( opts * options . Options ) ( [ ] allowedRoute , error ) {
routes := make ( [ ] allowedRoute , 0 , len ( opts . SkipAuthRegex ) + len ( opts . SkipAuthRoutes ) )
2020-09-23 04:54:32 +03:00
for _ , path := range opts . SkipAuthRegex {
compiledRegex , err := regexp . Compile ( path )
if err != nil {
return nil , err
}
2020-09-26 22:38:01 +03:00
logger . Printf ( "Skipping auth - Method: ALL | Path: %s" , path )
2020-10-05 22:39:44 +03:00
routes = append ( routes , allowedRoute {
2020-09-23 04:54:32 +03:00
method : "" ,
pathRegex : compiledRegex ,
} )
}
for _ , methodPath := range opts . SkipAuthRoutes {
var (
method string
path string
)
2020-10-05 22:39:44 +03:00
parts := strings . SplitN ( methodPath , "=" , 2 )
2020-09-23 04:54:32 +03:00
if len ( parts ) == 1 {
method = ""
path = parts [ 0 ]
} else {
method = strings . ToUpper ( parts [ 0 ] )
2020-10-05 22:39:44 +03:00
path = parts [ 1 ]
2020-09-23 04:54:32 +03:00
}
compiledRegex , err := regexp . Compile ( path )
if err != nil {
return nil , err
}
2020-09-26 22:38:01 +03:00
logger . Printf ( "Skipping auth - Method: %s | Path: %s" , method , path )
2020-10-05 22:39:44 +03:00
routes = append ( routes , allowedRoute {
2020-09-23 04:54:32 +03:00
method : method ,
pathRegex : compiledRegex ,
} )
}
return routes , nil
}
2018-12-20 12:30:42 +03:00
// GetRedirectURI returns the redirectURL that the upstream OAuth Provider will
// redirect clients to once authenticated
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) GetRedirectURI ( host string ) string {
2015-03-17 23:25:19 +03:00
// default to the request Host if not set
2015-11-09 02:47:44 +03:00
if p . redirectURL . Host != "" {
return p . redirectURL . String ( )
2015-03-17 23:25:19 +03:00
}
2020-04-14 11:36:44 +03:00
u := * p . redirectURL
2015-03-17 23:25:19 +03:00
if u . Scheme == "" {
2015-03-18 06:13:45 +03:00
if p . CookieSecure {
2018-11-29 17:26:41 +03:00
u . Scheme = httpsScheme
2015-03-17 23:25:19 +03:00
} else {
2018-11-29 17:26:41 +03:00
u . Scheme = httpScheme
2015-03-17 23:25:19 +03:00
}
}
u . Host = host
return u . String ( )
}
2020-09-24 02:04:54 +03:00
func ( p * OAuthProxy ) redeemCode ( ctx context . Context , host , code string ) ( * sessionsapi . SessionState , error ) {
2013-10-22 23:56:29 +04:00
if code == "" {
2020-10-24 05:35:15 +03:00
return nil , providers . ErrMissingCode
2013-10-22 23:56:29 +04:00
}
2015-11-09 02:50:42 +03:00
redirectURI := p . GetRedirectURI ( host )
2020-09-24 02:04:54 +03:00
s , err := p . provider . Redeem ( ctx , redirectURI , code )
2012-12-11 05:59:23 +04:00
if err != nil {
2020-09-24 02:04:54 +03:00
return nil , err
2012-12-11 05:59:23 +04:00
}
2020-09-26 23:19:08 +03:00
return s , nil
}
2012-12-17 22:15:23 +04:00
2020-09-27 21:46:29 +03:00
func ( p * OAuthProxy ) enrichSessionState ( ctx context . Context , s * sessionsapi . SessionState ) error {
2020-09-26 23:19:08 +03:00
var err error
2015-06-23 14:23:39 +03:00
if s . Email == "" {
2020-05-05 18:53:33 +03:00
s . Email , err = p . provider . GetEmailAddress ( ctx , s )
2020-09-27 00:06:52 +03:00
if err != nil && ! errors . Is ( err , providers . ErrNotImplemented ) {
2020-09-26 23:19:08 +03:00
return err
2020-09-24 02:04:54 +03:00
}
2014-08-08 00:16:39 +04:00
}
2020-09-27 21:46:29 +03:00
return p . provider . EnrichSessionState ( ctx , s )
2014-08-08 00:16:39 +04:00
}
2018-12-20 12:30:42 +03:00
// MakeCSRFCookie creates a cookie for CSRF
2017-03-28 04:14:38 +03:00
func ( p * OAuthProxy ) MakeCSRFCookie ( req * http . Request , value string , expiration time . Duration , now time . Time ) * http . Cookie {
return p . makeCookie ( req , p . CSRFCookieName , value , expiration , now )
}
func ( p * OAuthProxy ) makeCookie ( req * http . Request , name string , value string , expiration time . Duration , now time . Time ) * http . Cookie {
2020-04-12 14:00:44 +03:00
cookieDomain := cookies . GetCookieDomain ( req , p . CookieDomains )
if cookieDomain != "" {
2020-08-22 05:50:32 +03:00
domain := util . GetRequestHost ( req )
2017-04-19 06:33:50 +03:00
if h , _ , err := net . SplitHostPort ( domain ) ; err == nil {
domain = h
}
2020-04-12 14:00:44 +03:00
if ! strings . HasSuffix ( domain , cookieDomain ) {
2020-08-10 13:44:08 +03:00
logger . Errorf ( "Warning: request host is %q but using configured cookie domain of %q" , domain , cookieDomain )
2015-03-19 22:59:48 +03:00
}
2012-12-11 05:59:23 +04:00
}
2015-05-08 18:51:11 +03:00
return & http . Cookie {
2017-03-28 04:14:38 +03:00
Name : name ,
2015-05-08 18:51:11 +03:00
Value : value ,
2019-04-10 00:36:35 +03:00
Path : p . CookiePath ,
2020-04-12 14:00:44 +03:00
Domain : cookieDomain ,
2018-11-29 17:26:41 +03:00
HttpOnly : p . CookieHTTPOnly ,
2015-03-19 22:59:48 +03:00
Secure : p . CookieSecure ,
2015-06-22 22:10:08 +03:00
Expires : now . Add ( expiration ) ,
2019-12-16 21:10:04 +03:00
SameSite : cookies . ParseSameSite ( p . CookieSameSite ) ,
2012-12-11 05:59:23 +04:00
}
}
2018-12-20 12:30:42 +03:00
// ClearCSRFCookie creates a cookie to unset the CSRF cookie stored in the user's
// session
2017-03-28 04:14:38 +03:00
func ( p * OAuthProxy ) ClearCSRFCookie ( rw http . ResponseWriter , req * http . Request ) {
http . SetCookie ( rw , p . MakeCSRFCookie ( req , "" , time . Hour * - 1 , time . Now ( ) ) )
}
2018-12-20 12:30:42 +03:00
// SetCSRFCookie adds a CSRF cookie to the response
2017-03-28 04:14:38 +03:00
func ( p * OAuthProxy ) SetCSRFCookie ( rw http . ResponseWriter , req * http . Request , val string ) {
http . SetCookie ( rw , p . MakeCSRFCookie ( req , val , p . CookieExpire , time . Now ( ) ) )
2012-12-11 05:59:23 +04:00
}
2018-12-20 12:30:42 +03:00
// ClearSessionCookie creates a cookie to unset the user's authentication cookie
// stored in the user's session
2019-05-07 18:13:55 +03:00
func ( p * OAuthProxy ) ClearSessionCookie ( rw http . ResponseWriter , req * http . Request ) error {
return p . sessionStore . Clear ( rw , req )
2017-03-28 04:14:38 +03:00
}
2018-12-20 12:30:42 +03:00
// LoadCookiedSession reads the user's authentication details from the request
2019-05-07 18:13:55 +03:00
func ( p * OAuthProxy ) LoadCookiedSession ( req * http . Request ) ( * sessionsapi . SessionState , error ) {
return p . sessionStore . Load ( req )
2015-06-23 14:23:39 +03:00
}
2018-12-20 12:30:42 +03:00
// SaveSession creates a new session cookie value and sets this on the response
2019-05-07 16:27:09 +03:00
func ( p * OAuthProxy ) SaveSession ( rw http . ResponseWriter , req * http . Request , s * sessionsapi . SessionState ) error {
2019-05-07 18:13:55 +03:00
return p . sessionStore . Save ( rw , req , s )
2012-12-26 19:35:02 +04:00
}
2018-12-20 12:30:42 +03:00
// RobotsTxt disallows scraping pages from the OAuthProxy
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) RobotsTxt ( rw http . ResponseWriter ) {
2020-07-20 08:24:18 +03:00
_ , err := fmt . Fprintf ( rw , "User-agent: *\nDisallow: /" )
if err != nil {
2020-07-21 04:34:37 +03:00
logger . Printf ( "Error writing robots.txt: %v" , err )
p . ErrorPage ( rw , http . StatusInternalServerError , "Internal Server Error" , err . Error ( ) )
return
2020-07-20 08:24:18 +03:00
}
2020-07-21 04:34:37 +03:00
rw . WriteHeader ( http . StatusOK )
2015-05-10 22:15:52 +03:00
}
2018-12-20 12:30:42 +03:00
// ErrorPage writes an error response
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) ErrorPage ( rw http . ResponseWriter , code int , title string , message string ) {
2012-12-11 05:59:23 +04:00
rw . WriteHeader ( code )
2012-12-17 22:15:23 +04:00
t := struct {
2015-10-04 01:59:47 +03:00
Title string
Message string
ProxyPrefix string
2012-12-11 05:59:23 +04:00
} {
2015-10-04 01:59:47 +03:00
Title : fmt . Sprintf ( "%d %s" , code , title ) ,
Message : message ,
ProxyPrefix : p . ProxyPrefix ,
2012-12-11 05:59:23 +04:00
}
2020-07-20 08:24:18 +03:00
err := p . templates . ExecuteTemplate ( rw , "error.html" , t )
if err != nil {
2020-07-21 04:34:37 +03:00
logger . Printf ( "Error rendering error.html template: %v" , err )
2020-07-20 08:24:18 +03:00
http . Error ( rw , "Internal Server Error" , http . StatusInternalServerError )
}
2012-12-17 22:15:23 +04:00
}
2018-12-20 12:30:42 +03:00
// SignInPage writes the sing in template to the response
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) SignInPage ( rw http . ResponseWriter , req * http . Request , code int ) {
2020-04-09 17:39:07 +03:00
prepareNoCache ( rw )
2020-07-20 08:24:18 +03:00
err := p . ClearSessionCookie ( rw , req )
if err != nil {
2020-07-21 04:34:37 +03:00
logger . Printf ( "Error clearing session cookie: %v" , err )
p . ErrorPage ( rw , http . StatusInternalServerError , "Internal Server Error" , err . Error ( ) )
2020-07-20 08:24:18 +03:00
return
}
2012-12-17 22:15:23 +04:00
rw . WriteHeader ( code )
2012-12-26 19:35:02 +04:00
2020-02-28 12:59:27 +03:00
redirectURL , err := p . GetRedirect ( req )
if err != nil {
2020-08-10 13:44:08 +03:00
logger . Errorf ( "Error obtaining redirect: %v" , err )
2020-07-21 04:34:37 +03:00
p . ErrorPage ( rw , http . StatusInternalServerError , "Internal Server Error" , err . Error ( ) )
2020-02-28 12:59:27 +03:00
return
2016-11-16 09:36:18 +03:00
}
2020-02-28 12:59:27 +03:00
if redirectURL == p . SignInPath {
redirectURL = "/"
2015-04-07 05:10:03 +03:00
}
2020-07-21 04:49:45 +03:00
// We allow unescaped template.HTML since it is user configured options
/* #nosec G203 */
2012-12-26 19:35:02 +04:00
t := struct {
2015-03-31 19:59:07 +03:00
ProviderName string
2020-04-04 17:01:11 +03:00
SignInMessage template . HTML
2014-12-09 23:38:57 +03:00
CustomLogin bool
2013-10-22 23:56:29 +04:00
Redirect string
2014-11-10 06:01:50 +03:00
Version string
2015-05-30 01:47:40 +03:00
ProxyPrefix string
2016-06-19 06:53:42 +03:00
Footer template . HTML
2012-12-26 19:55:41 +04:00
} {
2015-03-31 19:59:07 +03:00
ProviderName : p . provider . Data ( ) . ProviderName ,
2020-04-04 17:01:11 +03:00
SignInMessage : template . HTML ( p . SignInMessage ) ,
2020-07-18 12:18:47 +03:00
CustomLogin : p . displayHtpasswdForm ,
2020-02-28 12:59:27 +03:00
Redirect : redirectURL ,
2014-11-10 06:01:50 +03:00
Version : VERSION ,
2015-05-30 01:47:40 +03:00
ProxyPrefix : p . ProxyPrefix ,
2016-06-19 06:53:42 +03:00
Footer : template . HTML ( p . Footer ) ,
2012-12-26 19:55:41 +04:00
}
2019-11-25 20:20:37 +03:00
if p . providerNameOverride != "" {
t . ProviderName = p . providerNameOverride
}
2020-07-20 08:24:18 +03:00
err = p . templates . ExecuteTemplate ( rw , "sign_in.html" , t )
if err != nil {
2020-07-21 04:34:37 +03:00
logger . Printf ( "Error rendering sign_in.html template: %v" , err )
p . ErrorPage ( rw , http . StatusInternalServerError , "Internal Server Error" , err . Error ( ) )
2020-07-20 08:24:18 +03:00
}
2012-12-11 05:59:23 +04:00
}
2018-12-20 12:30:42 +03:00
// ManualSignIn handles basic auth logins to the proxy
2020-07-20 08:24:18 +03:00
func ( p * OAuthProxy ) ManualSignIn ( req * http . Request ) ( string , bool ) {
2020-07-18 12:18:47 +03:00
if req . Method != "POST" || p . basicAuthValidator == nil {
2012-12-26 19:55:41 +04:00
return "" , false
}
user := req . FormValue ( "username" )
passwd := req . FormValue ( "password" )
if user == "" {
return "" , false
}
// check auth
2020-07-18 12:18:47 +03:00
if p . basicAuthValidator . Validate ( user , passwd ) {
2019-02-10 20:01:13 +03:00
logger . PrintAuthf ( user , req , logger . AuthSuccess , "Authenticated via HtpasswdFile" )
2012-12-26 19:55:41 +04:00
return user , true
}
2019-02-10 20:01:13 +03:00
logger . PrintAuthf ( user , req , logger . AuthFailure , "Invalid authentication via HtpasswdFile" )
2012-12-26 19:55:41 +04:00
return "" , false
}
2018-12-20 12:30:42 +03:00
// GetRedirect reads the query parameter to get the URL to redirect clients to
// once authenticated with the OAuthProxy
2017-03-28 04:14:38 +03:00
func ( p * OAuthProxy ) GetRedirect ( req * http . Request ) ( redirect string , err error ) {
err = req . ParseForm ( )
2013-10-24 19:31:08 +04:00
if err != nil {
2017-03-28 04:14:38 +03:00
return
2013-10-24 19:31:08 +04:00
}
2019-08-17 23:50:37 +03:00
redirect = req . Header . Get ( "X-Auth-Request-Redirect" )
if req . Form . Get ( "rd" ) != "" {
redirect = req . Form . Get ( "rd" )
}
2017-09-29 18:55:50 +03:00
if ! p . IsValidRedirect ( redirect ) {
2020-07-21 18:38:13 +03:00
// Use RequestURI to preserve ?query
redirect = req . URL . RequestURI ( )
2019-01-29 15:13:02 +03:00
if strings . HasPrefix ( redirect , p . ProxyPrefix ) {
redirect = "/"
}
2013-10-24 19:31:08 +04:00
}
2017-03-28 04:14:38 +03:00
return
2013-10-24 19:31:08 +04:00
}
2019-10-23 16:38:44 +03:00
// splitHostPort separates host and port. If the port is not valid, it returns
// the entire input as host, and it doesn't check the validity of the host.
// Unlike net.SplitHostPort, but per RFC 3986, it requires ports to be numeric.
// *** taken from net/url, modified validOptionalPort() to accept ":*"
func splitHostPort ( hostport string ) ( host , port string ) {
host = hostport
colon := strings . LastIndexByte ( host , ':' )
if colon != - 1 && validOptionalPort ( host [ colon : ] ) {
host , port = host [ : colon ] , host [ colon + 1 : ]
}
if strings . HasPrefix ( host , "[" ) && strings . HasSuffix ( host , "]" ) {
host = host [ 1 : len ( host ) - 1 ]
}
return
}
// validOptionalPort reports whether port is either an empty string
// or matches /^:\d*$/
// *** taken from net/url, modified to accept ":*"
func validOptionalPort ( port string ) bool {
if port == "" || port == ":*" {
return true
}
if port [ 0 ] != ':' {
return false
}
for _ , b := range port [ 1 : ] {
if b < '0' || b > '9' {
return false
}
}
return true
}
2017-09-29 18:55:50 +03:00
// IsValidRedirect checks whether the redirect URL is whitelisted
func ( p * OAuthProxy ) IsValidRedirect ( redirect string ) bool {
switch {
2020-05-31 17:32:07 +03:00
case redirect == "" :
// The user didn't specify a redirect, should fallback to `/`
return false
2020-05-06 14:42:02 +03:00
case strings . HasPrefix ( redirect , "/" ) && ! strings . HasPrefix ( redirect , "//" ) && ! invalidRedirectRegex . MatchString ( redirect ) :
2017-09-29 18:55:50 +03:00
return true
2017-12-11 12:24:52 +03:00
case strings . HasPrefix ( redirect , "http://" ) || strings . HasPrefix ( redirect , "https://" ) :
redirectURL , err := url . Parse ( redirect )
if err != nil {
2020-04-02 11:51:38 +03:00
logger . Printf ( "Rejecting invalid redirect %q: scheme unsupported or missing" , redirect )
2017-12-11 12:24:52 +03:00
return false
2017-09-29 18:55:50 +03:00
}
2019-10-12 23:47:23 +03:00
redirectHostname := redirectURL . Hostname ( )
2017-09-29 18:55:50 +03:00
for _ , domain := range p . whitelistDomains {
2019-10-23 16:38:44 +03:00
domainHostname , domainPort := splitHostPort ( strings . TrimLeft ( domain , "." ) )
2019-11-14 18:17:12 +03:00
if domainHostname == "" {
2019-10-12 23:47:23 +03:00
continue
}
if ( redirectHostname == domainHostname ) || ( strings . HasPrefix ( domain , "." ) && strings . HasSuffix ( redirectHostname , domainHostname ) ) {
2019-10-23 16:38:44 +03:00
// the domain names match, now validate the ports
// if the whitelisted domain's port is '*', allow all ports
// if the whitelisted domain contains a specific port, only allow that port
// if the whitelisted domain doesn't contain a port at all, only allow empty redirect ports ie http and https
redirectPort := redirectURL . Port ( )
if ( domainPort == "*" ) ||
( domainPort == redirectPort ) ||
( domainPort == "" && redirectPort == "" ) {
2019-10-12 23:47:23 +03:00
return true
}
2017-09-29 18:55:50 +03:00
}
}
2019-10-12 23:47:23 +03:00
2020-04-02 11:51:38 +03:00
logger . Printf ( "Rejecting invalid redirect %q: domain / port not in whitelist" , redirect )
2017-09-29 18:55:50 +03:00
return false
default :
2020-04-02 11:51:38 +03:00
logger . Printf ( "Rejecting invalid redirect %q: not an absolute or relative URL" , redirect )
2017-09-29 18:55:50 +03:00
return false
}
}
2020-09-23 04:54:32 +03:00
// IsAllowedRequest is used to check if auth should be skipped for this request
func ( p * OAuthProxy ) IsAllowedRequest ( req * http . Request ) bool {
2017-04-07 14:55:48 +03:00
isPreflightRequestAllowed := p . skipAuthPreflight && req . Method == "OPTIONS"
2020-09-23 04:54:32 +03:00
return isPreflightRequestAllowed || p . isAllowedRoute ( req ) || p . IsTrustedIP ( req )
2017-04-07 14:55:48 +03:00
}
2020-09-23 04:54:32 +03: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 {
if ( route . method == "" || req . Method == route . method ) && route . pathRegex . MatchString ( req . URL . Path ) {
2019-06-07 06:50:44 +03:00
return true
2015-06-23 14:23:39 +03:00
}
2012-12-26 19:55:41 +04:00
}
2019-06-07 06:50:44 +03:00
return false
2015-06-23 14:23:39 +03:00
}
2012-12-26 19:35:02 +04:00
2020-04-09 17:39:07 +03:00
// See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=en
var noCacheHeaders = map [ string ] string {
"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/
}
// 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 )
}
}
2020-07-11 13:10:58 +03: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 {
2020-08-10 13:44:08 +03:00
logger . Errorf ( "Error obtaining real IP for trusted IP list: %v" , err )
2020-07-11 13:10:58 +03:00
// Possibly spoofed X-Real-IP header
return false
}
if remoteAddr == nil {
return false
}
return p . trustedIPs . Has ( remoteAddr )
}
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) ServeHTTP ( rw http . ResponseWriter , req * http . Request ) {
2020-07-29 22:10:14 +03:00
p . preAuthChain . Then ( http . HandlerFunc ( p . serveHTTP ) ) . ServeHTTP ( rw , req )
}
func ( p * OAuthProxy ) serveHTTP ( rw http . ResponseWriter , req * http . Request ) {
2020-07-06 13:04:31 +03:00
if req . URL . Path != p . AuthOnlyPath && strings . HasPrefix ( req . URL . Path , p . ProxyPrefix ) {
2020-04-09 17:39:07 +03:00
prepareNoCache ( rw )
}
2015-06-23 14:23:39 +03:00
switch path := req . URL . Path ; {
case path == p . RobotsPath :
2015-05-10 22:15:52 +03:00
p . RobotsTxt ( rw )
2020-09-23 04:54:32 +03:00
case p . IsAllowedRequest ( req ) :
2020-07-15 01:46:44 +03:00
p . SkipAuthProxy ( rw , req )
2015-06-23 14:23:39 +03:00
case path == p . SignInPath :
p . SignIn ( rw , req )
2017-03-21 19:39:26 +03:00
case path == p . SignOutPath :
p . SignOut ( rw , req )
2015-11-09 02:57:01 +03:00
case path == p . OAuthStartPath :
p . OAuthStart ( rw , req )
case path == p . OAuthCallbackPath :
p . OAuthCallback ( rw , req )
2015-10-08 16:27:00 +03:00
case path == p . AuthOnlyPath :
p . AuthenticateOnly ( rw , req )
2019-11-08 01:38:36 +03:00
case path == p . UserInfoPath :
p . UserInfo ( rw , req )
2015-06-23 14:23:39 +03:00
default :
p . Proxy ( rw , req )
2015-05-10 22:15:52 +03:00
}
2015-06-23 14:23:39 +03:00
}
2015-05-10 22:15:52 +03:00
2018-12-20 12:30:42 +03:00
// SignIn serves a page prompting users to sign in
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) SignIn ( rw http . ResponseWriter , req * http . Request ) {
2015-06-23 14:23:39 +03:00
redirect , err := p . GetRedirect ( req )
if err != nil {
2020-08-10 13:44:08 +03:00
logger . Errorf ( "Error obtaining redirect: %v" , err )
2020-07-21 04:34:37 +03:00
p . ErrorPage ( rw , http . StatusInternalServerError , "Internal Server Error" , err . Error ( ) )
2014-10-15 00:22:38 +04:00
return
}
2020-07-20 08:24:18 +03:00
user , ok := p . ManualSignIn ( req )
2015-06-23 14:23:39 +03:00
if ok {
2019-05-07 16:27:09 +03:00
session := & sessionsapi . SessionState { User : user }
2020-07-20 08:24:18 +03:00
err = p . SaveSession ( rw , req , session )
if err != nil {
2020-07-21 05:29:31 +03:00
logger . Printf ( "Error saving session: %v" , err )
2020-07-21 04:34:37 +03:00
p . ErrorPage ( rw , http . StatusInternalServerError , "Internal Server Error" , err . Error ( ) )
2020-07-20 08:24:18 +03:00
return
}
2020-04-14 11:36:44 +03:00
http . Redirect ( rw , req , redirect , http . StatusFound )
2015-06-23 14:23:39 +03:00
} else {
2017-06-22 01:02:34 +03:00
if p . SkipProviderButton {
p . OAuthStart ( rw , req )
} else {
p . SignInPage ( rw , req , http . StatusOK )
}
2015-06-23 14:23:39 +03:00
}
}
2015-01-12 12:18:41 +03:00
2020-03-01 18:02:51 +03:00
//UserInfo endpoint outputs session email and preferred username in JSON format
2019-11-08 01:38:36 +03: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
}
2020-10-19 04:57:49 +03:00
2019-11-08 01:38:36 +03:00
userInfo := struct {
2020-10-19 04:57:49 +03:00
User string ` json:"user" `
Email string ` json:"email" `
Groups [ ] string ` json:"groups,omitempty" `
PreferredUsername string ` json:"preferredUsername,omitempty" `
2020-03-01 18:02:51 +03:00
} {
2020-10-19 04:57:49 +03:00
User : session . User ,
2020-03-01 18:02:51 +03:00
Email : session . Email ,
2020-10-19 04:57:49 +03:00
Groups : session . Groups ,
2020-03-01 18:02:51 +03:00
PreferredUsername : session . PreferredUsername ,
}
2020-10-19 04:57:49 +03:00
2019-11-08 01:38:36 +03:00
rw . Header ( ) . Set ( "Content-Type" , "application/json" )
rw . WriteHeader ( http . StatusOK )
2020-07-20 08:24:18 +03:00
err = json . NewEncoder ( rw ) . Encode ( userInfo )
if err != nil {
2020-07-21 04:34:37 +03:00
logger . Printf ( "Error encoding user info: %v" , err )
p . ErrorPage ( rw , http . StatusInternalServerError , "Internal Server Error" , err . Error ( ) )
2020-07-20 08:24:18 +03:00
}
2019-11-08 01:38:36 +03:00
}
2018-12-20 12:30:42 +03:00
// SignOut sends a response to clear the authentication cookie
2017-03-21 19:39:26 +03:00
func ( p * OAuthProxy ) SignOut ( rw http . ResponseWriter , req * http . Request ) {
2019-11-19 20:17:26 +03:00
redirect , err := p . GetRedirect ( req )
if err != nil {
2020-08-10 13:44:08 +03:00
logger . Errorf ( "Error obtaining redirect: %v" , err )
2020-07-21 04:34:37 +03:00
p . ErrorPage ( rw , http . StatusInternalServerError , "Internal Server Error" , err . Error ( ) )
2019-11-19 20:17:26 +03:00
return
}
2020-07-20 08:24:18 +03:00
err = p . ClearSessionCookie ( rw , req )
if err != nil {
2020-08-10 13:44:08 +03:00
logger . Errorf ( "Error clearing session cookie: %v" , err )
2020-07-21 04:34:37 +03:00
p . ErrorPage ( rw , http . StatusInternalServerError , "Internal Server Error" , err . Error ( ) )
2020-07-20 08:24:18 +03:00
return
}
2020-04-14 11:36:44 +03:00
http . Redirect ( rw , req , redirect , http . StatusFound )
2017-03-21 19:39:26 +03:00
}
2018-12-20 12:30:42 +03:00
// OAuthStart starts the OAuth2 authentication flow
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) OAuthStart ( rw http . ResponseWriter , req * http . Request ) {
2020-04-09 17:39:07 +03:00
prepareNoCache ( rw )
2019-05-24 19:06:48 +03:00
nonce , err := encryption . Nonce ( )
2017-03-28 04:14:38 +03:00
if err != nil {
2020-08-10 13:44:08 +03:00
logger . Errorf ( "Error obtaining nonce: %v" , err )
2020-07-21 04:34:37 +03:00
p . ErrorPage ( rw , http . StatusInternalServerError , "Internal Server Error" , err . Error ( ) )
2017-03-28 04:14:38 +03:00
return
}
p . SetCSRFCookie ( rw , req , nonce )
2015-06-23 14:23:39 +03:00
redirect , err := p . GetRedirect ( req )
if err != nil {
2020-08-10 13:44:08 +03:00
logger . Errorf ( "Error obtaining redirect: %v" , err )
2020-07-21 04:34:37 +03:00
p . ErrorPage ( rw , http . StatusInternalServerError , "Internal Server Error" , err . Error ( ) )
2015-06-23 14:23:39 +03:00
return
2015-01-12 12:18:41 +03:00
}
2020-08-22 05:50:32 +03:00
redirectURI := p . GetRedirectURI ( util . GetRequestHost ( req ) )
2020-04-14 11:36:44 +03:00
http . Redirect ( rw , req , p . provider . GetLoginURL ( redirectURI , fmt . Sprintf ( "%v:%v" , nonce , redirect ) ) , http . StatusFound )
2015-06-23 14:23:39 +03:00
}
2015-01-12 12:18:41 +03:00
2018-12-20 12:30:42 +03:00
// OAuthCallback is the OAuth2 authentication flow callback that finishes the
// OAuth2 authentication flow
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) OAuthCallback ( rw http . ResponseWriter , req * http . Request ) {
2020-05-23 17:17:41 +03:00
remoteAddr := ip . GetClientString ( p . realClientIPParser , req , true )
2013-10-24 19:31:08 +04:00
2015-06-23 14:23:39 +03:00
// finish the oauth cycle
err := req . ParseForm ( )
if err != nil {
2020-08-10 13:44:08 +03:00
logger . Errorf ( "Error while parsing OAuth2 callback: %v" , err )
2020-07-21 04:34:37 +03:00
p . ErrorPage ( rw , http . StatusInternalServerError , "Internal Server Error" , err . Error ( ) )
2012-12-11 05:59:23 +04:00
return
}
2015-06-23 14:23:39 +03:00
errorString := req . Form . Get ( "error" )
if errorString != "" {
2020-08-10 13:44:08 +03:00
logger . Errorf ( "Error while parsing OAuth2 callback: %s" , errorString )
2020-07-21 04:34:37 +03:00
p . ErrorPage ( rw , http . StatusForbidden , "Permission Denied" , errorString )
2012-12-11 05:59:23 +04:00
return
}
2020-08-22 05:50:32 +03:00
session , err := p . redeemCode ( req . Context ( ) , util . GetRequestHost ( req ) , req . Form . Get ( "code" ) )
2015-06-23 14:23:39 +03:00
if err != nil {
2020-08-10 13:44:08 +03:00
logger . Errorf ( "Error redeeming code during OAuth2 callback: %v" , err )
2020-07-21 04:34:37 +03:00
p . ErrorPage ( rw , http . StatusInternalServerError , "Internal Server Error" , "Internal Error" )
2015-06-23 14:23:39 +03:00
return
}
2020-09-27 21:46:29 +03:00
err = p . enrichSessionState ( req . Context ( ) , session )
2020-09-26 23:19:08 +03:00
if err != nil {
logger . Errorf ( "Error creating session during OAuth2 callback: %v" , err )
p . ErrorPage ( rw , http . StatusInternalServerError , "Internal Server Error" , "Internal Error" )
return
}
state := strings . SplitN ( req . Form . Get ( "state" ) , ":" , 2 )
if len ( state ) != 2 {
2020-08-10 13:44:08 +03:00
logger . Error ( "Error while parsing OAuth2 state: invalid length" )
2020-07-21 04:34:37 +03:00
p . ErrorPage ( rw , http . StatusInternalServerError , "Internal Server Error" , "Invalid State" )
2017-03-28 04:14:38 +03:00
return
}
2020-09-26 23:19:08 +03:00
nonce := state [ 0 ]
redirect := state [ 1 ]
2017-03-28 04:14:38 +03:00
c , err := req . Cookie ( p . CSRFCookieName )
if err != nil {
2020-07-21 04:34:37 +03:00
logger . PrintAuthf ( session . Email , req , logger . AuthFailure , "Invalid authentication via OAuth2: unable to obtain CSRF cookie" )
p . ErrorPage ( rw , http . StatusForbidden , "Permission Denied" , err . Error ( ) )
2017-03-28 04:14:38 +03:00
return
}
p . ClearCSRFCookie ( rw , req )
if c . Value != nonce {
2020-07-21 04:34:37 +03:00
logger . PrintAuthf ( session . Email , req , logger . AuthFailure , "Invalid authentication via OAuth2: CSRF token mismatch, potential attack" )
p . ErrorPage ( rw , http . StatusForbidden , "Permission Denied" , "CSRF Failed" )
2017-03-28 04:14:38 +03:00
return
}
2017-09-29 18:55:50 +03:00
if ! p . IsValidRedirect ( redirect ) {
2015-06-23 14:23:39 +03:00
redirect = "/"
}
// set cookie, or deny
2020-10-24 06:53:38 +03: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 19:36:18 +03:00
logger . PrintAuthf ( session . Email , req , logger . AuthSuccess , "Authenticated via OAuth2: %s" , session )
2015-06-23 14:23:39 +03:00
err := p . SaveSession ( rw , req , session )
2012-12-11 05:59:23 +04:00
if err != nil {
2020-10-24 06:53:38 +03:00
logger . Errorf ( "Error saving session state for %s: %v" , remoteAddr , err )
2020-07-21 04:34:37 +03:00
p . ErrorPage ( rw , http . StatusInternalServerError , "Internal Server Error" , err . Error ( ) )
2012-12-11 05:59:23 +04:00
return
}
2020-04-14 11:36:44 +03:00
http . Redirect ( rw , req , redirect , http . StatusFound )
2015-06-23 14:23:39 +03:00
} else {
2019-06-20 23:40:04 +03:00
logger . PrintAuthf ( session . Email , req , logger . AuthFailure , "Invalid authentication via OAuth2: unauthorized" )
2020-07-21 04:34:37 +03:00
p . ErrorPage ( rw , http . StatusForbidden , "Permission Denied" , "Invalid Account" )
2015-06-23 14:23:39 +03:00
}
}
2012-12-11 05:59:23 +04:00
2018-12-20 12:30:42 +03:00
// AuthenticateOnly checks whether the user is currently logged in
2015-10-08 16:27:00 +03:00
func ( p * OAuthProxy ) AuthenticateOnly ( rw http . ResponseWriter , req * http . Request ) {
2019-06-07 07:25:12 +03:00
session , err := p . getAuthenticatedSession ( rw , req )
2019-06-15 11:48:27 +03:00
if err != nil {
2015-10-08 21:10:28 +03:00
http . Error ( rw , "unauthorized request" , http . StatusUnauthorized )
2019-06-15 11:48:27 +03:00
return
2015-10-08 16:27:00 +03:00
}
2019-06-15 11:48:27 +03:00
// we are authenticated
p . addHeadersForProxying ( rw , req , session )
2020-07-29 22:10:14 +03:00
p . headersChain . Then ( http . HandlerFunc ( func ( rw http . ResponseWriter , req * http . Request ) {
rw . WriteHeader ( http . StatusAccepted )
} ) ) . ServeHTTP ( rw , req )
2015-10-08 16:27:00 +03:00
}
2020-09-23 04:54:32 +03:00
// SkipAuthProxy proxies allowlisted requests and skips authentication
2020-07-15 01:46:44 +03:00
func ( p * OAuthProxy ) SkipAuthProxy ( rw http . ResponseWriter , req * http . Request ) {
2020-07-29 22:10:14 +03:00
p . headersChain . Then ( p . serveMux ) . ServeHTTP ( rw , req )
2020-07-15 01:46:44 +03:00
}
2018-12-20 12:30:42 +03:00
// Proxy proxies the user request if the user is authenticated else it prompts
// them to authenticate
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) Proxy ( rw http . ResponseWriter , req * http . Request ) {
2019-06-07 06:50:44 +03:00
session , err := p . getAuthenticatedSession ( rw , req )
switch err {
case nil :
// we are authenticated
p . addHeadersForProxying ( rw , req , session )
2020-07-29 22:10:14 +03:00
p . headersChain . Then ( p . serveMux ) . ServeHTTP ( rw , req )
2019-06-07 06:50:44 +03:00
case ErrNeedsLogin :
// we need to send the user to a login screen
if isAjax ( req ) {
// no point redirecting an AJAX request
p . ErrorJSON ( rw , http . StatusUnauthorized )
return
}
2015-11-11 03:42:35 +03:00
if p . SkipProviderButton {
p . OAuthStart ( rw , req )
} else {
p . SignInPage ( rw , req , http . StatusForbidden )
}
2019-06-07 06:50:44 +03:00
2020-10-29 04:40:58 +03:00
case ErrAccessDenied :
p . ErrorPage ( rw , http . StatusUnauthorized , "Permission Denied" , "Unauthorized" )
2019-06-07 06:50:44 +03:00
default :
// unknown error
2020-08-10 13:44:08 +03:00
logger . Errorf ( "Unexpected internal error: %v" , err )
2019-06-07 06:50:44 +03:00
p . ErrorPage ( rw , http . StatusInternalServerError ,
"Internal Error" , "Internal Error" )
2015-10-08 21:10:28 +03:00
}
2019-06-07 06:50:44 +03:00
2015-10-08 21:10:28 +03:00
}
2019-06-07 06:50:44 +03:00
// getAuthenticatedSession checks whether a user is authenticated and returns a session object and nil error if so
2020-10-29 04:40:58 +03:00
// Returns:
// - `nil, ErrNeedsLogin` if user needs to login.
// - `nil, ErrAccessDenied` if the authenticated user is not authorized
2019-06-07 06:50:44 +03: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 ) {
2019-01-17 23:49:14 +03:00
var session * sessionsapi . SessionState
2015-06-23 14:23:39 +03:00
2020-07-18 02:42:51 +03:00
getSession := p . sessionChain . Then ( http . HandlerFunc ( func ( rw http . ResponseWriter , req * http . Request ) {
session = middleware . GetRequestScope ( req ) . Session
} ) )
getSession . ServeHTTP ( rw , req )
2015-06-23 14:23:39 +03:00
2019-01-17 23:49:14 +03:00
if session == nil {
2020-07-18 02:42:51 +03:00
return nil , ErrNeedsLogin
2015-06-23 14:23:39 +03:00
}
2013-10-22 23:56:29 +04:00
2020-09-27 03:29:34 +03: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 21:42:09 +03:00
2020-09-27 05:00:44 +03:00
if invalidEmail || ! authorized {
2020-10-29 04:40:58 +03:00
logger . PrintAuthf ( session . Email , req , logger . AuthFailure , "Invalid authorization via session: removing session %s" , session )
2020-07-18 02:42:51 +03:00
// Invalid session, clear it
2020-07-20 08:24:18 +03:00
err := p . ClearSessionCookie ( rw , req )
if err != nil {
2020-09-27 03:29:34 +03:00
logger . Errorf ( "Error clearing session cookie: %v" , err )
2020-07-20 08:24:18 +03:00
}
2020-10-29 04:40:58 +03:00
return nil , ErrAccessDenied
2012-12-11 05:59:23 +04:00
}
2019-06-07 06:50:44 +03:00
return session , nil
}
// addHeadersForProxying adds the appropriate headers the request / response for proxying
func ( p * OAuthProxy ) addHeadersForProxying ( rw http . ResponseWriter , req * http . Request , session * sessionsapi . SessionState ) {
2015-06-23 14:23:39 +03:00
if session . Email == "" {
rw . Header ( ) . Set ( "GAP-Auth" , session . User )
2015-03-19 23:37:16 +03:00
} else {
2015-06-23 14:23:39 +03:00
rw . Header ( ) . Set ( "GAP-Auth" , session . Email )
2015-03-19 23:37:16 +03:00
}
2012-12-11 05:59:23 +04:00
}
2019-01-30 13:13:12 +03:00
// isAjax checks if a request is an ajax request
2019-06-07 06:50:44 +03:00
func isAjax ( req * http . Request ) bool {
2020-04-14 11:36:44 +03:00
acceptValues := req . Header . Values ( "Accept" )
2019-01-31 18:22:30 +03:00
const ajaxReq = applicationJSON
2019-01-30 13:13:12 +03:00
for _ , v := range acceptValues {
if v == ajaxReq {
return true
}
}
return false
}
2019-08-13 13:42:23 +03:00
// ErrorJSON returns the error code with an application/json mime type
2019-01-30 13:13:12 +03:00
func ( p * OAuthProxy ) ErrorJSON ( rw http . ResponseWriter , code int ) {
2019-01-31 18:22:30 +03:00
rw . Header ( ) . Set ( "Content-Type" , applicationJSON )
2019-01-30 13:13:12 +03:00
rw . WriteHeader ( code )
}