Skip to content
This repository has been archived by the owner on Jun 26, 2023. It is now read-only.

Commit

Permalink
Remove regex
Browse files Browse the repository at this point in the history
URL parsing is better suited using `net/url`
  • Loading branch information
jbygdell committed Apr 24, 2023
1 parent 23e3a06 commit dd0a371
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 33 deletions.
56 changes: 26 additions & 30 deletions userauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/golang-jwt/jwt/v4"
Expand Down Expand Up @@ -73,15 +73,14 @@ func (u *ValidateFromToken) Authenticate(r *http.Request) (claims jwt.MapClaims,

log.Debugf("Looking for key for %s", strIss)

re := regexp.MustCompile(`//([^/]*)`)
keyMatch := re.FindStringSubmatch(strIss)
if len(keyMatch) < 2 || keyMatch[1] == "" {
return nil, fmt.Errorf("failed to get issuer from token iss (%v)", strIss)
iss, err := url.ParseRequestURI(strIss)
if err != nil || iss.Hostname() == "" {
return nil, fmt.Errorf("failed to get issuer from token (%v)", strIss)
}

switch token.Header["alg"] {
case "ES256":
key, err := jwt.ParseECPublicKeyFromPEM(u.pubkeys[keyMatch[1]])
key, err := jwt.ParseECPublicKeyFromPEM(u.pubkeys[iss.Hostname()])
if err != nil {
return nil, fmt.Errorf("failed to parse EC public key (%v)", err)
}
Expand All @@ -90,7 +89,7 @@ func (u *ValidateFromToken) Authenticate(r *http.Request) (claims jwt.MapClaims,
return nil, fmt.Errorf("signed token (ES256) not valid: %v, (token was %s)", err, tokenStr)
}
case "RS256":
key, err := jwt.ParseRSAPublicKeyFromPEM(u.pubkeys[keyMatch[1]])
key, err := jwt.ParseRSAPublicKeyFromPEM(u.pubkeys[iss.Hostname()])
if err != nil {
return nil, fmt.Errorf("failed to parse RSA256 public key (%v)", err)
}
Expand All @@ -103,12 +102,17 @@ func (u *ValidateFromToken) Authenticate(r *http.Request) (claims jwt.MapClaims,
}

// Check whether token username and filepath match
re = regexp.MustCompile("/([^/]+)/")
username := re.FindStringSubmatch(r.URL.Path)[1]
str, err := url.ParseRequestURI(r.URL.Path)
if err != nil || str.Path == "" {
return nil, fmt.Errorf("failed to get path from query (%v)", r.URL.Path)
}

path := strings.Split(str.Path, "/")
username := path[1]

// Case for Elixir and CEGA usernames: Replace @ with _ character
if strings.Contains(fmt.Sprintf("%v", claims["sub"]), "@") {
claimString := fmt.Sprintf("%v", claims["sub"])
if strings.ReplaceAll(claimString, "@", "_") != username {
if strings.ReplaceAll(fmt.Sprintf("%v", claims["sub"]), "@", "_") != username {
return nil, fmt.Errorf("token supplied username %s but URL had %s", claims["sub"], username)
}
} else if claims["sub"] != username {
Expand All @@ -120,7 +124,6 @@ func (u *ValidateFromToken) Authenticate(r *http.Request) (claims jwt.MapClaims,

// Function for reading the ega key in []byte
func (u *ValidateFromToken) getjwtkey(jwtpubkeypath string) error {
re := regexp.MustCompile(`(.*)\.+`)
err := filepath.Walk(jwtpubkeypath,
func(path string, info os.FileInfo, err error) error {
if err != nil {
Expand All @@ -132,13 +135,8 @@ func (u *ValidateFromToken) getjwtkey(jwtpubkeypath string) error {
if err != nil {
return fmt.Errorf("token file error: %v", err)
}
nameMatch := re.FindStringSubmatch(info.Name())

if nameMatch == nil || len(nameMatch) < 2 {
return fmt.Errorf("unexpected lack of substring match in filename %s", info.Name())
}

u.pubkeys[nameMatch[1]] = keyData
nameMatch := strings.TrimSuffix(info.Name(), filepath.Ext(info.Name()))
u.pubkeys[nameMatch] = keyData
}

return nil
Expand All @@ -152,18 +150,16 @@ func (u *ValidateFromToken) getjwtkey(jwtpubkeypath string) error {

// Function for fetching the elixir key from the JWK and transform it to []byte
func (u *ValidateFromToken) getjwtpubkey(jwtpubkeyurl string) error {
re := regexp.MustCompile("/([^/]+)/")
keyMatch := re.FindStringSubmatch(jwtpubkeyurl)

if keyMatch == nil {
return fmt.Errorf("not valid link for key %s", jwtpubkeyurl)
}
jwkURL, err := url.ParseRequestURI(jwtpubkeyurl)
if err != nil || jwkURL.Scheme == "" || jwkURL.Host == "" {
if err != nil {
return err
}

if len(keyMatch) < 2 {
return fmt.Errorf("unexpected lack of submatches in %s", jwtpubkeyurl)
return fmt.Errorf("jwtpubkeyurl is not a proper URL (%s)", jwkURL)
}
log.Debug("jwkURL: ", jwkURL.Scheme)

key := keyMatch[1]
set, err := jwk.Fetch(jwtpubkeyurl)
if err != nil {
return fmt.Errorf("jwk.Fetch failed (%v) for %s", err, jwtpubkeyurl)
Expand Down Expand Up @@ -198,8 +194,8 @@ func (u *ValidateFromToken) getjwtpubkey(jwtpubkeyurl string) error {
Bytes: pkeyBytes,
},
)
u.pubkeys[key] = keyData
log.Debugf("Registered public key for %s", key)
u.pubkeys[jwkURL.Hostname()] = keyData
log.Debugf("Registered public key for %s", jwkURL.Hostname())

return nil
}
59 changes: 56 additions & 3 deletions userauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,26 @@ func TestUserTokenAuthenticator_NoFile(t *testing.T) {
a.pubkeys = make(map[string][]byte)
err := a.getjwtkey("")
assert.Error(t, err)
}

err = a.getjwtpubkey("")
assert.Error(t, err)
func TestUserTokenAuthenticator_GetFile(t *testing.T) {
// Create temp demo rsa key pair
demoKeysPath := "temp-rsa-keys"
prKeyPath, pubKeyPath, err := helper.MakeFolder(demoKeysPath)
assert.NoError(t, err)

err = helper.CreateRSAkeys(prKeyPath, pubKeyPath)
assert.NoError(t, err)

var pubkeys map[string][]byte
jwtpubkeypath := demoKeysPath + "/public-key/"

a := NewValidateFromToken(pubkeys)
a.pubkeys = make(map[string][]byte)
err = a.getjwtkey(jwtpubkeypath)
assert.NoError(t, err)

defer os.RemoveAll(demoKeysPath)
}

func TestUserTokenAuthenticator_WrongURL(t *testing.T) {
Expand All @@ -37,7 +54,27 @@ func TestUserTokenAuthenticator_WrongURL(t *testing.T) {
jwtpubkeyurl := "/dummy/"

err := a.getjwtpubkey(jwtpubkeyurl)
assert.Error(t, err)
assert.Equal(t, "jwtpubkeyurl is not a proper URL (/dummy/)", err.Error())
}

func TestUserTokenAuthenticator_BadURL(t *testing.T) {
var pubkeys map[string][]byte
a := NewValidateFromToken(pubkeys)
a.pubkeys = make(map[string][]byte)
jwtpubkeyurl := "dummy.com/jwk"

err := a.getjwtpubkey(jwtpubkeyurl)
assert.Equal(t, "parse \"dummy.com/jwk\": invalid URI for request", err.Error())
}

func TestUserTokenAuthenticator_GoodURL(t *testing.T) {
var pubkeys map[string][]byte
a := NewValidateFromToken(pubkeys)
a.pubkeys = make(map[string][]byte)
jwtpubkeyurl := "https://example.com/jwk/"

err := a.getjwtpubkey(jwtpubkeyurl)
assert.ErrorContains(t, err, "failed to fetch remote JWK")
}

func TestUserTokenAuthenticator_ValidateSignature_RSA(t *testing.T) {
Expand Down Expand Up @@ -251,6 +288,22 @@ func TestUserTokenAuthenticator_ValidateSignature_EC(t *testing.T) {
_, err = a.Authenticate(r)
assert.Contains(t, err.Error(), "failed to get issuer from token")

// Test LS-AAI user authentication
token, err := helper.CreateECToken(prKeyParsed, "ES256", "JWT", helper.WrongUserClaims)
assert.NoError(t, err)

r, _ = http.NewRequest("", "/c5773f41d17d27bd53b1e6794aedc32d7906e779_elixir-europe.org/foo", nil)
r.Host = "localhost"
r.Header.Set("X-Amz-Security-Token", token)
_, err = a.Authenticate(r)
assert.NoError(t, err)

r, _ = http.NewRequest("", "/dataset", nil)
r.Host = "localhost"
r.Header.Set("X-Amz-Security-Token", token)
_, err = a.Authenticate(r)
assert.Equal(t, "token supplied username [email protected] but URL had dataset", err.Error())

defer os.RemoveAll(demoKeysPath)
}

Expand Down

0 comments on commit dd0a371

Please sign in to comment.