Skip to content

Commit

Permalink
Created auth configuration
Browse files Browse the repository at this point in the history
Now allows the server to be configured with plain OAuth/OIDC, HEART or none
Cleaned up previous server config
  • Loading branch information
eedrummer committed Aug 30, 2016
1 parent a6b42da commit 51a1497
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 11 deletions.
74 changes: 74 additions & 0 deletions auth/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package auth

// What type of authentication and authorization will be used
type Method int

const (
// No authentication or authorization
AuthTypeNone = iota
// "Plain" OpenID Connect and OAuth 2.0
AuthTypeOIDC
// HEART profiled OpenID Connect and OAuth 2.0
AuthTypeHEART
)

// Config represents configuration information necessary to set up authentication
// and authorization for the FHIR server
type Config struct {
Method Method
ClientID string
ClientSecret string
AuthorizationURL string
TokenURL string
IntrospectionURL string
UserInfoURL string
JWKPath string
OPURL string
SessionSecret string
}

// None provides a server config where no authorization or authentication will
// be provided
func None() Config {
return Config{Method: AuthTypeNone}
}

// OIDC provides a server configuration that will act as an OpenID Connect relying
// party for authentication, and will perform OAuth 2.0 token introspection to the
// same server for authorization.
//
// This configuration still uses the HEART scopes for authorizing access to FHIR
// resources when using OAuth 2.0.
//
// clientID is the registered ID at the OpenID Connect Provider (OP)
// clientSecret is the secret for the client (usually generated by the OP)
// authorizationURL Where to redirect users for authorization
// tokenURL Where to obtain OAuth 2.0 tokens
// userInfoURL The location of the OpenID Connect UserInfo endpoint
// sessionSecret The secret that will be used to encrypt the session when it is
// stored in a user's cookie
func OIDC(clientID, clientSecret, authorizationURL, tokenURL, userInfoURL, introspectionURL, sessionSecret string) Config {
return Config{Method: AuthTypeOIDC, ClientID: clientID, ClientSecret: clientSecret,
AuthorizationURL: authorizationURL, TokenURL: tokenURL, UserInfoURL: userInfoURL,
IntrospectionURL: introspectionURL, SessionSecret: sessionSecret}
}

// HEART provides a server configuration that will act as a HEART profiled
// OpenID Connect relying party for authentication, and will perform HEART profiled
// OAuth 2.0 token introspection to the same server for authorization.
//
// This configuration uses the HEART scopes for authorizing access to FHIR
// resources when using OAuth 2.0.
//
// clientID is the registered ID at the OpenID Connect Provider (OP)
// jwkPath is the file location of the private key for this client in JWK format
// it is expected that the corresponding public key has been registered at the
// OP
// opURL the location of the OIDC OP. Discovery will be used to locate all of the
// other necessary endpoints.
// sessionSecret The secret that will be used to encrypt the session when it is
// stored in a user's cookie
func HEART(clientID, jwkPath, opURL, sessionSecret string) Config {
return Config{Method: AuthTypeHEART, ClientID: clientID, JWKPath: jwkPath,
OPURL: opURL, SessionSecret: sessionSecret}
}
6 changes: 6 additions & 0 deletions auth/heart_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ func HEARTScopesHandler(resourceName string) gin.HandlerFunc {
writeScope := fmt.Sprintf("user/%s.write", resourceName)
allScope := fmt.Sprintf("user/%s.*", resourceName)
return func(c *gin.Context) {
_, exists := c.Get("UserInfo")
if exists {
// This is an OIDC authenticated request. Let it pass through.
return
}

if c.Request.Method == "GET" {
if !includesAnyScope(c, allResourcesAllScope, allResourcesReadScope, readScope, allScope) {
c.String(http.StatusForbidden, "You do not have permission to view this resource")
Expand Down
4 changes: 2 additions & 2 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ package main
import (
"flag"

"github.com/intervention-engine/fhir/auth"
"github.com/intervention-engine/fhir/server"
)

func main() {
smartAuth := flag.Bool("smart", false, "Enables SMART Authorization")
reqLog := flag.Bool("reqlog", false, "Enables request logging -- do NOT use in production")
flag.Parse()
s := server.NewServer("localhost")
if *reqLog {
s.Engine.Use(server.RequestLoggerHandler)
}

config := server.Config{UseSmartAuth: *smartAuth}
config := server.Config{Auth: auth.None()}
s.Run(config)
}
18 changes: 10 additions & 8 deletions server/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package server

import "gopkg.in/mgo.v2"
import (
"github.com/intervention-engine/fhir/auth"
"gopkg.in/mgo.v2"
)

// Although we got rid of the global in the fhir package, the ie project still needs it
// Once ie removes the dependency on the global, this should go away
Expand All @@ -9,11 +12,10 @@ var Database *mgo.Database
// Config is used to hold information about the configuration of the FHIR
// server.
type Config struct {
// Determines whether the server will enforce authorization decisions based on
// OAuth 2 scopes specified by SMART App Authorization. See SmartAuthHandler
// for more details
UseSmartAuth bool

// Enable the basic Echo logger
UseLoggingMiddleware bool
// ServerURL is the full URL for the root of the server. This may be used
// by other middleware to compute redirect URLs
ServerURL string
// Auth determines what, if any authentication and authorization will be used
// by the FHIR server
Auth auth.Config
}
49 changes: 48 additions & 1 deletion server/routing.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package server

import (
"github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/intervention-engine/fhir/auth"
"github.com/mitre/heart"
"golang.org/x/oauth2"
)

// RegisterController registers the CRUD routes (and middleware) for a FHIR resource
Expand All @@ -14,7 +17,12 @@ func RegisterController(name string, e *gin.Engine, m []gin.HandlerFunc, dal Dat
rcBase.Use(m...)
}

if config.UseSmartAuth {
switch config.Auth.Method {
case auth.AuthTypeNone:
// do nothing
case auth.AuthTypeOIDC:
rcBase.Use(auth.HEARTScopesHandler(name))
case auth.AuthTypeHEART:
rcBase.Use(auth.HEARTScopesHandler(name))
}

Expand All @@ -32,6 +40,45 @@ func RegisterController(name string, e *gin.Engine, m []gin.HandlerFunc, dal Dat
// RegisterRoutes registers the routes for each of the FHIR resources
func RegisterRoutes(e *gin.Engine, config map[string][]gin.HandlerFunc, dal DataAccessLayer, serverConfig Config) {

switch serverConfig.Auth.Method {
case auth.AuthTypeNone:
// do nothing
case auth.AuthTypeOIDC:
// Set up sessions so we can keep track of the logged in user
store := sessions.NewCookieStore([]byte(serverConfig.Auth.SessionSecret))
e.Use(sessions.Sessions("mysession", store))
// The OIDCAuthenticationHandler is set up before the IndexHandler in the handler function
// chain. It will check to see if the user is logged in based on their session. If they are not
// the user will be redirected to the authentication endpoint at the OP.
oauthConfig := oauth2.Config{ClientID: serverConfig.Auth.ClientID,
ClientSecret: serverConfig.Auth.ClientSecret,
Endpoint: oauth2.Endpoint{AuthURL: serverConfig.Auth.AuthorizationURL,
TokenURL: serverConfig.Auth.TokenURL},
}
oidcHandler := auth.OIDCAuthenticationHandler(oauthConfig)
oauthHandler := auth.OAuthIntrospectionHandler(serverConfig.Auth.ClientID,
serverConfig.Auth.ClientSecret, serverConfig.Auth.IntrospectionURL)
e.Use(func(c *gin.Context) {
if c.Request.Header.Get("Authorization") != "" {
oauthHandler(c)
} else {
oidcHandler(c)
}
})
// This handler is to take the redirect from the OP when the user logs in. It will
// then fetch information about the user by hitting the user info endpoint and put
// that in the session. Lastly, this handler is set up to redirect the user back
// to the root.
e.GET("/redirect", auth.RedirectHandler(oauthConfig, serverConfig.ServerURL,
serverConfig.Auth.UserInfoURL))
e.GET("/logout", heart.LogoutHandler)

case auth.AuthTypeHEART:
heart.SetUpRoutes(serverConfig.Auth.JWKPath, serverConfig.Auth.ClientID, serverConfig.Auth.OPURL,
serverConfig.ServerURL, serverConfig.Auth.SessionSecret, e)

}

// Batch Support
batch := NewBatchController(dal)
batchHandlers := make([]gin.HandlerFunc, len(config["Batch"]))
Expand Down

0 comments on commit 51a1497

Please sign in to comment.