2012-12-11 05:59:23 +04:00
package main
import (
2019-01-17 23:49:14 +03:00
"context"
2016-06-20 14:17:39 +03:00
b64 "encoding/base64"
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/http/httputil"
"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"
2018-11-29 17:26:41 +03:00
"github.com/mbland/hmacauth"
2018-11-27 14:45:05 +03:00
"github.com/pusher/oauth2_proxy/cookie"
2019-02-10 19:37:45 +03:00
"github.com/pusher/oauth2_proxy/logger"
2019-05-07 16:27:09 +03:00
sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
2018-11-27 14:45:05 +03:00
"github.com/pusher/oauth2_proxy/providers"
2019-03-08 11:15:21 +03:00
"github.com/yhat/wsutil"
2012-12-11 05:59:23 +04:00
)
2018-11-29 17:26:41 +03:00
const (
2018-12-20 12:30:42 +03:00
// SignatureHeader is the name of the request header containing the GAP Signature
// Part of hmacauth
2018-11-29 17:26:41 +03:00
SignatureHeader = "GAP-Signature"
2015-11-16 06:08:30 +03:00
2018-11-29 17:26:41 +03:00
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
)
2018-12-20 12:30:42 +03:00
// SignatureHeaders contains the headers to be signed by the hmac algorithm
// Part of hmacauth
2018-11-29 17:26:41 +03:00
var SignatureHeaders = [ ] string {
2015-11-16 06:08:30 +03:00
"Content-Length" ,
"Content-Md5" ,
"Content-Type" ,
"Date" ,
"Authorization" ,
"X-Forwarded-User" ,
"X-Forwarded-Email" ,
"X-Forwarded-Access-Token" ,
"Cookie" ,
"Gap-Auth" ,
}
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" )
)
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
2015-03-18 06:13:45 +03:00
CookieDomain 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
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
PingPath 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
2015-05-30 01:47:40 +03:00
2015-11-09 02:47:44 +03:00
redirectURL * url . URL // the url to receive requests at
2017-09-29 18:55:50 +03:00
whitelistDomains [ ] string
2015-03-30 22:48:30 +03:00
provider providers . Provider
2019-05-07 16:27:09 +03:00
sessionStore sessionsapi . SessionStore
2015-05-30 01:47:40 +03:00
ProxyPrefix string
2014-12-09 23:38:57 +03:00
SignInMessage string
HtpasswdFile * HtpasswdFile
DisplayHtpasswdForm bool
2015-03-19 23:37:16 +03:00
serveMux http . Handler
2016-10-20 15:19:59 +03:00
SetXAuthRequest bool
2014-12-09 23:38:57 +03:00
PassBasicAuth bool
2015-11-11 03:42:35 +03:00
SkipProviderButton bool
2016-02-08 18:57:47 +03:00
PassUserHeaders bool
2015-07-24 12:17:43 +03:00
BasicAuthPassword string
2015-05-09 23:08:55 +03:00
PassAccessToken bool
2018-01-27 13:14:19 +03:00
SetAuthorization bool
PassAuthorization bool
2015-01-12 12:18:41 +03:00
skipAuthRegex [ ] string
2017-04-07 14:55:48 +03:00
skipAuthPreflight bool
2019-01-17 23:49:14 +03:00
skipJwtBearerTokens bool
jwtBearerVerifiers [ ] * oidc . IDTokenVerifier
2015-01-12 12:18:41 +03:00
compiledRegex [ ] * regexp . Regexp
2015-03-18 01:06:06 +03:00
templates * template . Template
2016-06-19 06:53:42 +03:00
Footer string
2012-12-11 05:59:23 +04:00
}
2018-12-20 12:30:42 +03:00
// UpstreamProxy represents an upstream server to proxy to
2015-03-19 23:37:16 +03:00
type UpstreamProxy struct {
2019-03-08 11:15:21 +03:00
upstream string
handler http . Handler
wsHandler http . Handler
auth hmacauth . HmacAuth
2015-03-19 23:37:16 +03:00
}
2018-12-20 12:30:42 +03:00
// ServeHTTP proxies requests to the upstream provider while signing the
// request headers
2015-03-19 23:37:16 +03:00
func ( u * UpstreamProxy ) ServeHTTP ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "GAP-Upstream-Address" , u . upstream )
2015-11-16 06:08:30 +03:00
if u . auth != nil {
r . Header . Set ( "GAP-Auth" , w . Header ( ) . Get ( "GAP-Auth" ) )
u . auth . SignRequest ( r )
}
2019-03-23 00:19:38 +03:00
if u . wsHandler != nil && strings . ToLower ( r . Header . Get ( "Connection" ) ) == "upgrade" && r . Header . Get ( "Upgrade" ) == "websocket" {
2019-03-08 11:15:21 +03:00
u . wsHandler . ServeHTTP ( w , r )
} else {
u . handler . ServeHTTP ( w , r )
}
2015-03-19 23:37:16 +03:00
}
2018-12-20 12:30:42 +03:00
// NewReverseProxy creates a new reverse proxy for proxying requests to upstream
// servers
2019-01-31 17:02:15 +03:00
func NewReverseProxy ( target * url . URL , flushInterval time . Duration ) ( proxy * httputil . ReverseProxy ) {
proxy = httputil . NewSingleHostReverseProxy ( target )
proxy . FlushInterval = flushInterval
return proxy
2015-03-17 22:15:15 +03:00
}
2018-12-20 12:30:42 +03:00
2015-03-17 22:15:15 +03:00
func setProxyUpstreamHostHeader ( proxy * httputil . ReverseProxy , target * url . URL ) {
director := proxy . Director
proxy . Director = func ( req * http . Request ) {
director ( req )
2015-03-18 00:17:40 +03:00
// use RequestURI so that we aren't unescaping encoded slashes in the request path
2015-03-21 22:29:07 +03:00
req . Host = target . Host
req . URL . Opaque = req . RequestURI
2015-03-18 00:17:40 +03:00
req . URL . RawQuery = ""
}
}
2018-12-20 12:30:42 +03:00
2015-03-18 00:17:40 +03:00
func setProxyDirector ( proxy * httputil . ReverseProxy ) {
director := proxy . Director
proxy . Director = func ( req * http . Request ) {
director ( req )
// use RequestURI so that we aren't unescaping encoded slashes in the request path
2015-03-21 22:29:07 +03:00
req . URL . Opaque = req . RequestURI
2015-03-18 00:17:40 +03:00
req . URL . RawQuery = ""
2015-03-17 22:15:15 +03:00
}
2014-12-01 04:12:33 +03:00
}
2018-12-20 12:30:42 +03:00
// NewFileServer creates a http.Handler to serve files from the filesystem
2015-09-23 23:00:36 +03:00
func NewFileServer ( path string , filesystemPath string ) ( proxy http . Handler ) {
return http . StripPrefix ( path , http . FileServer ( http . Dir ( filesystemPath ) ) )
}
2014-12-01 04:12:33 +03:00
2019-03-08 11:15:21 +03:00
// NewWebSocketOrRestReverseProxy creates a reverse proxy for REST or websocket based on url
func NewWebSocketOrRestReverseProxy ( u * url . URL , opts * Options , auth hmacauth . HmacAuth ) ( restProxy http . Handler ) {
u . Path = ""
proxy := NewReverseProxy ( u , opts . FlushInterval )
if ! opts . PassHostHeader {
setProxyUpstreamHostHeader ( proxy , u )
} else {
setProxyDirector ( proxy )
}
// this should give us a wss:// scheme if the url is https:// based.
var wsProxy * wsutil . ReverseProxy
if opts . ProxyWebSockets {
wsScheme := "ws" + strings . TrimPrefix ( u . Scheme , "http" )
wsURL := & url . URL { Scheme : wsScheme , Host : u . Host }
wsProxy = wsutil . NewSingleHostReverseProxy ( wsURL )
}
return & UpstreamProxy { u . Host , proxy , wsProxy , auth }
}
2018-12-20 12:30:42 +03:00
// NewOAuthProxy creates a new instance of OOuthProxy from the options provided
2015-11-09 02:57:01 +03:00
func NewOAuthProxy ( opts * Options , validator func ( string ) bool ) * OAuthProxy {
2012-12-11 05:59:23 +04:00
serveMux := http . NewServeMux ( )
2015-11-16 06:08:30 +03:00
var auth hmacauth . HmacAuth
if sigData := opts . signatureData ; sigData != nil {
auth = hmacauth . NewHmacAuth ( sigData . hash , [ ] byte ( sigData . key ) ,
SignatureHeader , SignatureHeaders )
}
2015-11-09 02:47:44 +03:00
for _ , u := range opts . proxyURLs {
2012-12-11 05:59:23 +04:00
path := u . Path
2015-09-23 23:00:36 +03:00
switch u . Scheme {
2018-11-29 17:26:41 +03:00
case httpScheme , httpsScheme :
2019-04-12 19:26:44 +03:00
logger . Printf ( "mapping path %q => upstream %q" , path , u )
2019-03-08 11:15:21 +03:00
proxy := NewWebSocketOrRestReverseProxy ( u , opts , auth )
serveMux . Handle ( path , proxy )
2015-09-23 23:00:36 +03:00
case "file" :
if u . Fragment != "" {
path = u . Fragment
}
2019-02-10 19:37:45 +03:00
logger . Printf ( "mapping path %q => file system %q" , path , u . Path )
2015-09-23 23:00:36 +03:00
proxy := NewFileServer ( path , u . Path )
2019-03-08 11:15:21 +03:00
serveMux . Handle ( path , & UpstreamProxy { path , proxy , nil , nil } )
2015-09-23 23:00:36 +03:00
default :
panic ( fmt . Sprintf ( "unknown upstream protocol %s" , u . Scheme ) )
2015-03-17 22:15:15 +03:00
}
2012-12-11 05:59:23 +04:00
}
2015-01-12 12:18:41 +03:00
for _ , u := range opts . CompiledRegex {
2019-02-10 19:37:45 +03:00
logger . Printf ( "compiled skip-auth-regex => %q" , u )
2015-01-12 12:18:41 +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 )
}
}
2015-11-09 02:47:44 +03:00
redirectURL := opts . redirectURL
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
2019-02-10 19:37:45 +03:00
logger . Printf ( "OAuthProxy configured for %s Client ID: %s" , opts . provider . Data ( ) . ProviderName , opts . ClientID )
2015-06-22 22:10:08 +03:00
refresh := "disabled"
if opts . CookieRefresh != time . Duration ( 0 ) {
refresh = fmt . Sprintf ( "after %s" , opts . CookieRefresh )
}
2015-03-18 06:13:45 +03:00
2019-04-12 19:26:44 +03:00
logger . Printf ( "Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s path:%s refresh:%s" , opts . CookieName , opts . CookieSecure , opts . CookieHTTPOnly , opts . CookieExpire , opts . CookieDomain , opts . CookiePath , refresh )
2015-03-18 06:13:45 +03:00
2015-11-09 02:57:01 +03:00
return & OAuthProxy {
2015-06-08 06:52:28 +03:00
CookieName : opts . CookieName ,
2017-03-28 04:14:38 +03:00
CSRFCookieName : fmt . Sprintf ( "%v_%v" , opts . CookieName , "csrf" ) ,
2015-03-18 06:13:45 +03:00
CookieSeed : opts . CookieSecret ,
CookieDomain : opts . CookieDomain ,
2019-04-10 00:36:35 +03:00
CookiePath : opts . CookiePath ,
2015-03-18 06:13:45 +03:00
CookieSecure : opts . CookieSecure ,
2018-11-29 17:26:41 +03:00
CookieHTTPOnly : opts . CookieHTTPOnly ,
2015-03-18 06:13:45 +03:00
CookieExpire : opts . CookieExpire ,
2015-05-08 17:00:57 +03:00
CookieRefresh : opts . CookieRefresh ,
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" ,
PingPath : "/ping" ,
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 ) ,
2015-05-30 01:47:40 +03:00
2019-01-17 23:49:14 +03:00
ProxyPrefix : opts . ProxyPrefix ,
provider : opts . provider ,
sessionStore : opts . sessionStore ,
serveMux : serveMux ,
redirectURL : redirectURL ,
whitelistDomains : opts . WhitelistDomains ,
skipAuthRegex : opts . SkipAuthRegex ,
skipAuthPreflight : opts . SkipAuthPreflight ,
skipJwtBearerTokens : opts . SkipJwtBearerTokens ,
jwtBearerVerifiers : opts . jwtBearerVerifiers ,
compiledRegex : opts . CompiledRegex ,
SetXAuthRequest : opts . SetXAuthRequest ,
PassBasicAuth : opts . PassBasicAuth ,
PassUserHeaders : opts . PassUserHeaders ,
BasicAuthPassword : opts . BasicAuthPassword ,
PassAccessToken : opts . PassAccessToken ,
SetAuthorization : opts . SetAuthorization ,
PassAuthorization : opts . PassAuthorization ,
SkipProviderButton : opts . SkipProviderButton ,
templates : loadTemplates ( opts . CustomTemplatesDir ) ,
Footer : opts . Footer ,
2012-12-11 05:59:23 +04:00
}
}
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
}
var u url . URL
2015-11-09 02:47: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 ( )
}
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) displayCustomLoginForm ( ) bool {
2014-12-09 23:38:57 +03:00
return p . HtpasswdFile != nil && p . DisplayHtpasswdForm
}
2019-05-07 16:27:09 +03:00
func ( p * OAuthProxy ) redeemCode ( host , code string ) ( s * sessionsapi . SessionState , err error ) {
2013-10-22 23:56:29 +04:00
if code == "" {
2015-06-23 14:23:39 +03:00
return nil , errors . New ( "missing code" )
2013-10-22 23:56:29 +04:00
}
2015-11-09 02:50:42 +03:00
redirectURI := p . GetRedirectURI ( host )
s , err = p . provider . Redeem ( redirectURI , code )
2012-12-11 05:59:23 +04:00
if err != nil {
2015-06-23 14:23:39 +03:00
return
2012-12-11 05:59:23 +04:00
}
2012-12-17 22:15:23 +04:00
2015-06-23 14:23:39 +03:00
if s . Email == "" {
s . Email , err = p . provider . GetEmailAddress ( s )
2014-08-08 00:16:39 +04:00
}
2017-09-27 00:31:27 +03:00
if s . User == "" {
s . User , err = p . provider . GetUserName ( s )
if err != nil && err . Error ( ) == "not implemented" {
err = nil
}
}
2015-06-23 14:23:39 +03:00
return
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 {
2015-03-19 22:59:48 +03:00
if p . CookieDomain != "" {
2017-04-19 06:33:50 +03:00
domain := req . Host
if h , _ , err := net . SplitHostPort ( domain ) ; err == nil {
domain = h
}
2015-03-19 22:59:48 +03:00
if ! strings . HasSuffix ( domain , p . CookieDomain ) {
2019-02-10 19:37:45 +03:00
logger . Printf ( "Warning: request host is %q but using configured cookie domain of %q" , domain , p . 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 ,
2017-04-19 06:33:50 +03:00
Domain : p . 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 ) ,
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 ) {
2015-05-10 22:15:52 +03:00
rw . WriteHeader ( http . StatusOK )
fmt . Fprintf ( rw , "User-agent: *\nDisallow: /" )
}
2018-12-20 12:30:42 +03:00
// PingPage responds 200 OK to requests
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) PingPage ( rw http . ResponseWriter ) {
2014-10-15 00:22:38 +04:00
rw . WriteHeader ( http . StatusOK )
2014-10-15 01:05:59 +04:00
fmt . Fprintf ( rw , "OK" )
2014-10-15 00:22:38 +04: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
}
2015-03-18 01:06:06 +03:00
p . templates . ExecuteTemplate ( rw , "error.html" , t )
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 ) {
2017-03-28 04:14:38 +03:00
p . ClearSessionCookie ( rw , req )
2012-12-17 22:15:23 +04:00
rw . WriteHeader ( code )
2012-12-26 19:35:02 +04:00
2018-11-29 17:26:41 +03:00
redirecURL := req . URL . RequestURI ( )
2016-11-16 09:36:18 +03:00
if req . Header . Get ( "X-Auth-Request-Redirect" ) != "" {
2018-11-29 17:26:41 +03:00
redirecURL = req . Header . Get ( "X-Auth-Request-Redirect" )
2016-11-16 09:36:18 +03:00
}
2018-11-29 17:26:41 +03:00
if redirecURL == p . SignInPath {
redirecURL = "/"
2015-04-07 05:10:03 +03:00
}
2012-12-26 19:35:02 +04:00
t := struct {
2015-03-31 19:59:07 +03:00
ProviderName string
2012-12-26 19:55:41 +04:00
SignInMessage string
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 ,
2012-12-26 19:35:02 +04:00
SignInMessage : p . SignInMessage ,
2014-12-09 23:38:57 +03:00
CustomLogin : p . displayCustomLoginForm ( ) ,
2018-11-29 17:26:41 +03:00
Redirect : redirecURL ,
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
}
2015-03-18 01:06:06 +03:00
p . templates . ExecuteTemplate ( rw , "sign_in.html" , t )
2012-12-11 05:59:23 +04:00
}
2018-12-20 12:30:42 +03:00
// ManualSignIn handles basic auth logins to the proxy
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) ManualSignIn ( rw http . ResponseWriter , req * http . Request ) ( string , bool ) {
2012-12-26 19:35:02 +04:00
if req . Method != "POST" || p . HtpasswdFile == 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
if p . HtpasswdFile . 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
}
2017-03-28 04:14:38 +03:00
redirect = req . Form . Get ( "rd" )
2017-09-29 18:55:50 +03:00
if ! p . IsValidRedirect ( redirect ) {
2019-01-29 15:13:02 +03:00
redirect = req . URL . Path
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
}
2017-09-29 18:55:50 +03:00
// IsValidRedirect checks whether the redirect URL is whitelisted
func ( p * OAuthProxy ) IsValidRedirect ( redirect string ) bool {
switch {
case strings . HasPrefix ( redirect , "/" ) && ! strings . HasPrefix ( redirect , "//" ) :
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 {
return false
2017-09-29 18:55:50 +03:00
}
for _ , domain := range p . whitelistDomains {
2017-12-11 12:24:52 +03:00
if ( redirectURL . Host == domain ) || ( strings . HasPrefix ( domain , "." ) && strings . HasSuffix ( redirectURL . Host , domain ) ) {
2017-09-29 18:55:50 +03:00
return true
}
}
return false
default :
return false
}
}
2018-12-20 12:30:42 +03:00
// IsWhitelistedRequest is used to check if auth should be skipped for this request
2019-06-07 06:50:44 +03:00
func ( p * OAuthProxy ) IsWhitelistedRequest ( req * http . Request ) bool {
2017-04-07 14:55:48 +03:00
isPreflightRequestAllowed := p . skipAuthPreflight && req . Method == "OPTIONS"
return isPreflightRequestAllowed || p . IsWhitelistedPath ( req . URL . Path )
}
2018-12-20 12:30:42 +03:00
// IsWhitelistedPath is used to check if the request path is allowed without auth
2019-06-07 06:50:44 +03:00
func ( p * OAuthProxy ) IsWhitelistedPath ( path string ) bool {
2015-06-23 14:23:39 +03:00
for _ , u := range p . compiledRegex {
2019-06-07 06:50:44 +03:00
if u . MatchString ( path ) {
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
2015-06-23 14:23:39 +03:00
func getRemoteAddr ( req * http . Request ) ( s string ) {
s = req . RemoteAddr
if req . Header . Get ( "X-Real-IP" ) != "" {
s += fmt . Sprintf ( " (%q)" , req . Header . Get ( "X-Real-IP" ) )
}
return
}
2013-10-22 23:56:29 +04:00
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) ServeHTTP ( rw http . ResponseWriter , req * http . Request ) {
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 )
2015-06-23 14:23:39 +03:00
case path == p . PingPath :
p . PingPage ( rw )
2017-04-07 14:55:48 +03:00
case p . IsWhitelistedRequest ( req ) :
2015-06-23 14:23:39 +03:00
p . serveMux . ServeHTTP ( rw , req )
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 )
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 {
2019-02-10 19:37:45 +03:00
logger . Printf ( "Error obtaining redirect: %s" , err . Error ( ) )
2015-06-23 14:23:39 +03:00
p . ErrorPage ( rw , 500 , "Internal Error" , err . Error ( ) )
2014-10-15 00:22:38 +04:00
return
}
2015-06-23 14:23:39 +03:00
user , ok := p . ManualSignIn ( rw , req )
if ok {
2019-05-07 16:27:09 +03:00
session := & sessionsapi . SessionState { User : user }
2015-06-23 14:23:39 +03:00
p . SaveSession ( rw , req , session )
http . Redirect ( rw , req , redirect , 302 )
} 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
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 ) {
2017-03-28 04:14:38 +03:00
p . ClearSessionCookie ( rw , req )
2017-03-21 19:39:26 +03:00
http . Redirect ( rw , req , "/" , 302 )
}
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 ) {
2017-03-28 04:14:38 +03:00
nonce , err := cookie . Nonce ( )
if err != nil {
2019-02-10 19:37:45 +03:00
logger . Printf ( "Error obtaining nonce: %s" , err . Error ( ) )
2017-03-28 04:14:38 +03:00
p . ErrorPage ( rw , 500 , "Internal Error" , err . Error ( ) )
return
}
p . SetCSRFCookie ( rw , req , nonce )
2015-06-23 14:23:39 +03:00
redirect , err := p . GetRedirect ( req )
if err != nil {
2019-02-10 19:37:45 +03:00
logger . Printf ( "Error obtaining redirect: %s" , err . Error ( ) )
2015-06-23 14:23:39 +03:00
p . ErrorPage ( rw , 500 , "Internal Error" , err . Error ( ) )
return
2015-01-12 12:18:41 +03:00
}
2015-06-23 14:23:39 +03:00
redirectURI := p . GetRedirectURI ( req . Host )
2017-03-28 04:14:38 +03:00
http . Redirect ( rw , req , p . provider . GetLoginURL ( redirectURI , fmt . Sprintf ( "%v:%v" , nonce , redirect ) ) , 302 )
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 ) {
2015-06-23 14:23:39 +03:00
remoteAddr := getRemoteAddr ( req )
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 {
2019-02-10 20:01:13 +03:00
logger . Printf ( "Error while parsing OAuth2 callback: %s" + err . Error ( ) )
2015-06-23 14:23:39 +03:00
p . ErrorPage ( rw , 500 , "Internal 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 != "" {
2019-02-10 20:01:13 +03:00
logger . Printf ( "Error while parsing OAuth2 callback: %s " , errorString )
2015-06-23 14:23:39 +03:00
p . ErrorPage ( rw , 403 , "Permission Denied" , errorString )
2012-12-11 05:59:23 +04:00
return
}
2015-06-23 14:23:39 +03:00
session , err := p . redeemCode ( req . Host , req . Form . Get ( "code" ) )
if err != nil {
2019-05-07 20:47:15 +03:00
logger . Printf ( "Error redeeming code during OAuth2 callback: %s " , err . Error ( ) )
2015-06-23 14:23:39 +03:00
p . ErrorPage ( rw , 500 , "Internal Error" , "Internal Error" )
return
}
2017-03-28 04:14:38 +03:00
s := strings . SplitN ( req . Form . Get ( "state" ) , ":" , 2 )
if len ( s ) != 2 {
2019-04-23 19:36:18 +03:00
logger . Printf ( "Error while parsing OAuth2 state: invalid length" )
2017-03-28 04:14:38 +03:00
p . ErrorPage ( rw , 500 , "Internal Error" , "Invalid State" )
return
}
nonce := s [ 0 ]
redirect := s [ 1 ]
c , err := req . Cookie ( p . CSRFCookieName )
if err != nil {
2019-04-23 19:36:18 +03:00
logger . PrintAuthf ( session . Email , req , logger . AuthFailure , "Invalid authentication via OAuth2: unable too obtain CSRF cookie" )
2017-03-28 04:14:38 +03:00
p . ErrorPage ( rw , 403 , "Permission Denied" , err . Error ( ) )
return
}
p . ClearCSRFCookie ( rw , req )
if c . Value != nonce {
2019-04-23 19:36:18 +03:00
logger . PrintAuthf ( session . Email , req , logger . AuthFailure , "Invalid authentication via OAuth2: csrf token mismatch, potential attack" )
2017-03-28 04:14:38 +03:00
p . ErrorPage ( rw , 403 , "Permission Denied" , "csrf failed" )
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
2015-08-20 13:07:02 +03:00
if p . Validator ( session . Email ) && p . provider . ValidateGroup ( session . Email ) {
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 {
2019-02-10 19:37:45 +03:00
logger . Printf ( "%s %s" , remoteAddr , err )
2015-06-23 14:23:39 +03:00
p . ErrorPage ( rw , 500 , "Internal Error" , "Internal Error" )
2012-12-11 05:59:23 +04:00
return
}
2015-06-23 14:23:39 +03:00
http . Redirect ( rw , req , redirect , 302 )
} else {
2019-04-23 19:36:18 +03:00
logger . PrintAuthf ( session . Email , req , logger . AuthSuccess , "Invalid authentication via OAuth2: unauthorized" )
2015-06-23 14:23:39 +03:00
p . ErrorPage ( rw , 403 , "Permission Denied" , "Invalid Account" )
}
}
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 )
rw . WriteHeader ( http . StatusAccepted )
2015-10-08 16:27:00 +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 )
p . serveMux . ServeHTTP ( rw , req )
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
default :
// unknown error
logger . Printf ( "Unexpected internal error: %s" , err )
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
// Returns nil, ErrNeedsLogin if user needs to login.
// 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
var err error
2015-06-23 14:23:39 +03:00
var saveSession , clearSession , revalidated bool
2019-01-17 23:49:14 +03:00
if p . skipJwtBearerTokens && req . Header . Get ( "Authorization" ) != "" {
session , err = p . GetJwtSession ( req )
if err != nil {
2019-04-24 18:25:29 +03:00
logger . Printf ( "Error retrieving session from token in Authorization header: %s" , err )
2019-01-17 23:49:14 +03:00
}
if session != nil {
saveSession = false
}
2015-06-23 14:23:39 +03:00
}
2019-01-17 23:49:14 +03:00
remoteAddr := getRemoteAddr ( req )
if session == nil {
session , err = p . LoadCookiedSession ( req )
if err != nil {
logger . Printf ( "Error loading cookied session: %s" , err )
}
if session != nil && session . Age ( ) > p . CookieRefresh && p . CookieRefresh != time . Duration ( 0 ) {
logger . Printf ( "Refreshing %s old session cookie for %s (refresh after %s)" , session . Age ( ) , session , p . CookieRefresh )
saveSession = true
}
2015-06-23 14:23:39 +03:00
}
2019-01-17 23:49:14 +03:00
if session != nil {
var ok bool
if ok , err = p . provider . RefreshSessionIfNeeded ( session ) ; err != nil {
logger . Printf ( "%s removing session. error refreshing access token %s %s" , remoteAddr , err , session )
clearSession = true
session = nil
} else if ok {
saveSession = true
revalidated = true
}
2015-06-23 14:23:39 +03:00
}
if session != nil && session . IsExpired ( ) {
2019-04-23 19:36:18 +03:00
logger . Printf ( "Removing session: token expired %s" , session )
2015-06-23 14:23:39 +03:00
session = nil
saveSession = false
clearSession = true
}
2015-08-20 13:07:02 +03:00
if saveSession && ! revalidated && session != nil && session . AccessToken != "" {
2015-06-23 14:23:39 +03:00
if ! p . provider . ValidateSessionState ( session ) {
2019-04-23 19:36:18 +03:00
logger . Printf ( "Removing session: error validating %s" , session )
2015-06-23 14:23:39 +03:00
saveSession = false
session = nil
clearSession = true
2013-10-22 23:56:29 +04:00
}
2015-06-23 14:23:39 +03:00
}
2013-10-22 23:56:29 +04:00
2015-07-14 17:40:59 +03:00
if session != nil && session . Email != "" && ! p . Validator ( session . Email ) {
2019-04-23 19:36:18 +03:00
logger . Printf ( session . Email , req , logger . AuthFailure , "Invalid authentication via session: removing session %s" , session )
2015-06-23 14:23:39 +03:00
session = nil
saveSession = false
clearSession = true
}
2015-08-20 13:07:02 +03:00
if saveSession && session != nil {
2018-11-29 17:26:41 +03:00
err = p . SaveSession ( rw , req , session )
2015-06-23 14:23:39 +03:00
if err != nil {
2019-02-10 19:37:45 +03:00
logger . PrintAuthf ( session . Email , req , logger . AuthError , "Save session error %s" , err )
2019-06-07 06:50:44 +03:00
return nil , err
2012-12-11 05:59:23 +04:00
}
}
2012-12-17 22:38:33 +04:00
2015-06-23 14:23:39 +03:00
if clearSession {
2017-03-28 04:14:38 +03:00
p . ClearSessionCookie ( rw , req )
2012-12-11 05:59:23 +04:00
}
2015-06-23 14:23:39 +03:00
if session == nil {
session , err = p . CheckBasicAuth ( req )
2019-02-15 21:07:25 +03:00
if err != nil {
logger . Printf ( "Error during basic auth validation: %s" , err )
}
2012-12-11 05:59:23 +04:00
}
2015-06-23 14:23:39 +03:00
if session == nil {
2019-06-07 06:50:44 +03:00
return nil , ErrNeedsLogin
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 ) {
2014-11-09 22:51:10 +03:00
if p . PassBasicAuth {
2015-07-24 12:17:43 +03:00
req . SetBasicAuth ( session . User , p . BasicAuthPassword )
2015-06-23 14:23:39 +03:00
req . Header [ "X-Forwarded-User" ] = [ ] string { session . User }
if session . Email != "" {
req . Header [ "X-Forwarded-Email" ] = [ ] string { session . Email }
}
2012-12-11 05:59:23 +04:00
}
2016-02-08 18:57:47 +03:00
if p . PassUserHeaders {
req . Header [ "X-Forwarded-User" ] = [ ] string { session . User }
if session . Email != "" {
req . Header [ "X-Forwarded-Email" ] = [ ] string { session . Email }
}
}
2016-10-20 15:19:59 +03:00
if p . SetXAuthRequest {
rw . Header ( ) . Set ( "X-Auth-Request-User" , session . User )
if session . Email != "" {
rw . Header ( ) . Set ( "X-Auth-Request-Email" , session . Email )
}
2019-02-22 10:49:57 +03:00
if p . PassAccessToken && session . AccessToken != "" {
rw . Header ( ) . Set ( "X-Auth-Request-Access-Token" , session . AccessToken )
}
2016-10-20 15:19:59 +03:00
}
2015-06-23 14:23:39 +03:00
if p . PassAccessToken && session . AccessToken != "" {
req . Header [ "X-Forwarded-Access-Token" ] = [ ] string { session . AccessToken }
2015-04-03 03:57:17 +03:00
}
2018-01-27 13:14:19 +03:00
if p . PassAuthorization && session . IDToken != "" {
req . Header [ "Authorization" ] = [ ] string { fmt . Sprintf ( "Bearer %s" , session . IDToken ) }
}
if p . SetAuthorization && session . IDToken != "" {
rw . Header ( ) . Set ( "Authorization" , fmt . Sprintf ( "Bearer %s" , session . IDToken ) )
}
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
}
2018-12-20 12:30:42 +03:00
// CheckBasicAuth checks the requests Authorization header for basic auth
// credentials and authenticates these against the proxies HtpasswdFile
2019-05-07 16:27:09 +03:00
func ( p * OAuthProxy ) CheckBasicAuth ( req * http . Request ) ( * sessionsapi . SessionState , error ) {
2012-12-11 05:59:23 +04:00
if p . HtpasswdFile == nil {
2015-06-23 14:23:39 +03:00
return nil , nil
}
auth := req . Header . Get ( "Authorization" )
if auth == "" {
return nil , nil
2012-12-11 05:59:23 +04:00
}
2015-06-23 14:23:39 +03:00
s := strings . SplitN ( auth , " " , 2 )
2012-12-11 05:59:23 +04:00
if len ( s ) != 2 || s [ 0 ] != "Basic" {
2015-06-23 14:23:39 +03:00
return nil , fmt . Errorf ( "invalid Authorization header %s" , req . Header . Get ( "Authorization" ) )
2012-12-11 05:59:23 +04:00
}
2016-06-20 14:17:39 +03:00
b , err := b64 . StdEncoding . DecodeString ( s [ 1 ] )
2012-12-11 05:59:23 +04:00
if err != nil {
2015-06-23 14:23:39 +03:00
return nil , err
2012-12-11 05:59:23 +04:00
}
pair := strings . SplitN ( string ( b ) , ":" , 2 )
if len ( pair ) != 2 {
2015-06-23 14:23:39 +03:00
return nil , fmt . Errorf ( "invalid format %s" , b )
2012-12-11 05:59:23 +04:00
}
if p . HtpasswdFile . Validate ( pair [ 0 ] , pair [ 1 ] ) {
2019-02-10 20:01:13 +03:00
logger . PrintAuthf ( pair [ 0 ] , req , logger . AuthSuccess , "Authenticated via basic auth and HTpasswd File" )
2019-05-07 16:27:09 +03:00
return & sessionsapi . SessionState { User : pair [ 0 ] } , nil
2012-12-11 05:59:23 +04:00
}
2019-04-23 19:36:18 +03:00
logger . PrintAuthf ( pair [ 0 ] , req , logger . AuthFailure , "Invalid authentication via basic auth: not in Htpasswd File" )
2019-02-15 21:07:25 +03:00
return nil , nil
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 {
2019-01-30 13:13:12 +03:00
acceptValues , ok := req . Header [ "accept" ]
if ! ok {
acceptValues = req . Header [ "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
}
// ErrorJSON returns the error code witht an application/json mime type
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 )
}
2019-01-17 23:49:14 +03:00
// GetJwtSession loads a session based on a JWT token in the authorization header.
func ( p * OAuthProxy ) GetJwtSession ( req * http . Request ) ( * sessionsapi . SessionState , error ) {
rawBearerToken , err := p . findBearerToken ( req )
if err != nil {
return nil , err
}
ctx := context . Background ( )
var session * sessionsapi . SessionState
for _ , verifier := range p . jwtBearerVerifiers {
bearerToken , err := verifier . Verify ( ctx , rawBearerToken )
if err != nil {
logger . Printf ( "failed to verify bearer token: %v" , err )
continue
}
var claims struct {
2019-05-01 19:22:25 +03:00
Subject string ` json:"sub" `
2019-01-17 23:49:14 +03:00
Email string ` json:"email" `
Verified * bool ` json:"email_verified" `
}
if err := bearerToken . Claims ( & claims ) ; err != nil {
return nil , fmt . Errorf ( "failed to parse bearer token claims: %v" , err )
}
if claims . Email == "" {
2019-05-01 19:22:25 +03:00
claims . Email = claims . Subject
2019-01-17 23:49:14 +03:00
}
if claims . Verified != nil && ! * claims . Verified {
return nil , fmt . Errorf ( "email in id_token (%s) isn't verified" , claims . Email )
}
user := strings . Split ( claims . Email , "@" ) [ 0 ]
session = & sessionsapi . SessionState {
AccessToken : rawBearerToken ,
IDToken : rawBearerToken ,
RefreshToken : "" ,
ExpiresOn : bearerToken . Expiry ,
Email : claims . Email ,
User : user ,
}
return session , nil
}
return nil , fmt . Errorf ( "unable to verify jwt token %s" , req . Header . Get ( "Authorization" ) )
}
// findBearerToken finds a valid JWT token from the Authorization header of a given request.
func ( p * OAuthProxy ) findBearerToken ( req * http . Request ) ( string , error ) {
auth := req . Header . Get ( "Authorization" )
s := strings . SplitN ( auth , " " , 2 )
if len ( s ) != 2 {
return "" , fmt . Errorf ( "invalid authorization header %s" , auth )
}
2019-04-24 18:25:29 +03:00
jwtRegex := regexp . MustCompile ( ` ^eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$ ` )
2019-01-17 23:49:14 +03:00
var rawBearerToken string
2019-04-24 18:25:29 +03:00
if s [ 0 ] == "Bearer" && jwtRegex . MatchString ( s [ 1 ] ) {
2019-01-17 23:49:14 +03:00
rawBearerToken = s [ 1 ]
} else if s [ 0 ] == "Basic" {
// Check if we have a Bearer token masquerading in Basic
b , err := b64 . StdEncoding . DecodeString ( s [ 1 ] )
if err != nil {
return "" , err
}
pair := strings . SplitN ( string ( b ) , ":" , 2 )
if len ( pair ) != 2 {
return "" , fmt . Errorf ( "invalid format %s" , b )
}
user , password := pair [ 0 ] , pair [ 1 ]
// check user, user+password, or just password for a token
if jwtRegex . MatchString ( user ) {
// Support blank passwords or magic `x-oauth-basic` passwords - nothing else
if password == "" || password == "x-oauth-basic" {
rawBearerToken = user
}
} else if jwtRegex . MatchString ( password ) {
// support passwords and ignore user
rawBearerToken = password
}
2019-04-24 18:25:29 +03:00
}
if rawBearerToken == "" {
return "" , fmt . Errorf ( "no valid bearer token found in authorization header" )
2019-01-17 23:49:14 +03:00
}
return rawBearerToken , nil
}