Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[auth] Serve both raw (for download) and resigned (for upload) tokens #1362

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
3 changes: 3 additions & 0 deletions .github/integration/sda-s3-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,9 @@ services:
- AUTH_RESIGNJWT=true
- AUTH_CEGA_ID=test
- AUTH_CEGA_SECRET=test
- OIDC_REDIRECTURL=http://localhost:8888/oidc/login
- OIDC_ID=XC56EL11zz
- OIDC_SECRET=wHPVQaYXmdDHa
- DB_PASSWORD=auth
- DB_USER=auth
extra_hosts:
Expand Down
2 changes: 1 addition & 1 deletion .github/integration/sda/aai-mock/clients/aai-auth.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
client-name: "auth"
client-name: "aai-auth"
client-id: "XC56EL11xx"
client-secret: "wHPVQaYXmdDHg"
redirect-uris: ["http://localhost:8801/oidc/login"]
Expand Down
8 changes: 8 additions & 0 deletions .github/integration/sda/aai-mock/clients/cega-auth.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
client-name: "cega-auth"
client-id: "XC56EL11zz"
client-secret: "wHPVQaYXmdDHa"
redirect-uris: ["http://localhost:8888/oidc/login"]
token-endpoint-auth-method: "client_secret_basic"
scope: ["openid", "profile", "email", "ga4gh_passport_v1", "eduperson_entitlement"]
grant-types: ["authorization_code"]
post-logout-redirect-uris: ["http://localhost:8888/oidc/login"]
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,13 @@ buildNumber.properties
# End of https://www.gitignore.io/api/java,maven,eclipse,intellij+all

db/

# MacOS desktop service store
.DS_Store

# crypt4gh key files
*.pub.pem
*.sec.pem

# sda-admin binary
sda-admin/sda-admin
2 changes: 1 addition & 1 deletion sda-download/dev_utils/config-notls_local.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ db:
oidc:
# oidc configuration API must have values for "userinfo_endpoint" and "jwks_uri"
configuration:
url: "http://localhost:8080/.well-known/openid-configuration"
url: "http://localhost:8800/oidc/.well-known/openid-configuration"
2 changes: 1 addition & 1 deletion sda/cmd/auth/frontend/static/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
}

#loginbox1 {
max-width: 600px;
max-width: 800px;
}

#logintext {
Expand Down
16 changes: 5 additions & 11 deletions sda/cmd/auth/frontend/templates/ega.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,12 @@
Welcome, {{.User}}!
</p>
{{if .Token}}
<p class="text-center">
Your access token is:
</p>
<pre class="border border-secondary rounded py-2 px-3 my-4" id="logintext">{{.Token}}</pre>
{{end}}
{{if .ExpDate}}
<p class="text-center">
Your access token expires (UTC):
</p>
<pre class="text-center" id="logintext">{{.ExpDate}}</pre>
<p class="text-center">
For <strong>uploading</strong> to the Inbox, your access token {{if .ExpDate}}(expires at {{.ExpDate}} UTC){{end}} is:
</p>
<pre class="border border-secondary rounded py-2 px-3 my-4" id="logintext">{{.Token}}</pre>
{{end}}
<a href="/ega/s3conf" class="btn btn-primary btn-block">Download inbox s3cmd credentials</a>
<a href="/ega/s3conf" class="btn btn-primary btn-block mb-5">Download credentials to upload to the Inbox</a>
<a href="/" class="btn btn-primary btn-block">Continue</a>
</div>
</div>
Expand Down
31 changes: 13 additions & 18 deletions sda/cmd/auth/frontend/templates/oidc.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,29 +28,24 @@
<div class="jumbotron" role="region">
<div class="container" id="loginbox1">
<p class="lead text-center">
Welcome, {{.User}}!
Welcome, {{.Fullname}}!
</p>
{{if .Passport}}
<p class="text-center">
Your visas are the following:
</p>
{{ range .Passport }}
<pre class="border border-secondary rounded py-2 px-3 my-4" id="logintext">{{.}}</pre>
{{ end }}
{{ end }}
{{if .Token}}
<p class="text-center">
Your access token is:
</p>
<pre class="border border-secondary rounded py-2 px-3 my-4" id="logintext">{{.Token}}</pre>
{{if (not .cegaID)}}
{{if .ResignedToken}}
<p class="text-center">
For <strong>uploading</strong> to the Inbox, your access token {{if .ExpDateResigned}}(expires at {{.ExpDateResigned}} UTC){{end}} is:
</p>
<pre class="border border-secondary rounded py-2 px-3 my-4" id="logintext">{{.ResignedToken}}</pre>
{{end}}
<a href="/oidc/s3conf-inbox" class="btn btn-primary btn-block mb-5">Download credentials to upload to the Inbox</a>
{{end}}
{{if .ExpDate}}
{{if .RawToken}}
<p class="text-center">
Your access token expires (UTC):
For <strong>downloading</strong> from the Archive, your access token {{if .ExpDateRaw}}(expires at {{.ExpDateRaw}} UTC){{end}} is:
</p>
<pre class="text-center" id="logintext">{{.ExpDate}}</pre>
<pre class="border border-secondary rounded py-2 px-3 my-4" id="logintext">{{.RawToken}}</pre>
{{end}}
<a href="/oidc/s3conf" class="btn btn-primary btn-block">Download inbox s3cmd credentials</a>
<a href="/oidc/s3conf-download" class="btn btn-primary btn-block mb-5">Download credentials to access the Archive</a>
<a href="/" class="btn btn-primary btn-block">Continue</a>
</div>
</div>
Expand Down
54 changes: 36 additions & 18 deletions sda/cmd/auth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ type LoginOption struct {
}

type OIDCData struct {
S3Conf map[string]string
OIDCID OIDCIdentity
S3ConfInbox map[string]string
S3ConfDownload map[string]string
OIDCID OIDCIdentity
}

type AuthHandler struct {
Expand All @@ -40,7 +41,10 @@ type AuthHandler struct {
pubKey string
}

func (auth AuthHandler) getInboxConfig(ctx iris.Context, authType string) {
// getS3Config retrieves S3 config from session flash and serves it as a
// downloadable s3cmd file with the specified fileName. Redirects to home if
// config is missing.
func (auth AuthHandler) getS3Config(ctx iris.Context, authType string, fileName string) {

log.Infoln(ctx.Request().URL.Path)

Expand All @@ -52,7 +56,8 @@ func (auth AuthHandler) getInboxConfig(ctx iris.Context, authType string) {
return
}
s3cfmap := s3conf.(map[string]string)
ctx.ResponseWriter().Header().Set("Content-Disposition", "attachment; filename=s3cmd.conf")
ctx.ResponseWriter().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileName))

s3c := "[default]\n"

for k, v := range s3cfmap {
Expand Down Expand Up @@ -215,7 +220,7 @@ func (auth AuthHandler) getEGALogin(ctx iris.Context) {

// getEGAConf returns an s3config file for an oidc login
func (auth AuthHandler) getEGAConf(ctx iris.Context) {
auth.getInboxConfig(ctx, "ega")
auth.getS3Config(ctx, "ega", "s3cmd-inbox.conf")
}

// getOIDC redirects to the oidc page defined in auth.Config
Expand Down Expand Up @@ -264,30 +269,32 @@ func (auth AuthHandler) elixirLogin(ctx iris.Context) *OIDCData {

return nil
}
err = auth.Config.DB.UpdateUserInfo(idStruct.User, idStruct.Profile, idStruct.Email, idStruct.EdupersonEntitlement)
err = auth.Config.DB.UpdateUserInfo(idStruct.User, idStruct.Fullname, idStruct.Email, idStruct.EdupersonEntitlement)
if err != nil {
log.Warn("Could not log user info.")
}

if auth.Config.ResignJwt {
log.Debugf("Resigning token for user %s", idStruct.User)
claims := map[string]interface{}{
jwt.ExpirationKey: time.Now().UTC().Add(time.Duration(auth.Config.JwtTTL) * time.Hour),
jwt.IssuedAtKey: time.Now().UTC(),
jwt.IssuerKey: auth.Config.JwtIssuer,
jwt.SubjectKey: idStruct.Profile,
jwt.SubjectKey: idStruct.User,
}
token, expDate, err := generateJwtToken(claims, auth.Config.JwtPrivateKey, auth.Config.JwtSignatureAlg)
if err != nil {
log.Errorf("error when generating token: %v", err)
}
idStruct.Token = token
idStruct.ExpDate = expDate
idStruct.ResignedToken = token
idStruct.ExpDateResigned = expDate
}

log.WithFields(log.Fields{"authType": "oidc", "user": idStruct.User}).Infof("User was authenticated")
s3conf := getS3ConfigMap(idStruct.Token, auth.Config.S3Inbox, idStruct.User)
s3confInbox := getS3ConfigMap(idStruct.ResignedToken, auth.Config.S3Inbox, idStruct.User)
s3confDownload := getS3ConfigMap(idStruct.RawToken, auth.Config.S3Inbox, idStruct.User)

return &OIDCData{S3Conf: s3conf, OIDCID: idStruct}
return &OIDCData{S3ConfInbox: s3confInbox, S3ConfDownload: s3confDownload, OIDCID: idStruct}
}

// getOIDCLogin renders the `oidc.html` template to the given iris context
Expand All @@ -299,13 +306,18 @@ func (auth AuthHandler) getOIDCLogin(ctx iris.Context) {
}

s := sessions.Get(ctx)
s.SetFlash("oidc", oidcData.S3Conf)
s.SetFlash("oidcInbox", oidcData.S3ConfInbox)
s.SetFlash("oidcDownload", oidcData.S3ConfDownload)
ctx.ViewData("cegaID", auth.Config.Cega.ID)
ctx.ViewData("infoUrl", auth.Config.InfoURL)
ctx.ViewData("infoText", auth.Config.InfoText)
ctx.ViewData("User", oidcData.OIDCID.User)
ctx.ViewData("Fullname", oidcData.OIDCID.Fullname)
ctx.ViewData("Passport", oidcData.OIDCID.Passport)
ctx.ViewData("Token", oidcData.OIDCID.Token)
ctx.ViewData("ExpDate", oidcData.OIDCID.ExpDate)
ctx.ViewData("RawToken", oidcData.OIDCID.RawToken)
ctx.ViewData("ResignedToken", oidcData.OIDCID.ResignedToken)
ctx.ViewData("ExpDateRaw", oidcData.OIDCID.ExpDateRaw)
ctx.ViewData("ExpDateResigned", oidcData.OIDCID.ExpDateResigned)

err := ctx.View("oidc.html")
if err != nil {
Expand All @@ -331,9 +343,14 @@ func (auth AuthHandler) getOIDCCORSLogin(ctx iris.Context) {
}
}

// getOIDCConf returns an s3config file for an oidc login
func (auth AuthHandler) getOIDCConf(ctx iris.Context) {
auth.getInboxConfig(ctx, "oidc")
// getOIDCConfInbox returns an s3config file for uploading to the Inbox
func (auth AuthHandler) getOIDCConfInbox(ctx iris.Context) {
auth.getS3Config(ctx, "oidcInbox", "s3cmd-inbox.conf")
}

// getOIDCConfDownload returns an s3config file for downloading from the Archive
func (auth AuthHandler) getOIDCConfDownload(ctx iris.Context) {
auth.getS3Config(ctx, "oidcDownload", "s3cmd-download.conf")
}

// globalHeaders presets common response headers
Expand Down Expand Up @@ -427,7 +444,8 @@ func main() {

// OIDC endpoints
app.Get("/oidc", authHandler.getOIDC)
app.Get("/oidc/s3conf", authHandler.getOIDCConf)
app.Get("/oidc/s3conf-inbox", authHandler.getOIDCConfInbox)
app.Get("/oidc/s3conf-download", authHandler.getOIDCConfDownload)
app.Get("/oidc/login", authHandler.getOIDCLogin)
app.Get("/oidc/cors_login", authHandler.getOIDCCORSLogin)

Expand Down
18 changes: 11 additions & 7 deletions sda/cmd/auth/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ import (
type OIDCIdentity struct {
User string
Passport []string
Token string
Profile string
RawToken string
ResignedToken string
Fullname string
Email string
EdupersonEntitlement []string
ExpDate string
ExpDateRaw string
ExpDateResigned string
}

// Configure an OpenID Connect aware OAuth2 client.
Expand Down Expand Up @@ -92,7 +94,7 @@ func authenticateWithOidc(oauth2Config oauth2.Config, provider *oidc.Provider, c
// Extract custom passports, name and email claims
var claims struct {
PassportClaim []string `json:"ga4gh_passport_v1"`
ProfileClaim string `json:"name"`
FullnameClaim string `json:"name"`
EmailClaim string `json:"email"`
EdupersonEntitlement []string `json:"eduperson_entitlement"`
}
Expand All @@ -104,12 +106,14 @@ func authenticateWithOidc(oauth2Config oauth2.Config, provider *oidc.Provider, c

idStruct = OIDCIdentity{
User: userInfo.Subject,
Token: rawAccessToken,
RawToken: rawAccessToken,
ResignedToken: rawAccessToken,
Passport: claims.PassportClaim,
Profile: claims.ProfileClaim,
Fullname: claims.FullnameClaim,
Email: claims.EmailClaim,
EdupersonEntitlement: claims.EdupersonEntitlement,
ExpDate: rawExpDate,
ExpDateRaw: rawExpDate,
ExpDateResigned: rawExpDate,
}

return idStruct, err
Expand Down
10 changes: 5 additions & 5 deletions sda/cmd/auth/oidc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,7 @@ func (suite *OIDCTests) TestGetOidcClient() {
}

func (suite *OIDCTests) TestAuthenticateWithOidc() {

// Create a code to authenticate

session, err := suite.mockServer.SessionStore.NewSession(
"openid email profile", "nonce", mockoidc.DefaultUser(), "", "")
if err != nil {
Expand All @@ -84,7 +82,9 @@ func (suite *OIDCTests) TestAuthenticateWithOidc() {

elixirIdentity, err := authenticateWithOidc(oauth2Config, provider, code, jwkURL)
assert.Nil(suite.T(), err, "Failed to authenticate with OIDC")
assert.NotEqual(suite.T(), "", elixirIdentity.Token, "Empty token returned from OIDC authentication")
// Ensure both RawToken and ResignedToken are not empty
assert.NotEqual(suite.T(), "", elixirIdentity.RawToken, "Empty RawToken returned from OIDC authentication")
assert.NotEqual(suite.T(), "", elixirIdentity.ResignedToken, "Empty ResignedToken returned from OIDC authentication")
}

func (suite *OIDCTests) TestValidateJwt() {
Expand All @@ -93,7 +93,7 @@ func (suite *OIDCTests) TestValidateJwt() {
oauth2Config, provider := getOidcClient(suite.OIDCConfig)
jwkURL := suite.mockServer.JWKSEndpoint()
elixirIdentity, _ := authenticateWithOidc(oauth2Config, provider, session.SessionID, jwkURL)
elixirJWT := elixirIdentity.Token
elixirJWT := elixirIdentity.RawToken

claims := map[string]interface{}{
jwt.ExpirationKey: time.Now().UTC().Add(2 * time.Hour),
Expand Down Expand Up @@ -146,7 +146,7 @@ func (suite *OIDCTests) TestValidateJwt() {
// sanity check
_, expDate, err := validateToken(elixirJWT, suite.mockServer.JWKSEndpoint())
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), expDate, elixirIdentity.ExpDate, "Returned wrong exp date but without returning errors")
assert.Equal(suite.T(), expDate, elixirIdentity.ExpDateRaw, "Returned wrong exp date but without returning errors")

// Not a jwk url
_, _, err = validateToken(elixirJWT, "http://some/jwk/endpoint")
Expand Down
Loading