2012-12-11 05:59:23 +04:00
package main
import (
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"
2012-12-11 05:59:23 +04:00
"log"
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
2015-11-16 06:08:30 +03:00
"github.com/18F/hmacauth"
2015-06-23 14:23:39 +03:00
"github.com/bitly/oauth2_proxy/cookie"
2015-05-21 09:50:21 +03:00
"github.com/bitly/oauth2_proxy/providers"
2012-12-11 05:59:23 +04:00
)
2015-11-16 06:08:30 +03:00
const SignatureHeader = "GAP-Signature"
var SignatureHeaders [ ] string = [ ] string {
"Content-Length" ,
"Content-Md5" ,
"Content-Type" ,
"Date" ,
"Authorization" ,
"X-Forwarded-User" ,
"X-Forwarded-Email" ,
"X-Forwarded-Access-Token" ,
"Cookie" ,
"Gap-Auth" ,
}
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
CookieSecure bool
CookieHttpOnly bool
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
2015-03-30 22:48:30 +03:00
provider providers . Provider
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
2015-06-23 14:23:39 +03:00
CookieCipher * cookie . Cipher
2015-01-12 12:18:41 +03:00
skipAuthRegex [ ] string
2017-04-07 14:55:48 +03:00
skipAuthPreflight bool
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
}
2015-03-19 23:37:16 +03:00
type UpstreamProxy struct {
upstream string
handler http . Handler
2015-11-16 06:08:30 +03:00
auth hmacauth . HmacAuth
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 )
}
2015-03-19 23:37:16 +03:00
u . handler . ServeHTTP ( w , r )
}
2014-12-01 04:12:33 +03:00
func NewReverseProxy ( target * url . URL ) ( proxy * httputil . ReverseProxy ) {
2015-03-17 22:15:15 +03:00
return httputil . NewSingleHostReverseProxy ( target )
}
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 = ""
}
}
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
}
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
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 {
case "http" , "https" :
u . Path = ""
log . Printf ( "mapping path %q => upstream %q" , path , u )
proxy := NewReverseProxy ( u )
if ! opts . PassHostHeader {
setProxyUpstreamHostHeader ( proxy , u )
} else {
setProxyDirector ( proxy )
}
2015-11-16 06:08:30 +03:00
serveMux . Handle ( path ,
& UpstreamProxy { u . Host , proxy , auth } )
2015-09-23 23:00:36 +03:00
case "file" :
if u . Fragment != "" {
path = u . Fragment
}
log . Printf ( "mapping path %q => file system %q" , path , u . Path )
proxy := NewFileServer ( path , u . Path )
2015-11-16 06:08:30 +03:00
serveMux . Handle ( path , & UpstreamProxy { path , proxy , 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 {
log . Printf ( "compiled skip-auth-regex => %q" , u )
}
2015-11-09 02:47:44 +03:00
redirectURL := opts . redirectURL
redirectURL . Path = fmt . Sprintf ( "%s/callback" , opts . ProxyPrefix )
2012-12-11 05:59:23 +04:00
2015-11-09 02:57:01 +03:00
log . Printf ( "OAuthProxy configured for %s Client ID: %s" , opts . provider . Data ( ) . ProviderName , opts . ClientID )
2014-11-10 06:21:46 +03:00
domain := opts . CookieDomain
if domain == "" {
domain = "<default>"
}
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
2015-06-22 22:10:08 +03:00
log . Printf ( "Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s refresh:%s" , opts . CookieName , opts . CookieSecure , opts . CookieHttpOnly , opts . CookieExpire , domain , refresh )
2015-03-18 06:13:45 +03:00
2015-06-23 14:23:39 +03:00
var cipher * cookie . Cipher
2015-05-09 23:08:55 +03:00
if opts . PassAccessToken || ( opts . CookieRefresh != time . Duration ( 0 ) ) {
2015-04-03 03:57:17 +03:00
var err error
2016-06-20 14:17:39 +03:00
cipher , err = cookie . NewCipher ( secretBytes ( opts . CookieSecret ) )
2015-04-03 03:57:17 +03:00
if err != nil {
2016-06-20 14:17:39 +03:00
log . Fatal ( "cookie-secret error: " , err )
2015-04-03 03:57:17 +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 ,
CookieSecure : opts . CookieSecure ,
CookieHttpOnly : opts . CookieHttpOnly ,
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 ) ,
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
2015-11-11 03:42:35 +03:00
ProxyPrefix : opts . ProxyPrefix ,
provider : opts . provider ,
serveMux : serveMux ,
redirectURL : redirectURL ,
skipAuthRegex : opts . SkipAuthRegex ,
2017-04-07 14:55:48 +03:00
skipAuthPreflight : opts . SkipAuthPreflight ,
2015-11-11 03:42:35 +03:00
compiledRegex : opts . CompiledRegex ,
2016-10-20 15:19:59 +03:00
SetXAuthRequest : opts . SetXAuthRequest ,
2015-11-11 03:42:35 +03:00
PassBasicAuth : opts . PassBasicAuth ,
2017-03-29 16:36:38 +03:00
PassUserHeaders : opts . PassUserHeaders ,
2015-11-11 03:42:35 +03:00
BasicAuthPassword : opts . BasicAuthPassword ,
PassAccessToken : opts . PassAccessToken ,
SkipProviderButton : opts . SkipProviderButton ,
CookieCipher : cipher ,
templates : loadTemplates ( opts . CustomTemplatesDir ) ,
2016-06-19 06:53:42 +03:00
Footer : opts . Footer ,
2012-12-11 05:59:23 +04:00
}
}
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 {
2015-03-17 23:25:19 +03:00
u . Scheme = "https"
} else {
u . Scheme = "http"
}
}
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
}
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) redeemCode ( host , code string ) ( s * providers . 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
}
2015-06-23 14:23:39 +03:00
return
2014-08-08 00:16:39 +04:00
}
2017-03-28 04:14:38 +03:00
func ( p * OAuthProxy ) MakeSessionCookie ( req * http . Request , value string , expiration time . Duration , now time . Time ) * http . Cookie {
if value != "" {
value = cookie . SignedValue ( p . CookieSeed , p . CookieName , value , now )
if len ( value ) > 4096 {
// Cookies cannot be larger than 4kb
log . Printf ( "WARNING - Cookie Size: %d bytes" , len ( value ) )
}
}
return p . makeCookie ( req , p . CookieName , value , expiration , now )
}
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
domain := req . Host
if h , _ , err := net . SplitHostPort ( domain ) ; err == nil {
domain = h
}
if p . CookieDomain != "" {
if ! strings . HasSuffix ( domain , p . CookieDomain ) {
log . Printf ( "Warning: request host is %q but using configured cookie domain of %q" , domain , p . CookieDomain )
}
2014-11-09 22:51:10 +03:00
domain = p . CookieDomain
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 ,
2012-12-11 05:59:23 +04:00
Path : "/" ,
Domain : domain ,
2015-01-19 18:52:18 +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
}
}
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 ( ) ) )
}
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
}
2017-03-28 04:14:38 +03:00
func ( p * OAuthProxy ) ClearSessionCookie ( rw http . ResponseWriter , req * http . Request ) {
http . SetCookie ( rw , p . MakeSessionCookie ( req , "" , time . Hour * - 1 , time . Now ( ) ) )
}
func ( p * OAuthProxy ) SetSessionCookie ( rw http . ResponseWriter , req * http . Request , val string ) {
http . SetCookie ( rw , p . MakeSessionCookie ( req , val , p . CookieExpire , time . Now ( ) ) )
2012-12-26 19:35:02 +04:00
}
2012-12-26 19:55:41 +04:00
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) LoadCookiedSession ( req * http . Request ) ( * providers . SessionState , time . Duration , error ) {
2015-06-23 14:23:39 +03:00
var age time . Duration
c , err := req . Cookie ( p . CookieName )
if err != nil {
// always http.ErrNoCookie
return nil , age , fmt . Errorf ( "Cookie %q not present" , p . CookieName )
}
val , timestamp , ok := cookie . Validate ( c , p . CookieSeed , p . CookieExpire )
if ! ok {
return nil , age , errors . New ( "Cookie Signature not valid" )
2012-12-26 19:35:02 +04:00
}
2015-06-23 14:23:39 +03:00
session , err := p . provider . SessionFromCookie ( val , p . CookieCipher )
2015-05-08 18:51:43 +03:00
if err != nil {
2015-06-23 14:23:39 +03:00
return nil , age , err
2012-12-26 19:35:02 +04:00
}
2015-06-23 14:23:39 +03:00
age = time . Now ( ) . Truncate ( time . Second ) . Sub ( timestamp )
return session , age , nil
}
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) SaveSession ( rw http . ResponseWriter , req * http . Request , s * providers . SessionState ) error {
2015-06-23 14:23:39 +03:00
value , err := p . provider . CookieForSession ( s , p . CookieCipher )
if err != nil {
return err
}
2017-03-28 04:14:38 +03:00
p . SetSessionCookie ( rw , req , value )
2015-06-23 14:23:39 +03:00
return nil
2012-12-26 19:35:02 +04:00
}
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: /" )
}
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
}
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) ErrorPage ( rw http . ResponseWriter , code int , title string , message string ) {
2012-12-17 22:15:23 +04:00
log . Printf ( "ErrorPage %d %s %s" , code , title , message )
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
}
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
2015-04-07 05:10:03 +03:00
redirect_url := req . URL . RequestURI ( )
2016-11-16 09:36:18 +03:00
if req . Header . Get ( "X-Auth-Request-Redirect" ) != "" {
redirect_url = req . Header . Get ( "X-Auth-Request-Redirect" )
}
2015-05-30 01:47:40 +03:00
if redirect_url == p . SignInPath {
2015-04-07 05:10:03 +03:00
redirect_url = "/"
}
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 ( ) ,
2015-04-07 05:10:03 +03:00
Redirect : redirect_url ,
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
}
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 ) {
2015-03-19 23:37:16 +03:00
log . Printf ( "authenticated %q via HtpasswdFile" , user )
2012-12-26 19:55:41 +04:00
return user , true
}
return "" , false
}
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" )
if redirect == "" || ! strings . HasPrefix ( redirect , "/" ) || strings . HasPrefix ( redirect , "//" ) {
2013-10-24 19:31:08 +04:00
redirect = "/"
}
2017-03-28 04:14:38 +03:00
return
2013-10-24 19:31:08 +04:00
}
2017-04-07 14:55:48 +03:00
func ( p * OAuthProxy ) IsWhitelistedRequest ( req * http . Request ) ( ok bool ) {
isPreflightRequestAllowed := p . skipAuthPreflight && req . Method == "OPTIONS"
return isPreflightRequestAllowed || p . IsWhitelistedPath ( req . URL . Path )
}
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) IsWhitelistedPath ( path string ) ( ok bool ) {
2015-06-23 14:23:39 +03:00
for _ , u := range p . compiledRegex {
ok = u . MatchString ( path )
if ok {
return
}
2012-12-26 19:55:41 +04:00
}
2015-06-23 14:23:39 +03:00
return
}
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
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 {
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 {
session := & providers . SessionState { User : user }
p . SaveSession ( rw , req , session )
http . Redirect ( rw , req , redirect , 302 )
} else {
p . SignInPage ( rw , req , 200 )
}
}
2015-01-12 12:18:41 +03:00
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 )
}
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 {
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 {
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
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 {
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 != "" {
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 {
log . Printf ( "%s error redeeming code %s" , remoteAddr , err )
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 {
p . ErrorPage ( rw , 500 , "Internal Error" , "Invalid State" )
return
}
nonce := s [ 0 ]
redirect := s [ 1 ]
c , err := req . Cookie ( p . CSRFCookieName )
if err != nil {
p . ErrorPage ( rw , 403 , "Permission Denied" , err . Error ( ) )
return
}
p . ClearCSRFCookie ( rw , req )
if c . Value != nonce {
log . Printf ( "%s csrf token mismatch, potential attack" , remoteAddr )
p . ErrorPage ( rw , 403 , "Permission Denied" , "csrf failed" )
return
}
if ! strings . HasPrefix ( redirect , "/" ) || strings . HasPrefix ( 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 ) {
2015-06-23 14:23:39 +03:00
log . Printf ( "%s authentication complete %s" , remoteAddr , session )
err := p . SaveSession ( rw , req , session )
2012-12-11 05:59:23 +04:00
if err != nil {
2015-06-23 14:23:39 +03:00
log . Printf ( "%s %s" , remoteAddr , err )
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 {
log . Printf ( "%s Permission Denied: %q is unauthorized" , remoteAddr , session . Email )
p . ErrorPage ( rw , 403 , "Permission Denied" , "Invalid Account" )
}
}
2012-12-11 05:59:23 +04:00
2015-10-08 16:27:00 +03:00
func ( p * OAuthProxy ) AuthenticateOnly ( rw http . ResponseWriter , req * http . Request ) {
2015-10-08 21:10:28 +03:00
status := p . Authenticate ( rw , req )
if status == http . StatusAccepted {
2015-10-08 16:27:00 +03:00
rw . WriteHeader ( http . StatusAccepted )
2015-10-08 21:10:28 +03:00
} else {
http . Error ( rw , "unauthorized request" , http . StatusUnauthorized )
2015-10-08 16:27:00 +03:00
}
}
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) Proxy ( rw http . ResponseWriter , req * http . Request ) {
2015-10-08 21:10:28 +03:00
status := p . Authenticate ( rw , req )
if status == http . StatusInternalServerError {
p . ErrorPage ( rw , http . StatusInternalServerError ,
"Internal Error" , "Internal Error" )
} else if status == http . StatusForbidden {
2015-11-11 03:42:35 +03:00
if p . SkipProviderButton {
p . OAuthStart ( rw , req )
} else {
p . SignInPage ( rw , req , http . StatusForbidden )
}
2015-10-08 21:10:28 +03:00
} else {
p . serveMux . ServeHTTP ( rw , req )
}
}
func ( p * OAuthProxy ) Authenticate ( rw http . ResponseWriter , req * http . Request ) int {
2015-06-23 14:23:39 +03:00
var saveSession , clearSession , revalidated bool
remoteAddr := getRemoteAddr ( req )
session , sessionAge , err := p . LoadCookiedSession ( req )
if err != nil {
log . Printf ( "%s %s" , remoteAddr , err )
}
if session != nil && sessionAge > p . CookieRefresh && p . CookieRefresh != time . Duration ( 0 ) {
log . Printf ( "%s refreshing %s old session cookie for %s (refresh after %s)" , remoteAddr , sessionAge , session , p . CookieRefresh )
saveSession = true
}
if ok , err := p . provider . RefreshSessionIfNeeded ( session ) ; err != nil {
log . Printf ( "%s removing session. error refreshing access token %s %s" , remoteAddr , err , session )
clearSession = true
session = nil
} else if ok {
saveSession = true
revalidated = true
}
if session != nil && session . IsExpired ( ) {
log . Printf ( "%s removing session. token expired %s" , remoteAddr , session )
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 ) {
log . Printf ( "%s removing session. error validating %s" , remoteAddr , session )
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 ) {
2015-06-23 14:23:39 +03:00
log . Printf ( "%s Permission Denied: removing session %s" , remoteAddr , session )
session = nil
saveSession = false
clearSession = true
}
2015-08-20 13:07:02 +03:00
if saveSession && session != nil {
2015-06-23 14:23:39 +03:00
err := p . SaveSession ( rw , req , session )
if err != nil {
log . Printf ( "%s %s" , remoteAddr , err )
2015-10-08 21:10:28 +03:00
return http . StatusInternalServerError
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 )
if err != nil {
log . Printf ( "%s %s" , remoteAddr , err )
}
2012-12-11 05:59:23 +04:00
}
2015-06-23 14:23:39 +03:00
if session == nil {
2015-10-08 21:10:28 +03:00
return http . StatusForbidden
2012-12-11 05:59:23 +04:00
}
// At this point, the user is authenticated. proxy normally
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 )
}
}
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
}
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
}
2015-10-08 21:10:28 +03:00
return http . StatusAccepted
2012-12-11 05:59:23 +04:00
}
2015-11-09 02:57:01 +03:00
func ( p * OAuthProxy ) CheckBasicAuth ( req * http . Request ) ( * providers . 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 ] ) {
2015-03-19 23:37:16 +03:00
log . Printf ( "authenticated %q via basic auth" , pair [ 0 ] )
2015-06-23 14:23:39 +03:00
return & providers . SessionState { User : pair [ 0 ] } , nil
2012-12-11 05:59:23 +04:00
}
2015-06-23 14:23:39 +03:00
return nil , fmt . Errorf ( "%s not in HtpasswdFile" , pair [ 0 ] )
2012-12-11 05:59:23 +04:00
}