Skip to content
This repository has been archived by the owner on Dec 7, 2020. It is now read-only.

Commit

Permalink
- fixing up the comments in the Config struct
Browse files Browse the repository at this point in the history
  • Loading branch information
gambol99 committed Apr 25, 2017
1 parent 614dada commit cb6a24f
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 41 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ FEATURES
* the order of the resources are no longer important, the framework will handle the routing
* improved the overall spec of the proxy by removing URL inspection and prefix checking
* removed the CORS implementation and using the default echo middles, which is more compliant
* added the --enable-encrypted-token option to enable encrypting the access token:wq

BREAKING CHANGES:
* the proxy no longer uses prefixes for resources, if you wish to use wildcard urls you need
Expand Down
34 changes: 18 additions & 16 deletions config_sample.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ enable-refresh-tokens: true
enable-logging: true
# log in json format
enable-json-logging: true
# should the access token be encrypted - you need an encryption-key if 'true'
enable-encrypted-token: false
# do not redirec the request, simple 307 it
no-redirects: false
# the location of a certificate you wish the proxy to use for TLS support
Expand Down Expand Up @@ -55,22 +57,22 @@ add-claims:
- name
# a collection of resource i.e. urls that you wish to protect
resources:
- uri: /admin/test
# the methods on this url that should be protected, if missing, we assuming all
methods:
- GET
# a list of roles the user must have in order to accces urls under the above
roles:
- openvpn:vpn-test
- uri: /admin/white_listed
# permits a url prefix through, bypassing the admission controls
white-listed: true
- uri: /admin/*
methods:
- GET
roles:
- openvpn:vpn-user
- openvpn:prod-vpn
- uri: /admin/test
# the methods on this url that should be protected, if missing, we assuming all
methods:
- GET
# a list of roles the user must have in order to accces urls under the above
roles:
- openvpn:vpn-test
- uri: /admin/white_listed
# permits a url prefix through, bypassing the admission controls
white-listed: true
- uri: /admin/*
methods:
- GET
roles:
- openvpn:vpn-user
- openvpn:prod-vpn

# an array of origins (Access-Control-Allow-Origin)
cors-origins: []
Expand Down
6 changes: 4 additions & 2 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ var (
ErrRefreshTokenExpired = errors.New("the refresh token has expired")
// ErrNoTokenAudience indicates their is not audience in the token
ErrNoTokenAudience = errors.New("the token does not audience in claims")
// ErrDecryption indicates we can't decrypt the token
ErrDecryption = errors.New("failed to decrypt token")
)

// Resource represents a url resource to protect
Expand Down Expand Up @@ -191,12 +193,12 @@ type Config struct {
CorsHeaders []string `json:"cors-headers" yaml:"cors-headers" usage:"set of headers to add to the CORS access control (Access-Control-Allow-Headers)"`
// CorsExposedHeaders are the exposed header fields
CorsExposedHeaders []string `json:"cors-exposed-headers" yaml:"cors-exposed-headers" usage:"expose cors headers access control (Access-Control-Expose-Headers)"`
// CorsCredentials set the creds flag
// CorsCredentials set the credentials flag
CorsCredentials bool `json:"cors-credentials" yaml:"cors-credentials" usage:"credentials access control header (Access-Control-Allow-Credentials)"`
// CorsMaxAge is the age for CORS
CorsMaxAge time.Duration `json:"cors-max-age" yaml:"cors-max-age" usage:"max age applied to cors headers (Access-Control-Max-Age)"`

// Hostname is a list of hostname's the service should response to
// Hostnames is a list of hostname's the service should response to
Hostnames []string `json:"hostnames" yaml:"hostnames" usage:"list of hostnames the service will respond to"`

// Store is a url for a store resource, used to hold the refresh tokens
Expand Down
30 changes: 10 additions & 20 deletions handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,10 @@ func (r *oauthProxy) oauthAuthorizationHandler(cx echo.Context) error {

// oauthCallbackHandler is responsible for handling the response from oauth service
func (r *oauthProxy) oauthCallbackHandler(cx echo.Context) error {
// step: is token verification switched on?
if r.config.SkipTokenVerification {
return cx.NoContent(http.StatusNotAcceptable)
}
// step: ensure we have a authorization code to exchange
// step: ensure we have a authorization code
code := cx.QueryParam("code")
if code == "" {
return cx.NoContent(http.StatusBadRequest)
Expand All @@ -153,27 +152,26 @@ func (r *oauthProxy) oauthCallbackHandler(cx echo.Context) error {
return cx.NoContent(http.StatusInternalServerError)
}

// step: exchange the authorization for a access token
resp, err := exchangeAuthenticationCode(client, code)
if err != nil {
log.WithFields(log.Fields{"error": err.Error()}).Errorf("unable to exchange code for access token")
return r.accessForbidden(cx)
}

// step: parse decode the identity token
// Flow: once we exchange the authorization code we parse the ID Token; we then check for a access token,
// if a access token is present and we can decode it, we use that as the session token, otherwise we default
// to the ID Token.
token, identity, err := parseToken(resp.IDToken)
if err != nil {
log.WithFields(log.Fields{"error": err.Error()}).Errorf("unable to parse id token for identity")
return r.accessForbidden(cx)
}

// step: attempt to decode the access token else we default to the id token
access, id, err := parseToken(resp.AccessToken)
if err != nil {
log.WithFields(log.Fields{"error": err.Error()}).Errorf("unable to parse the access token, using id token only")
} else {
if err == nil {
token = access
identity = id
} else {
log.WithFields(log.Fields{"error": err.Error()}).Warn("unable to parse the access token, using id token only")
}

// step: check the access token is valid
Expand All @@ -195,7 +193,7 @@ func (r *oauthProxy) oauthCallbackHandler(cx echo.Context) error {
"email": identity.Email,
"expires": identity.ExpiresAt.Format(time.RFC3339),
"duration": time.Until(identity.ExpiresAt).String(),
}).Infof("issuing access token for user, email: %s", identity.Email)
}).Info("issuing access token for user")

// step: does the response has a refresh token and we are NOT ignore refresh tokens?
if r.config.EnableRefreshTokens && resp.RefreshToken != "" {
Expand All @@ -205,7 +203,6 @@ func (r *oauthProxy) oauthCallbackHandler(cx echo.Context) error {
log.WithFields(log.Fields{"error": err.Error()}).Errorf("failed to encrypt the refresh token")
return cx.NoContent(http.StatusInternalServerError)
}

// drop in the access token - cookie expiration = access token
r.dropAccessTokenCookie(cx.Request(), cx.Response().Writer, accessToken, r.getAccessCookieExpiration(token, resp.RefreshToken))

Expand Down Expand Up @@ -247,7 +244,6 @@ func (r *oauthProxy) oauthCallbackHandler(cx echo.Context) error {
// loginHandler provide's a generic endpoint for clients to perform a user_credentials login to the provider
func (r *oauthProxy) loginHandler(cx echo.Context) error {
errorMsg, code, err := func() (string, int, error) {
// step: check if the handler is disable
if !r.config.EnableLoginHandler {
return "attempt to login when login handler is disabled", http.StatusNotImplemented, errors.New("login handler disabled")
}
Expand All @@ -257,7 +253,6 @@ func (r *oauthProxy) loginHandler(cx echo.Context) error {
return "request does not have both username and password", http.StatusBadRequest, errors.New("no credentials")
}

// step: get the client
client, err := r.client.OAuthClient()
if err != nil {
return "unable to create the oauth client for user_credentials request", http.StatusInternalServerError, err
Expand Down Expand Up @@ -309,10 +304,10 @@ func emptyHandler(cx echo.Context) error {
// - if it's just a access token, the cookie is deleted
// - if the user has a refresh token, the token is invalidated by the provider
// - optionally, the user can be redirected by to a url
//
func (r *oauthProxy) logoutHandler(cx echo.Context) error {
// the user can specify a url to redirect the back
redirectURL := cx.QueryParam("redirect")

// step: drop the access token
user, err := r.getIdentity(cx.Request())
if err != nil {
Expand All @@ -336,15 +331,12 @@ func (r *oauthProxy) logoutHandler(cx echo.Context) error {
}()
}

// step: get the revocation endpoint from either the idp and or the user config
revocationURL := defaultTo(r.config.RevocationEndpoint, r.idp.EndSessionEndpoint.String())

// step: do we have a revocation endpoint?
if revocationURL != "" {
client, err := r.client.OAuthClient()
if err != nil {
log.WithFields(log.Fields{"error": err.Error()}).Errorf("unable to retrieve the openid client")

return cx.NoContent(http.StatusInternalServerError)
}

Expand All @@ -360,19 +352,17 @@ func (r *oauthProxy) logoutHandler(cx echo.Context) error {
log.WithFields(log.Fields{"error": err.Error()}).Errorf("unable to construct the revocation request")
return cx.NoContent(http.StatusInternalServerError)
}

// step: add the authentication headers and content-type
request.SetBasicAuth(encodedID, encodedSecret)
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

// step: attempt to make the
response, err := client.HttpClient().Do(request)
if err != nil {
log.WithFields(log.Fields{"error": err.Error()}).Errorf("unable to post to revocation endpoint")
return nil
}

// step: add a log for debugging
// step: check the response
switch response.StatusCode {
case http.StatusNoContent:
log.WithFields(log.Fields{
Expand Down
9 changes: 7 additions & 2 deletions server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,9 @@ func TestTokenEncryption(t *testing.T) {
requests := []fakeRequest{
{
URI: "/auth_all/test",
Redirects: true,
HasLogin: true,
ExpectedProxy: true,
Redirects: true,
ExpectedProxyHeaders: map[string]string{
"X-Auth-Email": "gambol99@gmail.com",
"X-Auth-Userid": "rjayawardene",
Expand All @@ -216,6 +216,12 @@ func TestTokenEncryption(t *testing.T) {
},
ExpectedCode: http.StatusOK,
},
// the token must be encrypted
{
URI: "/auth_all/test",
HasToken: true,
ExpectedCode: http.StatusUnauthorized,
},
}
newFakeProxy(c).RunTests(t, requests)
}
Expand Down Expand Up @@ -277,7 +283,6 @@ func newFakeKeycloakConfig() *Config {
Listen: "127.0.0.1:0",
EnableAuthorizationHeader: true,
EnableLoginHandler: true,
EncryptionKey: "AgXa7xRcoClDEU0ZDSH4X0XhL5Qy2Z2j",
Scopes: []string{},
Resources: []*Resource{
{
Expand Down
2 changes: 1 addition & 1 deletion session.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func (r *oauthProxy) getIdentity(req *http.Request) (*userContext, error) {
}
if r.config.EnableEncryptedToken {
if access, err = decodeText(access, r.config.EncryptionKey); err != nil {
return nil, err
return nil, ErrDecryption
}
}
token, err := jose.ParseJWT(access)
Expand Down

0 comments on commit cb6a24f

Please sign in to comment.