lib/dam/config.go (331 lines of code) (raw):
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dam
import (
"context"
"fmt"
"net/http"
"sort"
"strings"
"github.com/gorilla/mux" /* copybara-comment */
"google.golang.org/grpc/codes" /* copybara-comment */
"google.golang.org/grpc/status" /* copybara-comment */
"github.com/golang/protobuf/proto" /* copybara-comment */
"github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/httputils" /* copybara-comment: httputils */
"github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/kms" /* copybara-comment: kms */
"github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/persona" /* copybara-comment: persona */
"github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/storage" /* copybara-comment: storage */
"github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/timeutil" /* copybara-comment: timeutil */
"github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/translator" /* copybara-comment: translator */
pb "github.com/GoogleCloudPlatform/healthcare-federated-access-services/proto/dam/v1" /* copybara-comment: go_proto */
)
// GetResources implements the GetResources RPC method.
func (s *Service) GetResources(w http.ResponseWriter, r *http.Request) {
cfg, err := s.loadConfig(nil, getRealm(r))
if err != nil {
httputils.WriteError(w, status.Errorf(codes.Unavailable, "%v", err))
return
}
resMap := make(map[string]*pb.Resource, 0)
for k, v := range cfg.Resources {
resMap[k] = makeResource(k, v, cfg, s.hidePolicyBasis, s.adapters)
}
resp := pb.GetResourcesResponse{
Resources: resMap,
}
httputils.WriteResp(w, proto.Message(&resp))
}
// GetResource implements the corresponding endpoint in the REST API.
func (s *Service) GetResource(w http.ResponseWriter, r *http.Request) {
cfg, err := s.loadConfig(nil, getRealm(r))
if err != nil {
httputils.WriteError(w, status.Errorf(codes.Unavailable, "%v", err))
return
}
name := getName(r)
if err := checkName(name); err != nil {
httputils.WriteError(w, status.Errorf(codes.InvalidArgument, "%v", err))
return
}
res, ok := cfg.Resources[name]
if !ok {
httputils.WriteError(w, status.Errorf(codes.NotFound, "resource %q not found", name))
return
}
resp := pb.GetResourceResponse{
Resource: makeResource(name, res, cfg, s.hidePolicyBasis, s.adapters),
Access: s.makeAccessList(nil, []string{name}, nil, nil, cfg, r, s.ValidateCfgOpts(getRealm(r), nil)),
}
httputils.WriteResp(w, proto.Message(&resp))
}
// GetFlatViews implements the corresponding REST API endpoint.
func (s *Service) GetFlatViews(w http.ResponseWriter, r *http.Request) {
cfg, err := s.loadConfig(nil, getRealm(r))
if err != nil {
httputils.WriteError(w, status.Errorf(codes.Unavailable, "%v", err))
return
}
viewMap := make(map[string]*pb.GetFlatViewsResponse_FlatView, 0)
for resname, res := range cfg.Resources {
for vname, view := range res.Views {
v := makeView(vname, view, res, cfg, s.hidePolicyBasis, s.adapters)
st, ok := cfg.ServiceTemplates[v.ServiceTemplate]
if !ok {
httputils.WriteError(w, status.Errorf(codes.Internal, "resource %q view %q service template %q is undefined", resname, vname, v.ServiceTemplate))
return
}
desc, ok := s.adapters.Descriptors[st.ServiceName]
if !ok {
httputils.WriteError(w, status.Errorf(codes.Internal, "resource %q view %q service template %q service name %q is undefined", resname, vname, v.ServiceTemplate, st.ServiceName))
return
}
labels := v.Labels
if labels == nil {
labels = map[string]string{}
}
for rolename := range v.Roles {
var roleCat []string
if sr := st.ServiceRoles[rolename]; sr != nil {
roleCat = sr.DamRoleCategories
sort.Strings(roleCat)
}
for interfaceName, iface := range v.ComputedInterfaces {
for _, interfaceURI := range iface.Uri {
if len(v.ContentTypes) == 0 {
v.ContentTypes = []string{"*"}
}
for _, mime := range v.ContentTypes {
key := res.Umbrella + "/" + resname + "/" + vname + "/" + rolename + "/" + interfaceName + "/" + mime
path := strings.Replace(r.URL.Path, "/flatViews", "/resources/"+resname+"/views/"+vname+"/roles/"+rolename, -1)
viewMap[key] = &pb.GetFlatViewsResponse_FlatView{
ResourcePath: path,
Umbrella: resname,
ResourceName: resname,
ViewName: vname,
RoleName: rolename,
InterfaceName: interfaceName,
InterfaceUri: interfaceURI,
ContentType: mime,
Labels: labels,
ServiceName: st.ServiceName,
Platform: desc.Platform,
PlatformService: st.ServiceName, // for now it is the same name as ServiceName
MaxTokenTtl: res.MaxTokenTtl,
ResourceUi: res.Ui,
ViewUi: v.Ui,
RoleUi: st.Ui,
RoleCategories: roleCat,
}
}
}
}
}
}
}
resp := pb.GetFlatViewsResponse{
Views: viewMap,
}
httputils.WriteResp(w, proto.Message(&resp))
}
// GetViews implements the corresponding endpoint in the REST API.
func (s *Service) GetViews(w http.ResponseWriter, r *http.Request) {
cfg, err := s.loadConfig(nil, getRealm(r))
if err != nil {
httputils.WriteError(w, status.Errorf(codes.Unavailable, "%v", err))
return
}
name := getName(r)
if err := checkName(name); err != nil {
httputils.WriteError(w, status.Errorf(codes.InvalidArgument, "%v", err))
return
}
res, ok := cfg.Resources[name]
if !ok {
httputils.WriteError(w, status.Errorf(codes.NotFound, "resource %q not found", name))
return
}
out := make(map[string]*pb.View, 0)
for k, v := range res.Views {
out[k] = makeView(k, v, res, cfg, s.hidePolicyBasis, s.adapters)
}
resp := pb.GetViewsResponse{
Views: out,
Access: s.makeAccessList(nil, []string{name}, nil, nil, cfg, r, s.ValidateCfgOpts(getRealm(r), nil)),
}
httputils.WriteResp(w, proto.Message(&resp))
}
// GetView implements the corresponding endpoint in the REST API.
func (s *Service) GetView(w http.ResponseWriter, r *http.Request) {
cfg, err := s.loadConfig(nil, getRealm(r))
if err != nil {
httputils.WriteError(w, status.Errorf(codes.Unavailable, "%v", err))
return
}
name := getName(r)
if err := checkName(name); err != nil {
httputils.WriteError(w, status.Errorf(codes.InvalidArgument, "%v", err))
return
}
res, ok := cfg.Resources[name]
if !ok {
httputils.WriteError(w, status.Errorf(codes.NotFound, "resource %q not found", name))
return
}
viewName := mux.Vars(r)["view"]
if err := checkName(viewName); err != nil {
httputils.WriteError(w, status.Errorf(codes.InvalidArgument, "%v", err))
return
}
view, ok := res.Views[viewName]
if !ok {
httputils.WriteError(w, status.Errorf(codes.NotFound, "resource %q view %q not found", name, viewName))
return
}
resp := pb.GetViewResponse{
View: makeView(viewName, view, res, cfg, s.hidePolicyBasis, s.adapters),
Access: s.makeAccessList(nil, []string{name}, []string{viewName}, nil, cfg, r, s.ValidateCfgOpts(getRealm(r), nil)),
}
httputils.WriteResp(w, proto.Message(&resp))
}
// GetViewRoles implements the corresponding endpoint in the REST API.
func (s *Service) GetViewRoles(w http.ResponseWriter, r *http.Request) {
cfg, err := s.loadConfig(nil, getRealm(r))
if err != nil {
httputils.WriteError(w, status.Errorf(codes.Unavailable, "%v", err))
return
}
name := getName(r)
if err := checkName(name); err != nil {
httputils.WriteError(w, status.Errorf(codes.InvalidArgument, "%v", err))
return
}
res, ok := cfg.Resources[name]
if !ok {
httputils.WriteError(w, status.Errorf(codes.NotFound, "resource %q not found", name))
return
}
viewName := mux.Vars(r)["view"]
if err := checkName(viewName); err != nil {
httputils.WriteError(w, status.Errorf(codes.InvalidArgument, "%v", err))
return
}
view, ok := res.Views[viewName]
if !ok {
httputils.WriteError(w, status.Errorf(codes.NotFound, "resource %q view %q not found", name, viewName))
return
}
out := makeViewRoles(view, res, cfg, s.hidePolicyBasis, s.adapters)
resp := pb.GetViewRolesResponse{
Roles: out,
Access: s.makeAccessList(nil, []string{name}, []string{viewName}, nil, cfg, r, s.ValidateCfgOpts(getRealm(r), nil)),
}
httputils.WriteResp(w, proto.Message(&resp))
}
// GetViewRole implements the corresponding endpoint in the REST API.
func (s *Service) GetViewRole(w http.ResponseWriter, r *http.Request) {
cfg, err := s.loadConfig(nil, getRealm(r))
if err != nil {
httputils.WriteError(w, status.Errorf(codes.Unavailable, "%v", err))
return
}
name := getName(r)
if err := checkName(name); err != nil {
httputils.WriteError(w, status.Errorf(codes.InvalidArgument, "%v", err))
return
}
res, ok := cfg.Resources[name]
if !ok {
httputils.WriteError(w, status.Errorf(codes.NotFound, "resource %q not found", name))
return
}
vars := mux.Vars(r)
viewName := vars["view"]
if err := checkName(viewName); err != nil {
httputils.WriteError(w, status.Errorf(codes.InvalidArgument, "%v", err))
return
}
view, ok := res.Views[viewName]
if !ok {
httputils.WriteError(w, status.Errorf(codes.NotFound, "resource %q view %q not found", name, viewName))
return
}
roleName := vars["role"]
if err := checkName(roleName); err != nil {
httputils.WriteError(w, status.Errorf(codes.InvalidArgument, "%v", err))
return
}
roles := makeViewRoles(view, res, cfg, s.hidePolicyBasis, s.adapters)
role, ok := roles[roleName]
if !ok {
httputils.WriteError(w, status.Errorf(codes.NotFound, "resource %q view %q role %q not found", name, viewName, roleName))
return
}
resp := pb.GetViewRoleResponse{
Role: role,
Access: s.makeAccessList(nil, []string{name}, []string{viewName}, []string{roleName}, cfg, r, s.ValidateCfgOpts(getRealm(r), nil)),
}
httputils.WriteResp(w, proto.Message(&resp))
}
// GetServiceDescriptors implements the corresponding REST API endpoint.
func (s *Service) GetServiceDescriptors(w http.ResponseWriter, r *http.Request) {
out := &pb.ServicesResponse{
Services: s.adapters.Descriptors,
}
httputils.WriteResp(w, out)
}
func (s *Service) getIssuerTranslator(ctx context.Context, issuer string, cfg *pb.DamConfig, secrets *pb.DamSecrets, tx storage.Tx) (translator.Translator, error) {
v, ok := s.translators.Load(issuer)
var t translator.Translator
var err error
if ok {
t, ok = v.(translator.Translator)
if !ok {
return nil, fmt.Errorf("passport issuer %q with wrong type", issuer)
}
return t, nil
}
var cfgTpi *pb.TrustedIssuer
for _, tpi := range cfg.TrustedIssuers {
if tpi.Issuer == issuer {
cfgTpi = tpi
break
}
}
if cfgTpi == nil {
return nil, fmt.Errorf("passport issuer not found %q", issuer)
}
if secrets == nil {
secrets, err = s.loadSecrets(tx)
if err != nil {
return nil, fmt.Errorf("load secrets: %q", err)
}
}
t, err = createIssuerTranslator(ctx, cfgTpi, secrets, s.signer)
if err != nil {
return nil, fmt.Errorf("failed to create translator for issuer %q: %v", issuer, err)
}
s.translators.Store(issuer, t)
return t, err
}
func createIssuerTranslator(ctx context.Context, cfgTpi *pb.TrustedIssuer, secrets *pb.DamSecrets, signer kms.Signer) (translator.Translator, error) {
return translator.CreateTranslator(ctx, cfgTpi.Issuer, cfgTpi.TranslateUsing, cfgTpi.ClientId, "", "", signer)
}
// GetLocaleMetadata implements the corresponding REST API endpoint.
func (s *Service) GetLocaleMetadata(w http.ResponseWriter, r *http.Request) {
type response struct {
Locales map[string]*timeutil.LocaleInfo `json:"locales"`
TimeZones map[string]*timeutil.TimezoneInfo `json:"timeZones"`
}
httputils.WriteNonProtoResp(w, &response{Locales: timeutil.GetLocales(), TimeZones: timeutil.GetTimeZones()})
}
// GetPassportTranslators implements the corresponding REST API endpoint.
func (s *Service) GetPassportTranslators(w http.ResponseWriter, r *http.Request) {
out := translator.GetPassportTranslators()
httputils.WriteResp(w, out)
}
// GetDamRoleCategories implements the corresponding REST API method.
func (s *Service) GetDamRoleCategories(w http.ResponseWriter, r *http.Request) {
out := &pb.DamRoleCategoriesResponse{
DamRoleCategories: s.roleCategories,
}
httputils.WriteResp(w, out)
}
// GetTestPersonas implements the corresponding REST API method.
func (s *Service) GetTestPersonas(w http.ResponseWriter, r *http.Request) {
cfg, err := s.loadConfig(nil, getRealm(r))
if err != nil {
httputils.WriteError(w, status.Errorf(codes.Unavailable, "%v", err))
return
}
out := &pb.GetTestPersonasResponse{
Personas: cfg.TestPersonas,
StandardClaims: persona.StandardClaims,
}
httputils.WriteResp(w, out)
}