Skip to content

Commit

Permalink
Adapter MintTokens metadata and ResourceTokens format changes
Browse files Browse the repository at this point in the history
MintTokens now has support for a map key/value pairs instead of forcing an OIDC access token constraint. Adapters can choose what to return via this key/value map and it will be reflected in the response from the ResourceTokens endpoint.

PiperOrigin-RevId: 297653705
Change-Id: I63235c002a0f2187f6069e218c2d6921b6138a09
  • Loading branch information
cdvoisin authored and copybara-github committed Feb 27, 2020
1 parent 1391bdb commit feaa0c0
Show file tree
Hide file tree
Showing 10 changed files with 600 additions and 479 deletions.
9 changes: 7 additions & 2 deletions lib/adapter/adapters.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,13 @@ type Action struct {

// MintTokenResult is returned by the MintToken() method.
type MintTokenResult struct {
Account string
Token string
// A set of credential information like "account" and "access_token", or whatever
// may apply for the given target service.
Credentials map[string]string
// A set of metadata labels about the result to provide context to the client application.
Labels map[string]string
// The type of token, if applicable, that was able to be generated, which may vary from
// the TokenFormat requested in the Action depending on service requirements.
TokenFormat string
}

Expand Down
6 changes: 4 additions & 2 deletions lib/adapter/gatekeeper_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,10 @@ func (a *GatekeeperAdapter) MintToken(ctx context.Context, input *Action) (*Mint
return nil, err
}
return &MintTokenResult{
Account: input.Identity.Subject,
Token: token,
Credentials: map[string]string{
"account": input.Identity.Subject,
"access_token": token,
},
TokenFormat: "base64",
}, nil
}
2 changes: 1 addition & 1 deletion lib/adapter/gatekeeper_adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func TestGatekeeperAdapter(t *testing.T) {
if test.fail != (err != nil) {
t.Fatalf("test %q error mismatch: want error %v, got error %v", test.name, test.fail, err)
}
if err == nil && len(result.Token) == 0 {
if err == nil && (len(result.Credentials) == 0 || len(result.Credentials["access_token"]) == 0) {
t.Errorf("test %q token mismatch: want non-empty, got empty", test.name)
}
}
Expand Down
6 changes: 4 additions & 2 deletions lib/adapter/saw_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,10 @@ func (a *SawAdapter) MintToken(ctx context.Context, input *Action) (*MintTokenRe
return nil, fmt.Errorf("SAW minting token: %v", err)
}
return &MintTokenResult{
Account: result.Account,
Token: result.Token,
Credentials: map[string]string{
"account": result.Account,
"access_token": result.Token,
},
TokenFormat: result.Format,
}, nil
}
Expand Down
4 changes: 2 additions & 2 deletions lib/dam/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ func TestConfigHandlers(t *testing.T) {

role := `{"roleCategories":["metadata"],"policyBasis":{"AcceptedTermsAndPolicies":true,"ResearcherStatus":true}}`
roles := `{"discovery":` + role + `}`
beacon := `{"serviceTemplate":"beacon",*,"contentTypes":["application/bam"],"roles":` + roles + `,"ui":{"description":"Search data from Beacon Discovery","label":"Beacon Discovery"},"interfaces":{"http:beacon":{"uri":["https://gatekeeper-cafe-variome.staging.dnastack.com/beacon/query"]}}}`
views := `{"beacon":` + beacon + `,"gcs_read":{"serviceTemplate":"gcs",*,"contentTypes":["application/bam"],"roles":{"viewer":{"roleCategories":["list","metadata","read"],"policyBasis":{"AcceptedTermsAndPolicies":true,"ResearcherStatus":true}}},"ui":{"description":"GCS File Read","label":"File Read"},"interfaces":{"gcp:gs":{"uri":["gs://ga4gh-apis-controlled-access"]},"http:gcp:gs":{"uri":["https://www.googleapis.com/storage/v1/b/ga4gh-apis-controlled-access"]}}}}`
beacon := `{"serviceTemplate":"beacon",*,"contentTypes":["application/bam"],"roles":` + roles + `,"ui":{"description":"Search data from Beacon Discovery","label":"Beacon Discovery"},"interfaces":{"http:beacon":{"uri":["https://gatekeeper-cafe-variome.staging.dnastack.com/beacon/query"],"labels":{*}}}}`
views := `{"beacon":` + beacon + `,"gcs_read":{"serviceTemplate":"gcs",*,"contentTypes":["application/bam"],"roles":{"viewer":{"roleCategories":["list","metadata","read"],"policyBasis":{"AcceptedTermsAndPolicies":true,"ResearcherStatus":true}}},"ui":{"description":"GCS File Read","label":"File Read"},"interfaces":{"gcp:gs":{"uri":["gs://ga4gh-apis-controlled-access"],"labels":{*}},"http:gcp:gs":{"uri":["https://www.googleapis.com/storage/v1/b/ga4gh-apis-controlled-access"],"labels":{*}}}}}`
resource := `{"views":` + views + `,"maxTokenTtl":"1h","ui":{"applyUrl":"http://apply.ga4gh-apis.org","description":"Google demo of GA4GH APIs","imageUrl":"https://info.ga4gh-apis.org/images/image.jpg","infoUrl":"http://info.ga4gh-apis.org","label":"GA4GH APIs","troubleshootUrl":"http://troubleshoot.ga4gh-apis.org"}}`

tests := []test.HandlerTest{
Expand Down
22 changes: 18 additions & 4 deletions lib/dam/dam.go
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,8 @@ func makeViewInterfaces(srcView *pb.View, srcRes *pb.Resource, cfg *pb.DamConfig
if err != nil {
return out
}
cliMap := make(map[string]map[string]bool)
// Map of <client_interface_name>.<interface_uri>.<label_name>.<label_value>.
cliMap := make(map[string]map[string]map[string]string)
for _, entry := range entries {
st, ok := cfg.ServiceTemplates[entry.View.ServiceTemplate]
if !ok {
Expand All @@ -793,15 +794,25 @@ func makeViewInterfaces(srcView *pb.View, srcRes *pb.Resource, cfg *pb.DamConfig
for client, uriFmt := range st.Interfaces {
uriMap, ok := cliMap[client]
if !ok {
uriMap = make(map[string]bool)
uriMap = make(map[string]map[string]string)
cliMap[client] = uriMap
}
for k, v := range vars {
uriFmt = strings.Replace(uriFmt, "${"+k+"}", v, -1)
}
if !hasItemVariable(uriFmt) {
// Accept this string that has no more variables to replace.
uriMap[uriFmt] = true
uriMap[uriFmt] = srcView.Labels
if len(item.Labels) > 0 {
// Merge label lists for this item, with item.Labels overriding any view.Labels.
labels := make(map[string]string)
for k, v := range srcView.Labels {
labels[k] = v
}
for k, v := range item.Labels {
labels[k] = v
}
}
}
}
}
Expand All @@ -810,8 +821,11 @@ func makeViewInterfaces(srcView *pb.View, srcRes *pb.Resource, cfg *pb.DamConfig
vi := &pb.Interface{
Uri: []string{},
}
for uri := range v {
for uri, labels := range v {
vi.Uri = append(vi.Uri, uri)
if len(labels) > 0 {
vi.Labels = labels
}
}
sort.Strings(vi.Uri)
out[k] = vi
Expand Down
59 changes: 32 additions & 27 deletions lib/dam/token_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func responseKeyFile(r *http.Request) bool {
return httputil.QueryParam(r, "response_type") == "key-file-type"
}

func (s *Service) generateResourceToken(ctx context.Context, clientID, resourceName, viewName, role string, ttl time.Duration, useKeyFile bool, id *ga4gh.Identity, cfg *pb.DamConfig, res *pb.Resource, view *pb.View) (*pb.ResourceTokens_ResourceToken, int, error) {
func (s *Service) generateResourceToken(ctx context.Context, clientID, resourceName, viewName, role string, ttl time.Duration, useKeyFile bool, id *ga4gh.Identity, cfg *pb.DamConfig, res *pb.Resource, view *pb.View) (*pb.ResourceResults_ResourceAccess, int, error) {
sRole, err := adapter.ResolveServiceRole(role, view, res, cfg)
if err != nil {
return nil, http.StatusInternalServerError, err
Expand Down Expand Up @@ -149,23 +149,20 @@ func (s *Service) generateResourceToken(ctx context.Context, clientID, resourceN
return nil, http.StatusServiceUnavailable, err
}

if !useKeyFile {
return &pb.ResourceTokens_ResourceToken{
Account: result.Account,
AccessToken: result.Token,
ExpiresIn: uint32(ttl.Seconds()),
Platform: adapt.Platform(),
// TODO: remove these older fields
Name: resourceName,
View: makeView(viewName, view, res, cfg, s.hidePolicyBasis, s.adapters),
Ttl: timeutil.TTLString(ttl),
if httputil.IsJSON(result.TokenFormat) && result.Credentials != nil {
return &pb.ResourceResults_ResourceAccess{
Credentials: map[string]string{"key_file": result.Credentials["json"]},
}, http.StatusOK, nil
}

if httputil.IsJSON(result.TokenFormat) {
return &pb.ResourceTokens_ResourceToken{KeyFile: result.Token}, http.StatusOK, nil
if useKeyFile {
return nil, http.StatusBadRequest, fmt.Errorf("adapter cannot create key file format")
}
return nil, http.StatusBadRequest, fmt.Errorf("adapter cannot create key file format")

return &pb.ResourceResults_ResourceAccess{
Credentials: result.Credentials,
Labels: result.Labels,
ExpiresIn: uint32(ttl.Seconds()),
}, http.StatusOK, nil
}

func (s *Service) oauthConf(brokerName string, broker *pb.TrustedIssuer, clientSecret string, scopes []string) *oauth2.Config {
Expand Down Expand Up @@ -520,9 +517,9 @@ func (s *Service) ResourceTokens(w http.ResponseWriter, r *http.Request) {

ctx := r.Context()
keyFile := false
out := &pb.ResourceTokens{
Resources: make(map[string]*pb.ResourceTokens_Descriptor),
Access: make(map[string]*pb.ResourceTokens_ResourceToken),
out := &pb.ResourceResults{
Resources: make(map[string]*pb.ResourceResults_ResourceDescriptor),
Access: make(map[string]*pb.ResourceResults_ResourceAccess),
EpochSeconds: uint32(time.Now().Unix()),
}
for i, r := range state.Resources {
Expand All @@ -538,25 +535,33 @@ func (s *Service) ResourceTokens(w http.ResponseWriter, r *http.Request) {
return
}

tok, status, err := s.generateResourceToken(ctx, state.ClientId, r.Resource, r.View, r.Role, time.Duration(state.Ttl), keyFile, id, cfg, res, view)
result, status, err := s.generateResourceToken(ctx, state.ClientId, r.Resource, r.View, r.Role, time.Duration(state.Ttl), keyFile, id, cfg, res, view)
if err != nil {
httputil.WriteError(w, status, err)
return
}
access := strconv.Itoa(i)

out.Resources[r.Url] = &pb.ResourceTokens_Descriptor{
Interfaces: makeViewInterfaces(view, res, cfg, s.adapters),
interMap := map[string]*pb.ResourceResults_InterfaceEntry{}
for k, v := range makeViewInterfaces(view, res, cfg, s.adapters) {
entry := &pb.ResourceResults_InterfaceEntry{}
interMap[k] = entry
for _, uri := range v.Uri {
entry.Items = append(entry.Items, &pb.ResourceResults_ResourceInterface{Uri: uri, Labels: v.Labels})
}
}

out.Resources[r.Url] = &pb.ResourceResults_ResourceDescriptor{
Interfaces: interMap,
Permissions: makeRoleCategories(view, r.Role, cfg),
Access: access,
}
// TODO: remove these fields when no longer needed for the older interface
tok.Name = ""
tok.View = nil
tok.Ttl = ""
out.Access[access] = tok
out.Access[access] = &pb.ResourceResults_ResourceAccess{
Credentials: result.Credentials,
Labels: result.Labels,
}
}
httputil.WriteProtoResp(w, out)
httputil.WriteRPCResp(w, out, nil)
}

func (s *Service) resourceTokenState(stateID string, tx storage.Tx) (*pb.ResourceTokenRequestState, *ga4gh.Identity, error) {
Expand Down
26 changes: 17 additions & 9 deletions pages/damdemo/test.html
Original file line number Diff line number Diff line change
Expand Up @@ -318,20 +318,23 @@
var res = cart.resources[name];
var paths = [];
var btnClass = "";
for (var i in res.interfaces) {
for (var uri in res.interfaces[i].uri) {
var p = res.interfaces[i].uri[uri];
if (i == "http:gcp:gs") {
for (var interName in res.interfaces) {
var list = res.interfaces[interName].items
for (var idx = 0; idx < list.length; idx++) {
var inter = list[idx];
var path = inter.uri
if (interName == "http:gcp:gs") {
var num = browsePaths.length;
browsePaths.push({
url: p,
url: path,
labels: inter.labels,
access: res.access
});
p = `<span class="browse" onclick="browseDataset(${num})">${escapeHTML(p)}</span>`;
path = `<span class="browse" onclick="browseDataset(${num})">${escapeHTML(path)}</span>`;
} else {
p = escapeHTML(p);
path = escapeHTML(path);
}
paths.push(p);
paths.push(path);
}
}
html += `<tr><td>${escapeHTML(name)}</td><td>${paths.join(", ")}</td><td>${escapeHTML(res.permissions.join(", "))}</td></tr>`;
Expand All @@ -355,7 +358,12 @@
}
function browseDataset(index) {
var entry = browsePaths[index];
var token = cart.access[entry.access].access_token;
var creds = cart.access[entry.access] && cart.access[entry.access].credentials;
if (!creds) {
displayError("missing credentials", "", JSON.stringify(cart.access[entry.access], undefined, 2));
return;
}
var token = creds.access_token;
var url = entry.url.length ? entry.url+"/o/" + "?access_token=" + token : "";
if (!url) {
return;
Expand Down
Loading

0 comments on commit feaa0c0

Please sign in to comment.