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

Feature/v4backend #115

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3c46560
v4 compat changes
consolethinks Oct 1, 2024
f44116d
fix tests for v4 changes
consolethinks Oct 2, 2024
62389a4
add response body check to ChechMetadataValidity
consolethinks Oct 2, 2024
1114b60
change local_api_server address to match changes in scicatlive
consolethinks Oct 4, 2024
f7ca9db
v4 changes for ingestion command
consolethinks Oct 4, 2024
d864d4c
fix tests for v4 changes
consolethinks Oct 4, 2024
9d8c99b
v4 fixes for datasetArchiver
consolethinks Oct 4, 2024
2dfb02c
v4 updates for datasetGetProposal command
consolethinks Oct 4, 2024
7dfa34c
fix createJob test for v4 changes
consolethinks Oct 4, 2024
335a371
BE v4 changes
Oct 7, 2024
3831c94
general code cleanup: better error handling, preallocating some slices
consolethinks Oct 8, 2024
4757845
fix auth test
consolethinks Oct 8, 2024
fc006ee
move transferType.go to cliutils
consolethinks Oct 10, 2024
c3b8c15
createJob update to v4 BE
consolethinks Oct 10, 2024
22c6991
fix bearer token mapping to header
consolethinks Oct 10, 2024
919ea52
fix createJob_test for v4 changes
consolethinks Oct 10, 2024
f2c0564
v4 fixes for getProposal
consolethinks Oct 10, 2024
a5c70ba
cleanup and update datasetRetriever for v4
consolethinks Oct 11, 2024
5bbab98
remove unused parameter
consolethinks Oct 11, 2024
3c832ee
small fixes for datasetRetriever
consolethinks Oct 11, 2024
bbd0f58
fix ownerGroup setting when requesting archival jobs
consolethinks Oct 18, 2024
dcb57b2
write test for transfer type
consolethinks Oct 18, 2024
48b81b5
use /users/{id}/userIdentity endpoint
consolethinks Oct 18, 2024
ca8c254
changes to conform with CI checks
consolethinks Oct 18, 2024
adc4cb1
check auth token insertion
consolethinks Oct 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions cmd/commands/authenticate.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
// An interface with the methods so that we can mock them in tests
type Authenticator interface {
AuthenticateUser(httpClient *http.Client, APIServer string, username string, password string) (map[string]string, []string)
GetUserInfoFromToken(httpClient *http.Client, APIServer string, token string) (map[string]string, []string)
GetUserInfoFromToken(httpClient *http.Client, APIServer string, token string) (map[string]string, []string, error)
}

type RealAuthenticator struct{}
Expand All @@ -23,7 +23,7 @@ func (r RealAuthenticator) AuthenticateUser(httpClient *http.Client, APIServer s
return datasetUtils.AuthenticateUser(httpClient, APIServer, username, password)
}

func (r RealAuthenticator) GetUserInfoFromToken(httpClient *http.Client, APIServer string, token string) (map[string]string, []string) {
func (r RealAuthenticator) GetUserInfoFromToken(httpClient *http.Client, APIServer string, token string) (map[string]string, []string, error) {
return datasetUtils.GetUserInfoFromToken(httpClient, APIServer, token)
}

Expand All @@ -38,7 +38,10 @@ func authenticate(authenticator Authenticator, httpClient *http.Client, apiServe
fatalExit = overrideFatalExit[0]
}
if token != "" {
user, accessGroups := authenticator.GetUserInfoFromToken(httpClient, apiServer, token)
user, accessGroups, err := authenticator.GetUserInfoFromToken(httpClient, apiServer, token)
if err != nil {
log.Fatal(err)
}
uSplit := strings.Split(userpass, ":")
if len(uSplit) > 1 {
user["password"] = uSplit[1]
Expand Down
47 changes: 16 additions & 31 deletions datasetIngestor/checkMetadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,32 +31,36 @@ func ReadAndCheckMetadata(client *http.Client, APIServer string, metadatafile st
if err != nil {
return nil, "", false, err
}
sourceFolder, beamlineAccount, err = CheckMetadata(client, APIServer, metaDataMap, user, accessGroups)
return metaDataMap, sourceFolder, beamlineAccount, err
}

func CheckMetadata(client *http.Client, APIServer string, metaDataMap map[string]interface{}, user map[string]string, accessGroups []string) (sourceFolder string, beamlineAccount bool, err error) {
if keys := CollectIllegalKeys(metaDataMap); len(keys) > 0 {
return nil, "", false, errors.New(ErrIllegalKeys + ": \"" + strings.Join(keys, "\", \"") + "\"")
return "", false, errors.New(ErrIllegalKeys + ": \"" + strings.Join(keys, "\", \"") + "\"")
}

beamlineAccount, err = CheckUserAndOwnerGroup(user, accessGroups, metaDataMap)
if err != nil {
return nil, "", false, err
return "", false, err
}

err = GatherMissingMetadata(user, metaDataMap, client, APIServer, accessGroups)
if err != nil {
return nil, "", false, err
return "", false, err
}

err = CheckMetadataValidity(client, APIServer, metaDataMap)
if err != nil {
return nil, "", false, err
return "", false, err
}

sourceFolder, err = GetSourceFolder(metaDataMap)
if err != nil {
return nil, "", false, err
return "", false, err
}

return metaDataMap, sourceFolder, beamlineAccount, nil
return sourceFolder, beamlineAccount, nil
}

// ReadMetadataFromFile reads the metadata from the file and unmarshals it into a map.
Expand Down Expand Up @@ -175,7 +179,7 @@ func CheckUserAndOwnerGroup(user map[string]string, accessGroups []string, metaD
}

// getHost is a function that attempts to retrieve and return the fully qualified domain name (FQDN) of the current host.
// If it encounters any error during the process, it gracefully falls back to returning the simple hostname or "unknown".
// If it encounters any error during the process, it falls back to returning "unknown", as a simple hostname won't work with v4 backend
minottic marked this conversation as resolved.
Show resolved Hide resolved
func getHost() string {
// Try to get the hostname of the current machine.
hostname, err := os.Hostname()
Expand All @@ -185,24 +189,24 @@ func getHost() string {

addrs, err := net.LookupIP(hostname)
if err != nil {
return hostname
return unknown
}

for _, addr := range addrs {
if ipv4 := addr.To4(); ipv4 != nil {
ip, err := ipv4.MarshalText()
if err != nil {
return hostname
return unknown
}
hosts, err := net.LookupAddr(string(ip))
if err != nil || len(hosts) == 0 {
return hostname
return unknown
}
fqdn := hosts[0]
return strings.TrimSuffix(fqdn, ".") // return fqdn without trailing dot
}
}
return hostname
return unknown
}

// GatherMissingMetadata augments missing metadata fields.
Expand Down Expand Up @@ -288,25 +292,7 @@ func addPrincipalInvestigatorFromProposal(user map[string]string, metaDataMap ma

// CheckMetadataValidity checks the validity of the metadata by calling the appropriate API.
func CheckMetadataValidity(client *http.Client, APIServer string, metaDataMap map[string]interface{}) error {
dstype, ok := metaDataMap["type"].(string)
if !ok {
return fmt.Errorf("metadata type isn't a string")
}

myurl := ""
switch dstype {
case raw:
myurl = APIServer + "/RawDatasets/isValid"
case "derived":
myurl = APIServer + "/DerivedDatasets/isValid"
case "base":
myurl = APIServer + "/Datasets/isValid"
default:
return fmt.Errorf("unknown dataset type encountered: %s", dstype)
}

// add dummy data for fields which can only be filled after file scan to pass the validity test

if _, exists := metaDataMap["ownerGroup"]; !exists {
metaDataMap["ownerGroup"] = DUMMY_OWNER
}
Expand All @@ -320,7 +306,6 @@ func CheckMetadataValidity(client *http.Client, APIServer string, metaDataMap ma
}

// add accessGroups entry for beamline if creationLocation is defined

if value, exists := metaDataMap["creationLocation"]; exists {
var parts = strings.Split(value.(string), "/")
var groups []string
Expand All @@ -347,7 +332,7 @@ func CheckMetadataValidity(client *http.Client, APIServer string, metaDataMap ma
return err
}

req, err := http.NewRequest("POST", myurl, bytes.NewBuffer(bmm))
req, err := http.NewRequest("POST", "datasets/isValid", bytes.NewBuffer(bmm))
if err != nil {
return err
}
Expand Down
47 changes: 12 additions & 35 deletions datasetIngestor/ingestDataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,53 +83,31 @@ func createDataset(client *http.Client, APIServer string, metaDataMap map[string
cmm, _ := json.Marshal(metaDataMap)
datasetId := ""

if val, ok := metaDataMap["type"]; ok {
dstype := val.(string)
endpoint, err := getEndpoint(dstype)
if err != nil {
return "", err
}
myurl := APIServer + endpoint + "/?access_token=" + user["accessToken"]
resp, err := sendRequest(client, "POST", myurl, cmm)
resp, err := sendRequest(client, "POST", APIServer+"/datasets", user["accessToken"], cmm)
if err != nil {
return "", err
}
defer resp.Body.Close()

if resp.StatusCode == 200 {
datasetId, err = decodePid(resp)
if err != nil {
return "", err
}
defer resp.Body.Close()

if resp.StatusCode == 200 {
datasetId, err = decodePid(resp)
if err != nil {
return "", err
}
} else {
return "", fmt.Errorf("SendIngestCommand: Failed to create new dataset: status code %v", resp.StatusCode)
}
} else {
return "", fmt.Errorf("no dataset type defined for dataset %v", metaDataMap)
return "", fmt.Errorf("SendIngestCommand: Failed to create new dataset: status code %v", resp.StatusCode)
}

return datasetId, nil
}

func getEndpoint(dstype string) (string, error) {
switch dstype {
case "raw":
return "/RawDatasets", nil
case "derived":
return "/DerivedDatasets", nil
case "base":
return "/Datasets", nil
default:
return "", fmt.Errorf("unknown dataset type encountered: %s", dstype)
}
}

func sendRequest(client *http.Client, method, url string, body []byte) (*http.Response, error) {
func sendRequest(client *http.Client, method, url string, accessToken string, body []byte) (*http.Response, error) {
req, err := http.NewRequest(method, url, bytes.NewBuffer(body))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))

resp, err := client.Do(req)
if err != nil {
Expand Down Expand Up @@ -192,8 +170,7 @@ func createOrigDatablocks(client *http.Client, APIServer string, fullFileArray [
origBlock := createOrigBlock(start, end, fullFileArray, datasetId)

payloadString, _ := json.Marshal(origBlock)
myurl := APIServer + "/OrigDatablocks" + "?access_token=" + user["accessToken"]
resp, err := sendRequest(client, "POST", myurl, payloadString)
resp, err := sendRequest(client, "POST", APIServer+"/origdatablocks", user["accessToken"], payloadString)
if err != nil {
return err
}
Expand Down
112 changes: 76 additions & 36 deletions datasetUtils/getUserInfoFromToken.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,93 @@ package datasetUtils

import (
"encoding/json"
"io/ioutil"
"log"
"fmt"
"io"
"net/http"
"net/url"
)

type UserInfo struct {
CurrentUser string `json:"currentUser"`
CurrentUserEmail string `json:"currentUserEmail"`
CurrentGroups []string `json:"currentGroups"`
type ReturnedUser struct {
Id string `json:"id"`
}

func GetUserInfoFromToken(client *http.Client, APIServer string, token string) (map[string]string, []string) {
u := make(map[string]string)
accessGroups := make([]string, 0)
type UserIdentity struct {
Profile Profile `json:"profile"`
}

type Profile struct {
Username string `json:"username"`
DisplayName string `json:"displayName"`
AccessGroups []string `json:"accessGroups"`
Emails []Email `json:"emails"`
}

url := APIServer + "/Users/userInfos?access_token=" + token
req, err := http.NewRequest("GET", url, nil)
req.Header.Set("Content-Type", "application/json")
type Email struct {
Value string `json:"value"`
}

func GetUserInfoFromToken(client *http.Client, APIServer string, token string) (map[string]string, []string, error) {
var newUserInfo ReturnedUser
var accessGroups []string
u := map[string]string{}
bearerToken := fmt.Sprintf("Bearer %s", token)

resp, err := client.Do(req)
// get user info (does not contain access groups) [1st request]
req1, err := http.NewRequest("GET", APIServer+"/users/my/self", nil)
if err != nil {
log.Fatal(err)
return map[string]string{}, []string{}, err
}
req1.Header.Set("Authorization", bearerToken)
resp1, err := client.Do(req1)
if err != nil {
return map[string]string{}, []string{}, err
}
defer resp1.Body.Close()
body1, err := io.ReadAll(resp1.Body)
if err := json.Unmarshal(body1, &newUserInfo); err != nil {
return map[string]string{}, []string{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)

if resp.StatusCode != 200 {
log.Fatalf("Could not login with token:%v, status %v", token, resp.StatusCode)
// get extra details about user [2nd request]
var respObj UserIdentity
if err != nil {
return map[string]string{}, []string{}, err
}
filterString := url.QueryEscape(fmt.Sprintf("{\"where\":{\"userId\":\"%s\"}}", newUserInfo.Id))
req2, err := http.NewRequest("GET", APIServer+"/useridentities/findOne?filter="+filterString, nil)
minottic marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return map[string]string{}, []string{}, err
}
req2.Header.Set("Authorization", bearerToken)

var respObj UserInfo
err = json.Unmarshal(body, &respObj)
resp2, err := client.Do(req2)
if err != nil {
log.Fatal(err)
}

if respObj.CurrentUser != "" {
//log.Printf("Found the following user for this token %v", respObj[0])
u["username"] = respObj.CurrentUser
u["mail"] = respObj.CurrentUserEmail
u["displayName"] = respObj.CurrentUser
u["accessToken"] = token
log.Printf("User authenticated: %s %s\n", u["displayName"], u["mail"])
accessGroups = respObj.CurrentGroups
log.Printf("User is member in following groups: %v\n", accessGroups)
} else {
log.Fatalf("Could not map a user to the token %v", token)
}
return u, accessGroups
return map[string]string{}, []string{}, err
}
defer resp2.Body.Close()
if resp2.StatusCode != 200 {
return map[string]string{}, []string{}, fmt.Errorf("could not login with token:%v, status %v", token, resp1.StatusCode)
}
body2, err := io.ReadAll(resp2.Body)
if err != nil {
return map[string]string{}, []string{}, err
}
err = json.Unmarshal(body2, &respObj)
if err != nil {
return map[string]string{}, []string{}, err
}

// return important user informations
if respObj.Profile.Username == "" {
return map[string]string{}, []string{}, fmt.Errorf("could not map a user to the token '%v'", token)
}
u["username"] = respObj.Profile.Username
if len(respObj.Profile.Emails) > 0 {
u["mail"] = respObj.Profile.Emails[0].Value
}
u["displayName"] = respObj.Profile.DisplayName
u["accessToken"] = token
accessGroups = respObj.Profile.AccessGroups

return u, accessGroups, nil
}