From 0ac14e0be1127bcb7381a1a5ed7cd9f28abba3ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Mon, 4 Jul 2022 10:52:42 +0200 Subject: [PATCH 01/14] Add hierarchy level on top of sites --- pkg/mentix/accservice/accservice.go | 100 ---------------- pkg/mentix/connectors/gocdb.go | 57 ++++++--- pkg/mentix/connectors/gocdb/types.go | 13 +++ .../exchangers/exporters/cs3api/query.go | 78 +++++++------ .../exchangers/exporters/metrics/metrics.go | 14 ++- pkg/mentix/exchangers/exporters/promsd.go | 85 +++++++------- .../exchangers/exporters/siteloc/query.go | 18 +-- pkg/mentix/mentix.go | 6 - pkg/mentix/meshdata/meshdata.go | 67 +++++------ pkg/mentix/meshdata/operator.go | 110 ++++++++++++++++++ pkg/mentix/meshdata/properties.go | 2 + pkg/mentix/meshdata/site.go | 3 +- 12 files changed, 303 insertions(+), 250 deletions(-) delete mode 100644 pkg/mentix/accservice/accservice.go create mode 100644 pkg/mentix/meshdata/operator.go diff --git a/pkg/mentix/accservice/accservice.go b/pkg/mentix/accservice/accservice.go deleted file mode 100644 index 8d53bbba50..0000000000 --- a/pkg/mentix/accservice/accservice.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2018-2020 CERN -// -// 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. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package accservice - -import ( - "encoding/json" - "fmt" - "net/url" - "path" - "strings" - - "github.com/pkg/errors" - - "github.com/cs3org/reva/pkg/mentix/config" - "github.com/cs3org/reva/pkg/mentix/utils/network" -) - -// RequestResponse holds the response of an accounts service query. -type RequestResponse struct { - Success bool - Error string - Data interface{} -} - -type accountsServiceSettings struct { - URL *url.URL - User string - Password string -} - -var settings accountsServiceSettings - -// Query performs an account service query. -func Query(endpoint string, params network.URLParams) (*RequestResponse, error) { - fullURL, err := network.GenerateURL(fmt.Sprintf("%v://%v", settings.URL.Scheme, settings.URL.Host), path.Join(settings.URL.Path, endpoint), params) - if err != nil { - return nil, errors.Wrap(err, "error while building the service accounts query URL") - } - - data, err := network.ReadEndpoint(fullURL, &network.BasicAuth{User: settings.User, Password: settings.Password}, false) - if err != nil { - return nil, errors.Wrap(err, "unable to query the service accounts endpoint") - } - - resp := &RequestResponse{} - if err := json.Unmarshal(data, resp); err != nil { - return nil, errors.Wrap(err, "unable to unmarshal response data") - } - return resp, nil -} - -// GetResponseValue gets a value from an account service query using a dotted path notation. -func GetResponseValue(resp *RequestResponse, path string) interface{} { - if data, ok := resp.Data.(map[string]interface{}); ok { - tokens := strings.Split(path, ".") - for i, name := range tokens { - if i == len(tokens)-1 { - if value, ok := data[name]; ok { - return value - } - } - - if data, ok = data[name].(map[string]interface{}); !ok { - break - } - } - } - - return nil -} - -// InitAccountsService initializes the global accounts service. -func InitAccountsService(conf *config.Configuration) error { - URL, err := url.Parse(conf.AccountsService.URL) - if err != nil { - return errors.Wrap(err, "unable to parse the accounts service URL") - } - - settings.URL = URL - settings.User = conf.AccountsService.User - settings.Password = conf.AccountsService.Password - - return nil -} diff --git a/pkg/mentix/connectors/gocdb.go b/pkg/mentix/connectors/gocdb.go index 58b80850b3..43f71556ae 100755 --- a/pkg/mentix/connectors/gocdb.go +++ b/pkg/mentix/connectors/gocdb.go @@ -66,19 +66,25 @@ func (connector *GOCDBConnector) RetrieveMeshData() (*meshdata.MeshData, error) return nil, fmt.Errorf("could not query service types: %v", err) } - if err := connector.querySites(meshData); err != nil { - return nil, fmt.Errorf("could not query sites: %v", err) + if err := connector.queryNGIs(meshData); err != nil { + return nil, fmt.Errorf("could not query operators: %v", err) } - for _, site := range meshData.Sites { - // Get services associated with the current site - if err := connector.queryServices(meshData, site); err != nil { - return nil, fmt.Errorf("could not query services of site '%v': %v", site.Name, err) + for _, op := range meshData.Operators { + if err := connector.querySites(meshData, op); err != nil { + return nil, fmt.Errorf("could not query sites of operator '%v': %v", op.Name, err) } - // Get downtimes scheduled for the current site - if err := connector.queryDowntimes(meshData, site); err != nil { - return nil, fmt.Errorf("could not query downtimes of site '%v': %v", site.Name, err) + for _, site := range op.Sites { + // Get services associated with the current site + if err := connector.queryServices(meshData, site); err != nil { + return nil, fmt.Errorf("could not query services of site '%v': %v", site.Name, err) + } + + // Get downtimes scheduled for the current site + if err := connector.queryDowntimes(meshData, site); err != nil { + return nil, fmt.Errorf("could not query downtimes of site '%v': %v", site.Name, err) + } } } @@ -124,14 +130,39 @@ func (connector *GOCDBConnector) queryServiceTypes(meshData *meshdata.MeshData) return nil } -func (connector *GOCDBConnector) querySites(meshData *meshdata.MeshData) error { +func (connector *GOCDBConnector) queryNGIs(meshData *meshdata.MeshData) error { + var ngis gocdb.NGIs + if err := connector.query(&ngis, "get_ngi", false, true, network.URLParams{}); err != nil { + return err + } + + // Copy retrieved data into the mesh data + meshData.Operators = nil + for _, ngi := range ngis.NGIs { + operator := &meshdata.Operator{ + ID: ngi.Name, + Name: ngi.Name, + Homepage: "", + Email: ngi.Email, + HelpdeskEmail: ngi.HelpdeskEmail, + SecurityEmail: ngi.SecurityEmail, + Sites: nil, + Properties: map[string]string{}, + } + meshData.Operators = append(meshData.Operators, operator) + } + + return nil +} + +func (connector *GOCDBConnector) querySites(meshData *meshdata.MeshData, op *meshdata.Operator) error { var sites gocdb.Sites - if err := connector.query(&sites, "get_site", false, true, network.URLParams{}); err != nil { + if err := connector.query(&sites, "get_site", false, true, network.URLParams{"roc": op.ID}); err != nil { return err } // Copy retrieved data into the mesh data - meshData.Sites = nil + op.Sites = nil for _, site := range sites.Sites { properties := connector.extensionsToMap(&site.Extensions) @@ -158,7 +189,7 @@ func (connector *GOCDBConnector) querySites(meshData *meshdata.MeshData) error { Properties: properties, Downtimes: meshdata.Downtimes{}, } - meshData.Sites = append(meshData.Sites, meshsite) + op.Sites = append(op.Sites, meshsite) } return nil diff --git a/pkg/mentix/connectors/gocdb/types.go b/pkg/mentix/connectors/gocdb/types.go index d00d98254a..9227b1322e 100755 --- a/pkg/mentix/connectors/gocdb/types.go +++ b/pkg/mentix/connectors/gocdb/types.go @@ -40,6 +40,19 @@ type ServiceTypes struct { Types []*ServiceType `xml:"SERVICE_TYPE"` } +// NGI represents an NGI in GOCDB. +type NGI struct { + Name string `xml:"NAME"` + Email string `xml:"EMAIL"` + HelpdeskEmail string `xml:"HELPDESK_EMAIL"` + SecurityEmail string `xml:"SECURITY_EMAIL"` +} + +// NGIs is a list of NGI objects. +type NGIs struct { + NGIs []*NGI `xml:"NGI"` +} + // Site represents a site in GOCDB. type Site struct { ShortName string `xml:"SHORT_NAME"` diff --git a/pkg/mentix/exchangers/exporters/cs3api/query.go b/pkg/mentix/exchangers/exporters/cs3api/query.go index 2bdc056f72..0c18a39ab3 100755 --- a/pkg/mentix/exchangers/exporters/cs3api/query.go +++ b/pkg/mentix/exchangers/exporters/cs3api/query.go @@ -23,6 +23,7 @@ import ( "fmt" "net/http" "net/url" + "strings" ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" "github.com/cs3org/reva/pkg/mentix/utils" @@ -51,51 +52,54 @@ func HandleDefaultQuery(meshData *meshdata.MeshData, params url.Values, conf *co func convertMeshDataToOCMData(meshData *meshdata.MeshData, elevatedServiceTypes []string) ([]*ocmprovider.ProviderInfo, error) { // Convert the mesh data into the corresponding OCM data structures - providers := make([]*ocmprovider.ProviderInfo, 0, len(meshData.Sites)) - for _, site := range meshData.Sites { - // Gather all services from the site - services := make([]*ocmprovider.Service, 0, len(site.Services)) + providers := make([]*ocmprovider.ProviderInfo, 0, len(meshData.Operators)*3) + for _, op := range meshData.Operators { + for _, site := range op.Sites { + // Gather all services from the site + services := make([]*ocmprovider.Service, 0, len(site.Services)) - addService := func(host string, endpoint *meshdata.ServiceEndpoint, addEndpoints []*ocmprovider.ServiceEndpoint, apiVersion string) { - services = append(services, &ocmprovider.Service{ - Host: host, - Endpoint: convertServiceEndpointToOCMData(endpoint), - AdditionalEndpoints: addEndpoints, - ApiVersion: apiVersion, - }) - } + addService := func(host string, endpoint *meshdata.ServiceEndpoint, addEndpoints []*ocmprovider.ServiceEndpoint, apiVersion string) { + services = append(services, &ocmprovider.Service{ + Host: host, + Endpoint: convertServiceEndpointToOCMData(endpoint), + AdditionalEndpoints: addEndpoints, + ApiVersion: apiVersion, + }) + } - for _, service := range site.Services { - apiVersion := meshdata.GetPropertyValue(service.Properties, meshdata.PropertyAPIVersion, "") + for _, service := range site.Services { + apiVersion := meshdata.GetPropertyValue(service.Properties, meshdata.PropertyAPIVersion, "") - // Gather all additional endpoints of the service - addEndpoints := make([]*ocmprovider.ServiceEndpoint, 0, len(service.AdditionalEndpoints)) - for _, endpoint := range service.AdditionalEndpoints { - if utils.FindInStringArray(endpoint.Type.Name, elevatedServiceTypes, false) != -1 { - endpointURL, _ := url.Parse(endpoint.URL) - addService(endpointURL.Host, endpoint, nil, apiVersion) - } else { - addEndpoints = append(addEndpoints, convertServiceEndpointToOCMData(endpoint)) + // Gather all additional endpoints of the service + addEndpoints := make([]*ocmprovider.ServiceEndpoint, 0, len(service.AdditionalEndpoints)) + for _, endpoint := range service.AdditionalEndpoints { + if utils.FindInStringArray(endpoint.Type.Name, elevatedServiceTypes, false) != -1 { + endpointURL, _ := url.Parse(endpoint.URL) + addService(endpointURL.Host, endpoint, nil, apiVersion) + } else { + addEndpoints = append(addEndpoints, convertServiceEndpointToOCMData(endpoint)) + } } + + addService(service.Host, service.ServiceEndpoint, addEndpoints, apiVersion) } - addService(service.Host, service.ServiceEndpoint, addEndpoints, apiVersion) + // Copy the site info into a ProviderInfo + provider := &ocmprovider.ProviderInfo{ + Name: site.Name, + FullName: site.FullName, + Description: site.Description, + Organization: site.Organization, + Domain: site.Domain, + Homepage: site.Homepage, + Email: site.Email, + Services: services, + Properties: site.Properties, + } + provider.Properties[strings.ToUpper(meshdata.PropertyOperator)] = op.ID // Propagate the operator ID as a property + providers = append(providers, provider) } - - // Copy the site info into a ProviderInfo - providers = append(providers, &ocmprovider.ProviderInfo{ - Name: site.Name, - FullName: site.FullName, - Description: site.Description, - Organization: site.Organization, - Domain: site.Domain, - Homepage: site.Homepage, - Email: site.Email, - Services: services, - Properties: site.Properties, - }) } - return providers, nil } diff --git a/pkg/mentix/exchangers/exporters/metrics/metrics.go b/pkg/mentix/exchangers/exporters/metrics/metrics.go index 9863147d07..90a9beb6ca 100644 --- a/pkg/mentix/exchangers/exporters/metrics/metrics.go +++ b/pkg/mentix/exchangers/exporters/metrics/metrics.go @@ -39,6 +39,7 @@ type Metrics struct { } const ( + keyOperatorID = "operator_id" keySiteID = "site_id" keySiteName = "site" keyServiceType = "service_type" @@ -69,7 +70,7 @@ func (m *Metrics) registerMetrics() error { Name: m.isScheduledStats.Name(), Description: m.isScheduledStats.Description(), Measure: m.isScheduledStats, - TagKeys: []tag.Key{tag.MustNewKey(keySiteID), tag.MustNewKey(keySiteName), tag.MustNewKey(keyServiceType)}, + TagKeys: []tag.Key{tag.MustNewKey(keyOperatorID), tag.MustNewKey(keySiteID), tag.MustNewKey(keySiteName), tag.MustNewKey(keyServiceType)}, Aggregation: view.LastValue(), } @@ -82,17 +83,20 @@ func (m *Metrics) registerMetrics() error { // Update is used to update/expose all metrics. func (m *Metrics) Update(meshData *meshdata.MeshData) error { - for _, site := range meshData.Sites { - if err := m.exportSiteMetrics(site); err != nil { - return errors.Wrapf(err, "error while exporting metrics for site '%v'", site.Name) + for _, op := range meshData.Operators { + for _, site := range op.Sites { + if err := m.exportSiteMetrics(site, op); err != nil { + return errors.Wrapf(err, "error while exporting metrics for site '%v'", site.Name) + } } } return nil } -func (m *Metrics) exportSiteMetrics(site *meshdata.Site) error { +func (m *Metrics) exportSiteMetrics(site *meshdata.Site, op *meshdata.Operator) error { mutators := make([]tag.Mutator, 0) + mutators = append(mutators, tag.Insert(tag.MustNewKey(keyOperatorID), op.ID)) mutators = append(mutators, tag.Insert(tag.MustNewKey(keySiteID), site.ID)) mutators = append(mutators, tag.Insert(tag.MustNewKey(keySiteName), site.Name)) mutators = append(mutators, tag.Insert(tag.MustNewKey(keyServiceType), "SCIENCEMESH_HCHECK")) diff --git a/pkg/mentix/exchangers/exporters/promsd.go b/pkg/mentix/exchangers/exporters/promsd.go index 2c33c75935..baaf087682 100755 --- a/pkg/mentix/exchangers/exporters/promsd.go +++ b/pkg/mentix/exchangers/exporters/promsd.go @@ -36,7 +36,7 @@ import ( "github.com/cs3org/reva/pkg/mentix/meshdata" ) -type prometheusSDScrapeCreatorCallback = func(site *meshdata.Site, service *meshdata.Service, endpoint *meshdata.ServiceEndpoint) *prometheus.ScrapeConfig +type prometheusSDScrapeCreatorCallback = func(op *meshdata.Operator, site *meshdata.Site, service *meshdata.Service, endpoint *meshdata.ServiceEndpoint) *prometheus.ScrapeConfig type prometheusSDScrapeCreator struct { outputFilename string creatorCallback prometheusSDScrapeCreatorCallback @@ -51,41 +51,45 @@ type PrometheusSDExporter struct { } const ( - labelSiteName = "__meta_mentix_site" - labelSiteID = "__meta_mentix_site_id" - labelSiteCountry = "__meta_mentix_site_country" - labelType = "__meta_mentix_type" - labelURL = "__meta_mentix_url" - labelScheme = "__meta_mentix_scheme" - labelHost = "__meta_mentix_host" - labelPort = "__meta_mentix_port" - labelPath = "__meta_mentix_path" - labelServiceHost = "__meta_mentix_service_host" - labelServiceURL = "__meta_mentix_service_url" + labelOperatorName = "__meta_mentix_operator" + labelOperatorID = "__meta_mentix_operator_id" + labelSiteName = "__meta_mentix_site" + labelSiteID = "__meta_mentix_site_id" + labelSiteCountry = "__meta_mentix_site_country" + labelType = "__meta_mentix_type" + labelURL = "__meta_mentix_url" + labelScheme = "__meta_mentix_scheme" + labelHost = "__meta_mentix_host" + labelPort = "__meta_mentix_port" + labelPath = "__meta_mentix_path" + labelServiceHost = "__meta_mentix_service_host" + labelServiceURL = "__meta_mentix_service_url" ) -func createGenericScrapeConfig(site *meshdata.Site, service *meshdata.Service, endpoint *meshdata.ServiceEndpoint) *prometheus.ScrapeConfig { +func createGenericScrapeConfig(op *meshdata.Operator, site *meshdata.Site, service *meshdata.Service, endpoint *meshdata.ServiceEndpoint) *prometheus.ScrapeConfig { endpointURL, _ := url.Parse(endpoint.URL) - labels := getScrapeTargetLabels(site, service, endpoint) + labels := getScrapeTargetLabels(op, site, service, endpoint) return &prometheus.ScrapeConfig{ Targets: []string{endpointURL.Host}, Labels: labels, } } -func getScrapeTargetLabels(site *meshdata.Site, service *meshdata.Service, endpoint *meshdata.ServiceEndpoint) map[string]string { +func getScrapeTargetLabels(op *meshdata.Operator, site *meshdata.Site, service *meshdata.Service, endpoint *meshdata.ServiceEndpoint) map[string]string { endpointURL, _ := url.Parse(endpoint.URL) labels := map[string]string{ - labelSiteName: site.Name, - labelSiteID: site.ID, - labelSiteCountry: site.CountryCode, - labelType: endpoint.Type.Name, - labelURL: endpoint.URL, - labelScheme: endpointURL.Scheme, - labelHost: endpointURL.Hostname(), - labelPort: endpointURL.Port(), - labelPath: endpointURL.Path, - labelServiceHost: service.Host, - labelServiceURL: service.URL, + labelOperatorName: op.Name, + labelOperatorID: op.ID, + labelSiteName: site.Name, + labelSiteID: site.ID, + labelSiteCountry: site.CountryCode, + labelType: endpoint.Type.Name, + labelURL: endpoint.URL, + labelScheme: endpointURL.Scheme, + labelHost: endpointURL.Hostname(), + labelPort: endpointURL.Port(), + labelPath: endpointURL.Path, + labelServiceHost: service.Host, + labelServiceURL: service.URL, } return labels @@ -175,28 +179,30 @@ func (exporter *PrometheusSDExporter) exportMeshData() { func (exporter *PrometheusSDExporter) createScrapeConfigs(creatorCallback prometheusSDScrapeCreatorCallback, serviceFilter []string) []*prometheus.ScrapeConfig { var scrapes []*prometheus.ScrapeConfig - var addScrape = func(site *meshdata.Site, service *meshdata.Service, endpoint *meshdata.ServiceEndpoint) { + var addScrape = func(op *meshdata.Operator, site *meshdata.Site, service *meshdata.Service, endpoint *meshdata.ServiceEndpoint) { if len(serviceFilter) == 0 || utils.FindInStringArray(endpoint.Type.Name, serviceFilter, false) != -1 { - if scrape := creatorCallback(site, service, endpoint); scrape != nil { + if scrape := creatorCallback(op, site, service, endpoint); scrape != nil { scrapes = append(scrapes, scrape) } } } // Create a scrape config for each service alongside any additional endpoints - for _, site := range exporter.MeshData().Sites { - for _, service := range site.Services { - if !service.IsMonitored { - continue - } + for _, op := range exporter.MeshData().Operators { + for _, site := range op.Sites { + for _, service := range site.Services { + if !service.IsMonitored { + continue + } - // Add the "main" service to the scrapes - addScrape(site, service, service.ServiceEndpoint) + // Add the "main" service to the scrapes + addScrape(op, site, service, service.ServiceEndpoint) - // Add all additional endpoints as well - for _, endpoint := range service.AdditionalEndpoints { - if endpoint.IsMonitored { - addScrape(site, service, endpoint) + // Add all additional endpoints as well + for _, endpoint := range service.AdditionalEndpoints { + if endpoint.IsMonitored { + addScrape(op, site, service, endpoint) + } } } } @@ -205,7 +211,6 @@ func (exporter *PrometheusSDExporter) createScrapeConfigs(creatorCallback promet if scrapes == nil { scrapes = []*prometheus.ScrapeConfig{} } - return scrapes } diff --git a/pkg/mentix/exchangers/exporters/siteloc/query.go b/pkg/mentix/exchangers/exporters/siteloc/query.go index ba534e7597..2f478d9fc9 100755 --- a/pkg/mentix/exchangers/exporters/siteloc/query.go +++ b/pkg/mentix/exchangers/exporters/siteloc/query.go @@ -49,14 +49,16 @@ func HandleDefaultQuery(meshData *meshdata.MeshData, params url.Values, _ *confi func convertMeshDataToLocationData(meshData *meshdata.MeshData) ([]*SiteLocation, error) { // Gather the locations of all sites - locations := make([]*SiteLocation, 0, len(meshData.Sites)) - for _, site := range meshData.Sites { - locations = append(locations, &SiteLocation{ - SiteID: site.ID, - FullName: site.FullName, - Longitude: site.Longitude, - Latitude: site.Latitude, - }) + locations := make([]*SiteLocation, 0, len(meshData.Operators)*3) + for _, op := range meshData.Operators { + for _, site := range op.Sites { + locations = append(locations, &SiteLocation{ + SiteID: site.ID, + FullName: site.FullName, + Longitude: site.Longitude, + Latitude: site.Latitude, + }) + } } return locations, nil diff --git a/pkg/mentix/mentix.go b/pkg/mentix/mentix.go index 16ace2cea7..0b07e811f5 100644 --- a/pkg/mentix/mentix.go +++ b/pkg/mentix/mentix.go @@ -27,7 +27,6 @@ import ( "github.com/rs/zerolog" "github.com/cs3org/reva/pkg/appctx" - "github.com/cs3org/reva/pkg/mentix/accservice" "github.com/cs3org/reva/pkg/mentix/config" "github.com/cs3org/reva/pkg/mentix/connectors" "github.com/cs3org/reva/pkg/mentix/entity" @@ -322,11 +321,6 @@ func (mntx *Mentix) handleRequest(exchangers []exchangers.RequestExchanger, w ht // New creates a new Mentix service instance. func New(conf *config.Configuration, log *zerolog.Logger) (*Mentix, error) { - // Configure the accounts service upfront - if err := accservice.InitAccountsService(conf); err != nil { - return nil, fmt.Errorf("unable to initialize the accounts service: %v", err) - } - mntx := new(Mentix) if err := mntx.initialize(conf, log); err != nil { return nil, fmt.Errorf("unable to initialize Mentix: %v", err) diff --git a/pkg/mentix/meshdata/meshdata.go b/pkg/mentix/meshdata/meshdata.go index 7e6ec4e580..69b007c9bb 100644 --- a/pkg/mentix/meshdata/meshdata.go +++ b/pkg/mentix/meshdata/meshdata.go @@ -37,7 +37,7 @@ const ( // MeshData holds the entire mesh data managed by Mentix. type MeshData struct { - Sites []*Site + Operators []*Operator ServiceTypes []*ServiceType Status int `json:"-"` @@ -45,39 +45,39 @@ type MeshData struct { // Clear removes all saved data, leaving an empty mesh. func (meshData *MeshData) Clear() { - meshData.Sites = nil + meshData.Operators = nil meshData.ServiceTypes = nil meshData.Status = StatusDefault } -// AddSite adds a new site; if a site with the same ID already exists, the existing one is overwritten. -func (meshData *MeshData) AddSite(site *Site) { - if siteExisting := meshData.FindSite(site.ID); siteExisting != nil { - *siteExisting = *site +// AddOperator adds a new operator; if an operator with the same ID already exists, the existing one is overwritten. +func (meshData *MeshData) AddOperator(op *Operator) { + if opExisting := meshData.FindOperator(op.ID); opExisting != nil { + *opExisting = *op } else { - meshData.Sites = append(meshData.Sites, site) + meshData.Operators = append(meshData.Operators, op) } } -// RemoveSite removes the provided site. -func (meshData *MeshData) RemoveSite(site *Site) { - for idx, siteExisting := range meshData.Sites { - if strings.EqualFold(siteExisting.ID, site.ID) { // Remove the site by its ID - lastIdx := len(meshData.Sites) - 1 - meshData.Sites[idx] = meshData.Sites[lastIdx] - meshData.Sites[lastIdx] = nil - meshData.Sites = meshData.Sites[:lastIdx] +// RemoveOperator removes the provided operator. +func (meshData *MeshData) RemoveOperator(op *Operator) { + for idx, opExisting := range meshData.Operators { + if strings.EqualFold(opExisting.ID, op.ID) { // Remove the operator by its ID + lastIdx := len(meshData.Operators) - 1 + meshData.Operators[idx] = meshData.Operators[lastIdx] + meshData.Operators[lastIdx] = nil + meshData.Operators = meshData.Operators[:lastIdx] break } } } -// FindSite searches for a site with the given ID. -func (meshData *MeshData) FindSite(id string) *Site { - for _, site := range meshData.Sites { - if strings.EqualFold(site.ID, id) { - return site +// FindOperator searches for an operator with the given ID. +func (meshData *MeshData) FindOperator(id string) *Operator { + for _, op := range meshData.Operators { + if strings.EqualFold(op.ID, id) { + return op } } return nil @@ -117,8 +117,8 @@ func (meshData *MeshData) FindServiceType(name string) *ServiceType { // Merge merges data from another MeshData instance into this one. func (meshData *MeshData) Merge(inData *MeshData) { - for _, site := range inData.Sites { - meshData.AddSite(site) + for _, op := range inData.Operators { + meshData.AddOperator(op) } for _, serviceType := range inData.ServiceTypes { @@ -126,22 +126,11 @@ func (meshData *MeshData) Merge(inData *MeshData) { } } -// Unmerge removes data from another MeshData instance from this one. -func (meshData *MeshData) Unmerge(inData *MeshData) { - for _, site := range inData.Sites { - meshData.RemoveSite(site) - } - - for _, serviceType := range inData.ServiceTypes { - meshData.RemoveServiceType(serviceType) - } -} - // Verify checks if the mesh data is valid. func (meshData *MeshData) Verify() error { - // Verify all sites - for _, site := range meshData.Sites { - if err := site.Verify(); err != nil { + // Verify all operators + for _, op := range meshData.Operators { + if err := op.Verify(); err != nil { return err } } @@ -158,9 +147,9 @@ func (meshData *MeshData) Verify() error { // InferMissingData infers missing data from other data where possible. func (meshData *MeshData) InferMissingData() { - // Infer missing site data - for _, site := range meshData.Sites { - site.InferMissingData() + // Infer missing operator data + for _, op := range meshData.Operators { + op.InferMissingData() } // Infer missing service type data diff --git a/pkg/mentix/meshdata/operator.go b/pkg/mentix/meshdata/operator.go new file mode 100644 index 0000000000..35d73223f9 --- /dev/null +++ b/pkg/mentix/meshdata/operator.go @@ -0,0 +1,110 @@ +// Copyright 2018-2021 CERN +// +// 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. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package meshdata + +import ( + "fmt" + "strings" +) + +// Operator represents a complete operator including its sites managed by Mentix. +type Operator struct { + ID string + Name string + Homepage string + Email string + HelpdeskEmail string + SecurityEmail string + + Sites []*Site + Properties map[string]string +} + +// AddSite adds a new site; if a site with the same ID already exists, the existing one is overwritten. +func (op *Operator) AddSite(site *Site) { + if siteExisting := op.FindSite(site.ID); siteExisting != nil { + *siteExisting = *site + } else { + op.Sites = append(op.Sites, site) + } +} + +// RemoveSite removes the provided site. +func (op *Operator) RemoveSite(id string) { + if site := op.FindSite(id); site != nil { + for idx, siteExisting := range op.Sites { + if siteExisting == site { + lastIdx := len(op.Sites) - 1 + op.Sites[idx] = op.Sites[lastIdx] + op.Sites[lastIdx] = nil + op.Sites = op.Sites[:lastIdx] + break + } + } + } +} + +// FindSite searches for a site with the given ID. +func (op *Operator) FindSite(id string) *Site { + for _, site := range op.Sites { + if strings.EqualFold(site.ID, id) { + return site + } + } + return nil +} + +// Verify checks if the operator data is valid. +func (op *Operator) Verify() error { + // Verify data + if op.Name == "" { + return fmt.Errorf("operator name missing") + } + if op.Email == "" { + return fmt.Errorf("operator email missing") + } + + // Verify sites + for _, site := range op.Sites { + if err := site.Verify(); err != nil { + return err + } + } + + return nil +} + +// InferMissingData infers missing data from other data where possible. +func (op *Operator) InferMissingData() { + // Infer missing data + if op.Name == "" { + op.Name = op.ID + } + if op.HelpdeskEmail == "" { + op.HelpdeskEmail = op.Email + } + if op.SecurityEmail == "" { + op.SecurityEmail = op.Email + } + + // Infer missing for sites + for _, site := range op.Sites { + site.InferMissingData() + } +} diff --git a/pkg/mentix/meshdata/properties.go b/pkg/mentix/meshdata/properties.go index b4adccb541..e9dd80bf4f 100644 --- a/pkg/mentix/meshdata/properties.go +++ b/pkg/mentix/meshdata/properties.go @@ -21,6 +21,8 @@ package meshdata import "strings" const ( + // PropertyOperator identifies the operator property. + PropertyOperator = "operator" // PropertySiteID identifies the site ID property. PropertySiteID = "site_id" // PropertyOrganization identifies the organization property. diff --git a/pkg/mentix/meshdata/site.go b/pkg/mentix/meshdata/site.go index edf45d2ae8..9e6120e2a9 100644 --- a/pkg/mentix/meshdata/site.go +++ b/pkg/mentix/meshdata/site.go @@ -28,7 +28,6 @@ import ( // Site represents a single site managed by Mentix. type Site struct { - // Internal settings ID string Name string FullName string @@ -52,7 +51,7 @@ type Site struct { // AddService adds a new service; if a service with the same name already exists, the existing one is overwritten. func (site *Site) AddService(service *Service) { if serviceExisting := site.FindService(service.Name); serviceExisting != nil { - *service = *serviceExisting + *serviceExisting = *service } else { site.Services = append(site.Services, service) } From 1613c4af324897d72ed89aefa0cacdf1f83814a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Mon, 4 Jul 2022 12:37:41 +0200 Subject: [PATCH 02/14] Remove obsolete config --- .../config/http/services/mentix/_index.md | 27 ------------------- examples/mentix/mentix.toml | 7 ----- pkg/mentix/config/config.go | 6 ----- 3 files changed, 40 deletions(-) diff --git a/docs/content/en/docs/config/http/services/mentix/_index.md b/docs/content/en/docs/config/http/services/mentix/_index.md index c71eb414da..1b7a479a23 100644 --- a/docs/content/en/docs/config/http/services/mentix/_index.md +++ b/docs/content/en/docs/config/http/services/mentix/_index.md @@ -71,30 +71,3 @@ Mentix exposes its data via an HTTP endpoint using the `webapi` exporter. Data c - **metrics** The [Metrics](metrics) exporter exposes various site-specific metrics through Prometheus. - -## Site Accounts service -Mentix uses the Reva site accounts service to query information about site accounts. The following settings must be configured properly: - -{{% dir name="url" type="string" default="" %}} -The URL of the site accounts service. -{{< highlight toml >}} -[http.services.mentix.accounts] -url = "https://example.com/accounts" -{{< /highlight >}} -{{% /dir %}} - -{{% dir name="user" type="string" default="" %}} -The user name to use for basic HTTP authentication. -{{< highlight toml >}} -[http.services.mentix.accounts] -user = "hans" -{{< /highlight >}} -{{% /dir %}} - -{{% dir name="password" type="string" default="" %}} -The user password to use for basic HTTP authentication. -{{< highlight toml >}} -[http.services.mentix.accounts] -password = "secret" -{{< /highlight >}} -{{% /dir %}} diff --git a/examples/mentix/mentix.toml b/examples/mentix/mentix.toml index 340ff4c508..ba410b4d50 100644 --- a/examples/mentix/mentix.toml +++ b/examples/mentix/mentix.toml @@ -24,13 +24,6 @@ enabled_connectors = ["gocdb"] # Enable the Metrics exporter [http.services.mentix.exporters.metrics] -# Set up the accounts service used to query information about accounts associated with registered sites -[http.services.mentix.accounts] -# Depending on where the service is running, localhost may also be used here -url = "https://sciencemesh.example.com/iop/accounts" -user = "username" -password = "userpass" - # Configure the Prometheus Service Discovery: [http.services.mentix.exporters.promsd] # The following path must be made available to Prometheus. diff --git a/pkg/mentix/config/config.go b/pkg/mentix/config/config.go index 6ea8abb156..364fb77281 100644 --- a/pkg/mentix/config/config.go +++ b/pkg/mentix/config/config.go @@ -66,12 +66,6 @@ type Configuration struct { } `mapstructure:"metrics"` } `mapstructure:"exporters"` - AccountsService struct { - URL string `mapstructure:"url"` - User string `mapstructure:"user"` - Password string `mapstructure:"password"` - } `mapstructure:"accounts"` - // Internal settings EnabledConnectors []string `mapstructure:"-"` EnabledImporters []string `mapstructure:"-"` From fe6c8ac6d66fc326bb631a3604e8d4d29e91b62f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Mon, 4 Jul 2022 15:25:01 +0200 Subject: [PATCH 03/14] Adapt site accounts service to new hierarchy --- .../config/http/services/siteacc/_index.md | 4 +- examples/siteacc/siteacc.toml | 2 +- internal/http/services/siteacc/siteacc.go | 4 +- pkg/siteacc/account/contact/template.go | 4 +- pkg/siteacc/account/edit/template.go | 2 +- pkg/siteacc/account/manage/template.go | 18 +- pkg/siteacc/account/panel.go | 62 +++--- pkg/siteacc/account/registration/template.go | 16 +- pkg/siteacc/account/settings/template.go | 2 +- .../account/{site/site.go => sites/sites.go} | 6 +- .../account/{site => sites}/template.go | 14 +- pkg/siteacc/admin/template.go | 10 +- pkg/siteacc/alerting/dispatcher.go | 8 +- pkg/siteacc/config/config.go | 4 +- pkg/siteacc/config/endpoints.go | 9 +- pkg/siteacc/data/account.go | 30 +-- pkg/siteacc/data/filestorage.go | 44 ++-- pkg/siteacc/data/operator.go | 58 +++++ pkg/siteacc/data/opinfo.go | 98 +++++++++ pkg/siteacc/data/scopes.go | 4 +- pkg/siteacc/data/site.go | 13 +- pkg/siteacc/data/siteinfo.go | 64 ++---- pkg/siteacc/data/storage.go | 22 +- pkg/siteacc/email/email.go | 6 +- pkg/siteacc/email/template.go | 4 +- pkg/siteacc/endpoints.go | 51 ++--- pkg/siteacc/html/panel.go | 18 +- pkg/siteacc/html/session.go | 10 +- pkg/siteacc/html/sessionmanager.go | 2 +- pkg/siteacc/manager/accmanager.go | 10 +- pkg/siteacc/manager/opsmanager.go | 198 ++++++++++++++++++ pkg/siteacc/manager/sitesmanager.go | 185 ---------------- pkg/siteacc/manager/usersmanager.go | 24 +-- pkg/siteacc/siteacc.go | 24 +-- 34 files changed, 588 insertions(+), 442 deletions(-) rename pkg/siteacc/account/{site/site.go => sites/sites.go} (94%) rename pkg/siteacc/account/{site => sites}/template.go (82%) create mode 100644 pkg/siteacc/data/operator.go create mode 100644 pkg/siteacc/data/opinfo.go create mode 100644 pkg/siteacc/manager/opsmanager.go delete mode 100644 pkg/siteacc/manager/sitesmanager.go diff --git a/docs/content/en/docs/config/http/services/siteacc/_index.md b/docs/content/en/docs/config/http/services/siteacc/_index.md index a556106c19..4d0473a8db 100644 --- a/docs/content/en/docs/config/http/services/siteacc/_index.md +++ b/docs/content/en/docs/config/http/services/siteacc/_index.md @@ -122,10 +122,10 @@ driver = "file" ### Storage settings - File drivers {{% dir name="sites_file" type="string" default="" %}} -The sites file location. +The operators file location. {{< highlight toml >}} [http.services.siteacc.storage.file] -sites_file = "/var/reva/sites.json" +operators_file = "/var/reva/operators.json" {{< /highlight >}} {{% /dir %}} diff --git a/examples/siteacc/siteacc.toml b/examples/siteacc/siteacc.toml index 68a0c52e4f..128fbf566e 100644 --- a/examples/siteacc/siteacc.toml +++ b/examples/siteacc/siteacc.toml @@ -15,7 +15,7 @@ apikey = "verysecret" [http.services.siteacc.storage] driver = "file" [http.services.siteacc.storage.file] -sites_file = "/var/revad/sites.json" +operators_file = "/var/revad/operators.json" accounts_file = "/var/revad/accounts.json" # Email related settings diff --git a/internal/http/services/siteacc/siteacc.go b/internal/http/services/siteacc/siteacc.go index 16eef4a857..8bed2526ba 100644 --- a/internal/http/services/siteacc/siteacc.go +++ b/internal/http/services/siteacc/siteacc.go @@ -111,10 +111,10 @@ func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error) return nil, err } - // Create the site accounts instance + // Create the sites accounts instance siteacc, err := siteacc.New(conf, log) if err != nil { - return nil, errors.Wrap(err, "error creating the site accounts service") + return nil, errors.Wrap(err, "error creating the sites accounts service") } // Create the service diff --git a/pkg/siteacc/account/contact/template.go b/pkg/siteacc/account/contact/template.go index 9a5b76e1be..ff53bf9854 100644 --- a/pkg/siteacc/account/contact/template.go +++ b/pkg/siteacc/account/contact/template.go @@ -79,8 +79,8 @@ const tplBody = `

Contact the ScienceMesh administration using the form below.

Please include as much information as possible in your request, especially:

    -
  • The site your request refers to (if not obvious from your account information)
  • -
  • Your role within the ScienceMesh site (e.g., administrator, operational team member, etc.)
  • +
  • The site(s) your request refers to (if not obvious from your account information)
  • +
  • Your role within the ScienceMesh sites (e.g., administrator, operational team member, etc.)
  • Any specific reasons for your request
  • Anything else that might help to process your request
diff --git a/pkg/siteacc/account/edit/template.go b/pkg/siteacc/account/edit/template.go index 558f0295bd..b82327b76e 100644 --- a/pkg/siteacc/account/edit/template.go +++ b/pkg/siteacc/account/edit/template.go @@ -31,7 +31,7 @@ function verifyForm(formData) { } if (formData.getTrimmed("role") == "") { - setState(STATE_ERROR, "Please specify your role within your site.", "form", "role", true); + setState(STATE_ERROR, "Please specify your role within your operator.", "form", "role", true); return false; } diff --git a/pkg/siteacc/account/manage/template.go b/pkg/siteacc/account/manage/template.go index 5dc597dca7..1c71f77bf9 100644 --- a/pkg/siteacc/account/manage/template.go +++ b/pkg/siteacc/account/manage/template.go @@ -29,9 +29,9 @@ function handleEditAccount() { window.location.replace("{{getServerAddress}}/account/?path=edit"); } -function handleSiteSettings() { - setState(STATE_STATUS, "Redirecting to the site settings..."); - window.location.replace("{{getServerAddress}}/account/?path=site"); +function handleSitesSettings() { + setState(STATE_STATUS, "Redirecting to the sites settings..."); + window.location.replace("{{getServerAddress}}/account/?path=sites"); } function handleRequestAccess(scope) { @@ -79,7 +79,7 @@ const tplBody = `
  • Name: {{.Account.Title}}. {{.Account.FirstName}} {{.Account.LastName}}
  • Email: {{.Account.Email}}
  • -
  • ScienceMesh Site: {{getSiteName .Account.Site false}} ({{getSiteName .Account.Site true}})
  • +
  • ScienceMesh Operator: {{getOperatorName .Account.Operator}} ({{getOperatorSites .Account.Operator true}})
  • Role: {{.Account.Role}}
  • {{if .Account.PhoneNumber}}
  • Phone: {{.Account.PhoneNumber}}
  • @@ -89,7 +89,7 @@ const tplBody = `
    Account data:
      -
    • Site access: {{if .Account.Data.SiteAccess}}Granted{{else}}Not granted{{end}}
    • +
    • Sites access: {{if .Account.Data.SitesAccess}}Granted{{else}}Not granted{{end}}
    • GOCDB access: {{if .Account.Data.GOCDBAccess}}Granted{{else}}Not granted{{end}}
    @@ -101,14 +101,14 @@ const tplBody = `   {{if .Account.Data.SiteAccess}} - +   {{end}}
    - +
    @@ -117,8 +117,8 @@ const tplBody = `
    Notes:
      -
    • The Site access allows you to access and modify the global configuration of your site.
    • -
    • The GOCDB access allows you to log into the central database where all site metadata is stored.
    • +
    • The Sites access allows you to access and modify the global configuration of your sites.
    • +
    • The GOCDB access allows you to log into the central database where all sites metadata is stored.
    diff --git a/pkg/siteacc/account/panel.go b/pkg/siteacc/account/panel.go index a3c29ef55e..b0c3f286db 100644 --- a/pkg/siteacc/account/panel.go +++ b/pkg/siteacc/account/panel.go @@ -29,7 +29,7 @@ import ( "github.com/cs3org/reva/pkg/siteacc/account/manage" "github.com/cs3org/reva/pkg/siteacc/account/registration" "github.com/cs3org/reva/pkg/siteacc/account/settings" - "github.com/cs3org/reva/pkg/siteacc/account/site" + "github.com/cs3org/reva/pkg/siteacc/account/sites" "github.com/cs3org/reva/pkg/siteacc/config" "github.com/cs3org/reva/pkg/siteacc/data" "github.com/cs3org/reva/pkg/siteacc/html" @@ -51,7 +51,7 @@ const ( templateManage = "manage" templateSettings = "settings" templateEdit = "edit" - templateSite = "site" + templateSites = "sites" templateContact = "contact" templateRegistration = "register" ) @@ -86,8 +86,8 @@ func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger) return errors.Wrap(err, "unable to create the account editing template") } - if err := panel.htmlPanel.AddTemplate(templateSite, &site.PanelTemplate{}); err != nil { - return errors.Wrap(err, "unable to create the site template") + if err := panel.htmlPanel.AddTemplate(templateSites, &sites.PanelTemplate{}); err != nil { + return errors.Wrap(err, "unable to create the sites template") } if err := panel.htmlPanel.AddTemplate(templateContact, &contact.PanelTemplate{}); err != nil { @@ -103,7 +103,7 @@ func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger) // GetActiveTemplate returns the name of the active template. func (panel *Panel) GetActiveTemplate(session *html.Session, path string) string { - validPaths := []string{templateLogin, templateManage, templateSettings, templateEdit, templateSite, templateContact, templateRegistration} + validPaths := []string{templateLogin, templateManage, templateSettings, templateEdit, templateSites, templateContact, templateRegistration} template := templateLogin // Only allow valid template paths; redirect to the login page otherwise @@ -119,13 +119,13 @@ func (panel *Panel) GetActiveTemplate(session *html.Session, path string) string // PreExecute is called before the actual template is being executed. func (panel *Panel) PreExecute(session *html.Session, path string, w http.ResponseWriter, r *http.Request) (html.ExecutionResult, error) { - protectedPaths := []string{templateManage, templateSettings, templateEdit, templateSite, templateContact} + protectedPaths := []string{templateManage, templateSettings, templateEdit, templateSites, templateContact} if user := session.LoggedInUser(); user != nil { switch path { - case templateSite: - // If the logged in user doesn't have site access, redirect him back to the main account page - if !user.Account.Data.SiteAccess { + case templateSites: + // If the logged in user doesn't have sites access, redirect him back to the main account page + if !user.Account.Data.SitesAccess { return panel.redirect(templateManage, w, r), nil } @@ -154,29 +154,29 @@ func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, session *htm flatValues[strings.Title(k)] = v[0] } - availSites, err := data.QueryAvailableSites(panel.conf.Mentix.URL, panel.conf.Mentix.DataEndpoint) + availOps, err := data.QueryAvailableOperators(panel.conf.Mentix.URL, panel.conf.Mentix.DataEndpoint) if err != nil { - return errors.Wrap(err, "unable to query available sites") + return errors.Wrap(err, "unable to query available operators") } type TemplateData struct { - Site *data.Site - Account *data.Account - Params map[string]string + Operator *data.Operator + Account *data.Account + Params map[string]string - Titles []string - Sites []data.SiteInformation + Titles []string + Operators []data.OperatorInformation } tplData := TemplateData{ - Site: nil, - Account: nil, - Params: flatValues, - Titles: []string{"Mr", "Mrs", "Ms", "Prof", "Dr"}, - Sites: availSites, + Operator: nil, + Account: nil, + Params: flatValues, + Titles: []string{"Mr", "Mrs", "Ms", "Prof", "Dr"}, + Operators: availOps, } if user := session.LoggedInUser(); user != nil { - tplData.Site = panel.cloneUserSite(user.Site) + tplData.Operator = panel.cloneUserOperator(user.Operator) tplData.Account = user.Account } return tplData @@ -202,15 +202,17 @@ func (panel *Panel) redirect(path string, w http.ResponseWriter, r *http.Request return html.AbortExecution } -func (panel *Panel) cloneUserSite(site *data.Site) *data.Site { - // Clone the user's site and decrypt the credentials for the panel - siteClone := site.Clone(true) - id, secret, err := site.Config.TestClientCredentials.Get(panel.conf.Security.CredentialsPassphrase) - if err == nil { - siteClone.Config.TestClientCredentials.ID = id - siteClone.Config.TestClientCredentials.Secret = secret +func (panel *Panel) cloneUserOperator(op *data.Operator) *data.Operator { + // Clone the user's operator and decrypt all credentials for the panel + opClone := op.Clone(true) + for _, site := range opClone.Sites { + id, secret, err := site.Config.TestClientCredentials.Get(panel.conf.Security.CredentialsPassphrase) + if err == nil { + site.Config.TestClientCredentials.ID = id + site.Config.TestClientCredentials.Secret = secret + } } - return siteClone + return opClone } // NewPanel creates a new account panel. diff --git a/pkg/siteacc/account/registration/template.go b/pkg/siteacc/account/registration/template.go index babab0e52e..43a3075eda 100644 --- a/pkg/siteacc/account/registration/template.go +++ b/pkg/siteacc/account/registration/template.go @@ -35,13 +35,13 @@ function verifyForm(formData) { return false; } - if (formData.getTrimmed("site") == "") { - setState(STATE_ERROR, "Please select your ScienceMesh site.", "form", "site", true); + if (formData.getTrimmed("operator") == "") { + setState(STATE_ERROR, "Please select your ScienceMesh operator.", "form", "operator", true); return false; } if (formData.getTrimmed("role") == "") { - setState(STATE_ERROR, "Please specify your role within your site.", "form", "role", true); + setState(STATE_ERROR, "Please specify your role within your sites.", "form", "role", true); return false; } @@ -92,7 +92,7 @@ function handleAction(action) { "title": formData.getTrimmed("title"), "firstName": formData.getTrimmed("fname"), "lastName": formData.getTrimmed("lname"), - "site": formData.getTrimmed("site"), + "operator": formData.getTrimmed("operator"), "role": formData.getTrimmed("role"), "phoneNumber": formData.getTrimmed("phone"), "password": { @@ -124,11 +124,11 @@ const tplBody = `
    -
    +
    - + {{range .Operators}} + {{end}}
    diff --git a/pkg/siteacc/account/settings/template.go b/pkg/siteacc/account/settings/template.go index 98fec5d61b..75b1aaa4ad 100644 --- a/pkg/siteacc/account/settings/template.go +++ b/pkg/siteacc/account/settings/template.go @@ -78,7 +78,7 @@ const tplBody = `
    - +
    diff --git a/pkg/siteacc/account/site/site.go b/pkg/siteacc/account/sites/sites.go similarity index 94% rename from pkg/siteacc/account/site/site.go rename to pkg/siteacc/account/sites/sites.go index fc540a7fa3..bb266fc810 100644 --- a/pkg/siteacc/account/site/site.go +++ b/pkg/siteacc/account/sites/sites.go @@ -16,7 +16,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -package site +package sites import "github.com/cs3org/reva/pkg/siteacc/html" @@ -27,12 +27,12 @@ type PanelTemplate struct { // GetTitle returns the title of the panel. func (template *PanelTemplate) GetTitle() string { - return "ScienceMesh Site Configuration" + return "ScienceMesh Sites Configuration" } // GetCaption returns the caption which is displayed on the panel. func (template *PanelTemplate) GetCaption() string { - return "Configure your ScienceMesh Site!" + return "Configure your ScienceMesh Sites!" } // GetContentJavaScript delivers additional JavaScript code. diff --git a/pkg/siteacc/account/site/template.go b/pkg/siteacc/account/sites/template.go similarity index 82% rename from pkg/siteacc/account/site/template.go rename to pkg/siteacc/account/sites/template.go index fef2c646cc..6e9e61da11 100644 --- a/pkg/siteacc/account/site/template.go +++ b/pkg/siteacc/account/sites/template.go @@ -16,7 +16,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -package site +package sites const tplJavaScript = ` function verifyForm(formData) { @@ -39,7 +39,7 @@ function handleAction(action) { return; } - setState(STATE_STATUS, "Configuring site... this should only take a moment.", "form", null, false); + setState(STATE_STATUS, "Configuring sites... this should only take a moment.", "form", null, false); var xhr = new XMLHttpRequest(); xhr.open("POST", "{{getServerAddress}}/" + action); @@ -47,10 +47,10 @@ function handleAction(action) { xhr.onload = function() { if (this.status == 200) { - setState(STATE_SUCCESS, "Your site was successfully configured!", "form", null, true); + setState(STATE_SUCCESS, "Your sites was successfully configured!", "form", null, true); } else { var resp = JSON.parse(this.responseText); - setState(STATE_ERROR, "An error occurred while trying to configure your site:
    " + resp.error + "", "form", null, true); + setState(STATE_ERROR, "An error occurred while trying to configure your sites:
    " + resp.error + "", "form", null, true); } } @@ -84,14 +84,14 @@ input[type="checkbox"] { const tplBody = `
    -

    Configure your ScienceMesh Site below. These settings affect your entire site and not just your account.

    +

    Configure your ScienceMesh Site below. These settings affect your entire sites and not just your account.

     
    - +

    Test user settings

    -

    In order to perform automated tests on your site, a test user has to be configured below. Please note that the user has to exist in your Reva instance! If you do not have a user for automated tests in your instance yet, create one first.

    +

    In order to perform automated tests on your sites, a test user has to be configured below. Please note that the user has to exist in your Reva instance! If you do not have a user for automated tests in your instance yet, create one first.


    diff --git a/pkg/siteacc/admin/template.go b/pkg/siteacc/admin/template.go index b1ba600fa3..75b4d74eee 100644 --- a/pkg/siteacc/admin/template.go +++ b/pkg/siteacc/admin/template.go @@ -61,7 +61,7 @@ const tplBody = `
      -
    • ScienceMesh Site: {{getSiteName .Site false}} ({{getSiteName .Site true}})
    • +
    • ScienceMesh Operator: {{getOperatorName .Operator}} ({{getOperatorSites .Operator true}})
    • Role: {{.Role}}
    • Phone: {{.PhoneNumber}}
    @@ -73,7 +73,7 @@ const tplBody = `
    Account data:
      -
    • Site access: {{if .Data.SiteAccess}}Granted{{else}}Not granted{{end}}
    • +
    • Sites access: {{if .Data.SitesAccess}}Granted{{else}}Not granted{{end}}
    • GOCDB access: {{if .Data.GOCDBAccess}}Granted{{else}}Not granted{{end}}
    @@ -82,10 +82,10 @@ const tplBody = `
    - {{if .Data.SiteAccess}} - + {{if .Data.SitesAccess}} + {{else}} - + {{end}} {{if .Data.GOCDBAccess}} diff --git a/pkg/siteacc/alerting/dispatcher.go b/pkg/siteacc/alerting/dispatcher.go index 2ae350b42c..26700c4449 100644 --- a/pkg/siteacc/alerting/dispatcher.go +++ b/pkg/siteacc/alerting/dispatcher.go @@ -60,14 +60,14 @@ func (dispatcher *Dispatcher) initialize(conf *config.Configuration, log *zerolo // DispatchAlerts sends the provided alert(s) via email to the appropriate recipients. func (dispatcher *Dispatcher) DispatchAlerts(alerts *template.Data, accounts data.Accounts) error { for _, alert := range alerts.Alerts { - siteID, ok := alert.Labels["site_id"] + opID, ok := alert.Labels["operator_id"] if !ok { continue } // Dispatch the alert to all accounts configured to receive it for _, account := range accounts { - if strings.EqualFold(account.Site, siteID) /* && account.Settings.ReceiveAlerts */ { // TODO: Uncomment if alert notifications aren't mandatory anymore + if strings.EqualFold(account.Operator, opID) /* && account.Settings.ReceiveAlerts */ { // TODO: Uncomment if alert notifications aren't mandatory anymore if err := dispatcher.dispatchAlert(alert, account); err != nil { // Log errors only dispatcher.log.Err(err).Str("id", alert.Fingerprint).Str("recipient", account.Email).Msg("unable to dispatch alert to user") @@ -81,7 +81,7 @@ func (dispatcher *Dispatcher) DispatchAlerts(alerts *template.Data, accounts dat Email: dispatcher.conf.Email.NotificationsMail, FirstName: "ScienceMesh", LastName: "Global Alerts receiver", - Site: "Global", + Operator: "Global", Role: "Alerts receiver", Settings: data.AccountSettings{ ReceiveAlerts: true, @@ -107,7 +107,7 @@ func (dispatcher *Dispatcher) dispatchAlert(alert template.Alert, account *data. "Instance": alert.Labels["instance"], "Job": alert.Labels["job"], "Severity": alert.Labels["severity"], - "Site": alert.Labels["site"], + "Site": alert.Labels["sites"], "SiteID": alert.Labels["site_id"], "Description": alert.Annotations["description"], diff --git a/pkg/siteacc/config/config.go b/pkg/siteacc/config/config.go index 4c9484c0f8..0b3595dec1 100644 --- a/pkg/siteacc/config/config.go +++ b/pkg/siteacc/config/config.go @@ -36,8 +36,8 @@ type Configuration struct { Driver string `mapstructure:"driver"` File struct { - SitesFile string `mapstructure:"sites_file"` - AccountsFile string `mapstructure:"accounts_file"` + OperatorsFile string `mapstructure:"operators_file"` + AccountsFile string `mapstructure:"accounts_file"` } `mapstructure:"file"` } `mapstructure:"storage"` diff --git a/pkg/siteacc/config/endpoints.go b/pkg/siteacc/config/endpoints.go index 1186ea8994..94651936aa 100644 --- a/pkg/siteacc/config/endpoints.go +++ b/pkg/siteacc/config/endpoints.go @@ -40,8 +40,9 @@ const ( // EndpointSiteGet is the endpoint path for retrieving site data. EndpointSiteGet = "/site-get" - // EndpointSiteConfigure is the endpoint path for site configuration. - EndpointSiteConfigure = "/site-configure" + + // EndpointSitesConfigure is the endpoint path for sites configuration. + EndpointSitesConfigure = "/sites-configure" // EndpointLogin is the endpoint path for (internal) user login. EndpointLogin = "/login" @@ -55,8 +56,8 @@ const ( // EndpointVerifyUserToken is the endpoint path for user token validation. EndpointVerifyUserToken = "/verify-user-token" - // EndpointGrantSiteAccess is the endpoint path for granting or revoking Site access. - EndpointGrantSiteAccess = "/grant-site-access" + // EndpointGrantSitesAccess is the endpoint path for granting or revoking Sites access. + EndpointGrantSitesAccess = "/grant-sites-access" // EndpointGrantGOCDBAccess is the endpoint path for granting or revoking GOCDB access. EndpointGrantGOCDBAccess = "/grant-gocdb-access" diff --git a/pkg/siteacc/data/account.go b/pkg/siteacc/data/account.go index 8ea5f10912..cf8435a3e9 100644 --- a/pkg/siteacc/data/account.go +++ b/pkg/siteacc/data/account.go @@ -28,13 +28,13 @@ import ( "github.com/cs3org/reva/pkg/utils" ) -// Account represents a single site account. +// Account represents a single sites account. type Account struct { Email string `json:"email"` Title string `json:"title"` FirstName string `json:"firstName"` LastName string `json:"lastName"` - Site string `json:"site"` + Operator string `json:"operator"` Role string `json:"role"` PhoneNumber string `json:"phoneNumber"` @@ -47,18 +47,18 @@ type Account struct { Settings AccountSettings `json:"settings"` } -// AccountData holds additional data for a site account. +// AccountData holds additional data for a sites account. type AccountData struct { GOCDBAccess bool `json:"gocdbAccess"` - SiteAccess bool `json:"siteAccess"` + SitesAccess bool `json:"sitesAccess"` } -// AccountSettings holds additional settings for a site account. +// AccountSettings holds additional settings for a sites account. type AccountSettings struct { ReceiveAlerts bool `json:"receiveAlerts"` } -// Accounts holds an array of site accounts. +// Accounts holds an array of sites accounts. type Accounts = []*Account // Update copies the data of the given account to this account. @@ -126,8 +126,8 @@ func (acc *Account) CheckScopeAccess(scope string) bool { case ScopeGOCDB: hasAccess = acc.Data.GOCDBAccess - case ScopeSite: - hasAccess = acc.Data.SiteAccess + case ScopeSites: + hasAccess = acc.Data.SitesAccess } return hasAccess @@ -139,7 +139,7 @@ func (acc *Account) Cleanup() { acc.Title = strings.TrimSpace(acc.Title) acc.FirstName = strings.TrimSpace(acc.FirstName) acc.LastName = strings.TrimSpace(acc.LastName) - acc.Site = strings.TrimSpace(acc.Site) + acc.Operator = strings.TrimSpace(acc.Operator) acc.Role = strings.TrimSpace(acc.Role) acc.PhoneNumber = strings.TrimSpace(acc.PhoneNumber) } @@ -163,8 +163,8 @@ func (acc *Account) verify(isNewAccount, verifyPassword bool) error { return errors.Errorf("last name contains invalid characters: %v", acc.LastName) } - if isNewAccount && acc.Site == "" { - return errors.Errorf("no site provided") + if isNewAccount && acc.Operator == "" { + return errors.Errorf("no operator provided") } if acc.Role == "" { @@ -186,8 +186,8 @@ func (acc *Account) verify(isNewAccount, verifyPassword bool) error { return nil } -// NewAccount creates a new site account. -func NewAccount(email string, title, firstName, lastName string, site, role string, phoneNumber string, password string) (*Account, error) { +// NewAccount creates a new sites account. +func NewAccount(email string, title, firstName, lastName string, operator, role string, phoneNumber string, password string) (*Account, error) { t := time.Now() acc := &Account{ @@ -195,14 +195,14 @@ func NewAccount(email string, title, firstName, lastName string, site, role stri Title: title, FirstName: firstName, LastName: lastName, - Site: site, + Operator: operator, Role: role, PhoneNumber: phoneNumber, DateCreated: t, DateModified: t, Data: AccountData{ GOCDBAccess: false, - SiteAccess: false, + SitesAccess: false, }, Settings: AccountSettings{ ReceiveAlerts: true, diff --git a/pkg/siteacc/data/filestorage.go b/pkg/siteacc/data/filestorage.go index 66f3a4190b..6c72796a78 100644 --- a/pkg/siteacc/data/filestorage.go +++ b/pkg/siteacc/data/filestorage.go @@ -36,8 +36,8 @@ type FileStorage struct { conf *config.Configuration log *zerolog.Logger - sitesFilePath string - accountsFilePath string + operatorsFilePath string + accountsFilePath string } func (storage *FileStorage) initialize(conf *config.Configuration, log *zerolog.Logger) error { @@ -51,10 +51,10 @@ func (storage *FileStorage) initialize(conf *config.Configuration, log *zerolog. } storage.log = log - if conf.Storage.File.SitesFile == "" { - return errors.Errorf("no sites file set in the configuration") + if conf.Storage.File.OperatorsFile == "" { + return errors.Errorf("no operators file set in the configuration") } - storage.sitesFilePath = conf.Storage.File.SitesFile + storage.operatorsFilePath = conf.Storage.File.OperatorsFile if conf.Storage.File.AccountsFile == "" { return errors.Errorf("no accounts file set in the configuration") @@ -62,7 +62,7 @@ func (storage *FileStorage) initialize(conf *config.Configuration, log *zerolog. storage.accountsFilePath = conf.Storage.File.AccountsFile // Create the file directories if necessary - _ = os.MkdirAll(filepath.Dir(storage.sitesFilePath), 0755) + _ = os.MkdirAll(filepath.Dir(storage.operatorsFilePath), 0755) _ = os.MkdirAll(filepath.Dir(storage.accountsFilePath), 0755) return nil @@ -82,13 +82,13 @@ func (storage *FileStorage) readData(file string, obj interface{}) error { return nil } -// ReadSites reads all stored sites into the given data object. -func (storage *FileStorage) ReadSites() (*Sites, error) { - sites := &Sites{} - if err := storage.readData(storage.sitesFilePath, sites); err != nil { - return nil, errors.Wrap(err, "error reading sites") +// ReadOperators reads all stored operators into the given data object. +func (storage *FileStorage) ReadOperators() (*Operators, error) { + operators := &Operators{} + if err := storage.readData(storage.operatorsFilePath, operators); err != nil { + return nil, errors.Wrap(err, "error reading operators") } - return sites, nil + return operators, nil } // ReadAccounts reads all stored accounts into the given data object. @@ -109,10 +109,10 @@ func (storage *FileStorage) writeData(file string, obj interface{}) error { return nil } -// WriteSites writes all stored sites from the given data object. -func (storage *FileStorage) WriteSites(sites *Sites) error { - if err := storage.writeData(storage.sitesFilePath, sites); err != nil { - return errors.Wrap(err, "error writing sites") +// WriteOperators writes all stored operators from the given data object. +func (storage *FileStorage) WriteOperators(ops *Operators) error { + if err := storage.writeData(storage.operatorsFilePath, ops); err != nil { + return errors.Wrap(err, "error writing operators") } return nil } @@ -125,18 +125,18 @@ func (storage *FileStorage) WriteAccounts(accounts *Accounts) error { return nil } -// SiteAdded is called when a site has been added. -func (storage *FileStorage) SiteAdded(site *Site) { +// OperatorAdded is called when a sites has been added. +func (storage *FileStorage) OperatorAdded(op *Operator) { // Simply skip this action; all data is saved solely in WriteSites } -// SiteUpdated is called when a site has been updated. -func (storage *FileStorage) SiteUpdated(site *Site) { +// OperatorUpdated is called when a sites has been updated. +func (storage *FileStorage) OperatorUpdated(op *Operator) { // Simply skip this action; all data is saved solely in WriteSites } -// SiteRemoved is called when a site has been removed. -func (storage *FileStorage) SiteRemoved(site *Site) { +// OperatorRemoved is called when a sites has been removed. +func (storage *FileStorage) OperatorRemoved(op *Operator) { // Simply skip this action; all data is saved solely in WriteSites } diff --git a/pkg/siteacc/data/operator.go b/pkg/siteacc/data/operator.go new file mode 100644 index 0000000000..b5eb6a52d5 --- /dev/null +++ b/pkg/siteacc/data/operator.go @@ -0,0 +1,58 @@ +// Copyright 2018-2022 CERN +// +// 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. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package data + +// Operator represents the global operator-specific settings stored in the service. +type Operator struct { + ID string `json:"id"` + + Sites []*Site `json:"sites"` +} + +// Operators holds an array of operators. +type Operators = []*Operator + +// Update copies the data of the given operator to this operator. +func (op *Operator) Update(other *Operator) error { + return nil +} + +// Clone creates a copy of the operator; if eraseCredentials is set to true, the (test user) credentials will be cleared in the cloned object. +func (op *Operator) Clone(eraseCredentials bool) *Operator { + clone := &Operator{ + ID: op.ID, + Sites: []*Site{}, + } + + // Clone sites + for _, site := range op.Sites { + clone.Sites = append(clone.Sites, site.Clone(eraseCredentials)) + } + + return clone +} + +// NewOperator creates a new operator. +func NewOperator(id string) (*Operator, error) { + op := &Operator{ + ID: id, + Sites: []*Site{}, + } + return op, nil +} diff --git a/pkg/siteacc/data/opinfo.go b/pkg/siteacc/data/opinfo.go new file mode 100644 index 0000000000..3f56e16915 --- /dev/null +++ b/pkg/siteacc/data/opinfo.go @@ -0,0 +1,98 @@ +// Copyright 2018-2022 CERN +// +// 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. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package data + +import ( + "encoding/json" + "sort" + + "github.com/cs3org/reva/pkg/mentix/utils/network" + "github.com/pkg/errors" +) + +// OperatorInformation holds the most basic information about an operator and its sites. +type OperatorInformation struct { + ID string + Name string + + Sites []SiteInformation +} + +// QueryAvailableOperators uses Mentix to query a list of all available operators and sites. +func QueryAvailableOperators(mentixHost, dataEndpoint string) ([]OperatorInformation, error) { + mentixURL, err := network.GenerateURL(mentixHost, dataEndpoint, network.URLParams{}) + if err != nil { + return nil, errors.Wrap(err, "unable to generate Mentix URL") + } + + data, err := network.ReadEndpoint(mentixURL, nil, true) + if err != nil { + return nil, errors.Wrap(err, "unable to read the Mentix endpoint") + } + + // Decode the data into a simplified, reduced data type + type operatorsData struct { + Operators []OperatorInformation + } + operators := operatorsData{} + if err := json.Unmarshal(data, &operators); err != nil { + return nil, errors.Wrap(err, "error while decoding the JSON data") + } + + sort.Slice(operators.Operators, func(i, j int) bool { + return operators.Operators[i].Name < operators.Operators[j].Name + }) + return operators.Operators, nil +} + +// QueryOperatorName uses Mentix to query the name of an operator given by its ID. +func QueryOperatorName(opID string, mentixHost, dataEndpoint string) (string, error) { + ops, err := QueryAvailableOperators(mentixHost, dataEndpoint) + if err != nil { + return "", err + } + + for _, op := range ops { + if op.ID == opID { + return op.Name, nil + } + } + + return "", errors.Errorf("no operator with ID %v found", opID) +} + +// QueryOperatorSites uses Mentix to query the sites associated with the specified operator. +func QueryOperatorSites(opID string, mentixHost, dataEndpoint string) ([]string, error) { + ops, err := QueryAvailableOperators(mentixHost, dataEndpoint) + if err != nil { + return []string{}, err + } + + for _, op := range ops { + if op.ID == opID { + var sites []string + for _, site := range op.Sites { + sites = append(sites, site.ID) + } + return sites, nil + } + } + + return []string{}, errors.Errorf("no operator with ID %v found", opID) +} diff --git a/pkg/siteacc/data/scopes.go b/pkg/siteacc/data/scopes.go index 438c9a1c53..2a8728800c 100644 --- a/pkg/siteacc/data/scopes.go +++ b/pkg/siteacc/data/scopes.go @@ -23,6 +23,6 @@ const ( ScopeDefault = "" // ScopeGOCDB is used to access the GOCDB. ScopeGOCDB = "gocdb" - // ScopeSite is used to access the global site configuration. - ScopeSite = "site" + // ScopeSites is used to access the global sites configuration. + ScopeSites = "sites" ) diff --git a/pkg/siteacc/data/site.go b/pkg/siteacc/data/site.go index 8e003d985d..25df60654e 100644 --- a/pkg/siteacc/data/site.go +++ b/pkg/siteacc/data/site.go @@ -23,22 +23,19 @@ import ( "github.com/pkg/errors" ) -// Site represents the global site-specific settings stored in the service. +// Site represents the global sites-specific settings stored in the service. type Site struct { ID string `json:"id"` Config SiteConfiguration `json:"config"` } -// SiteConfiguration stores the global configuration of a site. +// SiteConfiguration stores the global configuration of a sites. type SiteConfiguration struct { TestClientCredentials credentials.Credentials `json:"testClientCredentials"` } -// Sites holds an array of sites. -type Sites = []*Site - -// Update copies the data of the given site to this site. +// Update copies the data of the given sites to this sites. func (site *Site) Update(other *Site, credsPassphrase string) error { if other.Config.TestClientCredentials.IsValid() { // If credentials were provided, use those as the new ones @@ -58,7 +55,7 @@ func (site *Site) UpdateTestClientCredentials(id, secret string, passphrase stri return nil } -// Clone creates a copy of the site; if eraseCredentials is set to true, the (test user) credentials will be cleared in the cloned object. +// Clone creates a copy of the sites; if eraseCredentials is set to true, the (test user) credentials will be cleared in the cloned object. func (site *Site) Clone(eraseCredentials bool) *Site { clone := *site @@ -69,7 +66,7 @@ func (site *Site) Clone(eraseCredentials bool) *Site { return &clone } -// NewSite creates a new site. +// NewSite creates a new sites. func NewSite(id string) (*Site, error) { site := &Site{ ID: id, diff --git a/pkg/siteacc/data/siteinfo.go b/pkg/siteacc/data/siteinfo.go index adc4654584..1e8213de16 100644 --- a/pkg/siteacc/data/siteinfo.go +++ b/pkg/siteacc/data/siteinfo.go @@ -1,4 +1,4 @@ -// Copyright 2018-2020 CERN +// Copyright 2018-2022 CERN // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,71 +19,33 @@ package data import ( - "encoding/json" - "sort" - - "github.com/cs3org/reva/pkg/mentix/utils/network" "github.com/pkg/errors" ) -// SiteInformation holds the most basic information about a site. +// SiteInformation holds the most basic information about a sites. type SiteInformation struct { ID string Name string FullName string } -// QueryAvailableSites uses Mentix to query a list of all available (registered) sites. -func QueryAvailableSites(mentixHost, dataEndpoint string) ([]SiteInformation, error) { - mentixURL, err := network.GenerateURL(mentixHost, dataEndpoint, network.URLParams{}) - if err != nil { - return nil, errors.Wrap(err, "unable to generate Mentix URL") - } - - data, err := network.ReadEndpoint(mentixURL, nil, true) - if err != nil { - return nil, errors.Wrap(err, "unable to read the Mentix endpoint") - } - - // Decode the data into a simplified, reduced data type - type siteData struct { - Sites []SiteInformation - } - sites := siteData{} - if err := json.Unmarshal(data, &sites); err != nil { - return nil, errors.Wrap(err, "error while decoding the JSON data") - } - - // Sort the sites alphabetically by their names - sort.Slice(sites.Sites, func(i, j int) bool { - return sites.Sites[i].Name < sites.Sites[j].Name - }) - - return sites.Sites, nil -} - -// QuerySiteName uses Mentix to query the name of a site given by its ID. +// QuerySiteName uses Mentix to query the name of a sites given by its ID. func QuerySiteName(siteID string, fullName bool, mentixHost, dataEndpoint string) (string, error) { - sites, err := QueryAvailableSites(mentixHost, dataEndpoint) + ops, err := QueryAvailableOperators(mentixHost, dataEndpoint) if err != nil { return "", err } - index := len(sites) - for i, site := range sites { - if site.ID == siteID { - index = i - break + for _, op := range ops { + for _, site := range op.Sites { + if site.ID == siteID { + if fullName { + return site.FullName, nil + } + return site.Name, nil + } } } - if index != len(sites) { - if fullName { - return sites[index].FullName, nil - } - - return sites[index].Name, nil - } - - return "", errors.Errorf("no site with ID %v found", siteID) + return "", errors.Errorf("no sites with ID %v found", siteID) } diff --git a/pkg/siteacc/data/storage.go b/pkg/siteacc/data/storage.go index 7d770a3f2f..b7ff2bc0b7 100644 --- a/pkg/siteacc/data/storage.go +++ b/pkg/siteacc/data/storage.go @@ -18,19 +18,19 @@ package data -// Storage defines the interface for sites and accounts storages. +// Storage defines the interface for operators and accounts storages. type Storage interface { - // ReadSites reads all stored sites into the given data object. - ReadSites() (*Sites, error) - // WriteSites writes all stored sites from the given data object. - WriteSites(sites *Sites) error + // ReadOperators reads all stored operators into the given data object. + ReadOperators() (*Operators, error) + // WriteOperators writes all stored operators from the given data object. + WriteOperators(ops *Operators) error - // SiteAdded is called when a site has been added. - SiteAdded(site *Site) - // SiteUpdated is called when a site has been updated. - SiteUpdated(site *Site) - // SiteRemoved is called when a site has been removed. - SiteRemoved(site *Site) + // OperatorAdded is called when an operator has been added. + OperatorAdded(op *Operator) + // OperatorUpdated is called when an operator has been updated. + OperatorUpdated(op *Operator) + // OperatorRemoved is called when an operator has been removed. + OperatorRemoved(op *Operator) // ReadAccounts reads all stored accounts into the given data object. ReadAccounts() (*Accounts, error) diff --git a/pkg/siteacc/email/email.go b/pkg/siteacc/email/email.go index f8c681d0e9..7db45b06f2 100644 --- a/pkg/siteacc/email/email.go +++ b/pkg/siteacc/email/email.go @@ -55,9 +55,9 @@ func SendAccountCreated(account *data.Account, recipients []string, params map[s return send(recipients, "ScienceMesh: Site Administrator Account created", accountCreatedTemplate, getEmailData(account, conf, params), conf.Email.SMTP) } -// SendSiteAccessGranted sends an email about granted Site access. -func SendSiteAccessGranted(account *data.Account, recipients []string, params map[string]string, conf config.Configuration) error { - return send(recipients, "ScienceMesh: Site access granted", siteAccessGrantedTemplate, getEmailData(account, conf, params), conf.Email.SMTP) +// SendSitesAccessGranted sends an email about granted Sites access. +func SendSitesAccessGranted(account *data.Account, recipients []string, params map[string]string, conf config.Configuration) error { + return send(recipients, "ScienceMesh: Sites access granted", sitesAccessGrantedTemplate, getEmailData(account, conf, params), conf.Email.SMTP) } // SendGOCDBAccessGranted sends an email about granted GOCDB access. diff --git a/pkg/siteacc/email/template.go b/pkg/siteacc/email/template.go index 6912d1dfb2..b99b1aeffb 100644 --- a/pkg/siteacc/email/template.go +++ b/pkg/siteacc/email/template.go @@ -32,10 +32,10 @@ Kind regards, The ScienceMesh Team ` -const siteAccessGrantedTemplate = ` +const sitesAccessGrantedTemplate = ` Dear {{.Account.FirstName}} {{.Account.LastName}}, -You have been granted access to the global configuration of your site. +You have been granted access to the global configuration of your sites. Log in to your account to access this configuration: {{.AccountsAddress}} diff --git a/pkg/siteacc/endpoints.go b/pkg/siteacc/endpoints.go index 0079843bc1..9d1a005dac 100644 --- a/pkg/siteacc/endpoints.go +++ b/pkg/siteacc/endpoints.go @@ -76,7 +76,8 @@ func getEndpoints() []endpoint { {config.EndpointRemove, callMethodEndpoint, createMethodCallbacks(nil, handleRemove), false}, // Site endpoints {config.EndpointSiteGet, callMethodEndpoint, createMethodCallbacks(handleSiteGet, nil), false}, - {config.EndpointSiteConfigure, callMethodEndpoint, createMethodCallbacks(nil, handleSiteConfigure), false}, + // Sites endpoints + {config.EndpointSitesConfigure, callMethodEndpoint, createMethodCallbacks(nil, handleSitesConfigure), false}, // Login endpoints {config.EndpointLogin, callMethodEndpoint, createMethodCallbacks(nil, handleLogin), true}, {config.EndpointLogout, callMethodEndpoint, createMethodCallbacks(handleLogout, nil), true}, @@ -85,7 +86,7 @@ func getEndpoints() []endpoint { // Authentication endpoints {config.EndpointVerifyUserToken, callMethodEndpoint, createMethodCallbacks(handleVerifyUserToken, nil), true}, // Access management endpoints - {config.EndpointGrantSiteAccess, callMethodEndpoint, createMethodCallbacks(nil, handleGrantSiteAccess), false}, + {config.EndpointGrantSitesAccess, callMethodEndpoint, createMethodCallbacks(nil, handleGrantSitesAccess), false}, {config.EndpointGrantGOCDBAccess, callMethodEndpoint, createMethodCallbacks(nil, handleGrantGOCDBAccess), false}, // Alerting endpoints {config.EndpointDispatchAlert, callMethodEndpoint, createMethodCallbacks(nil, handleDispatchAlert), false}, @@ -239,34 +240,36 @@ func handleSiteGet(siteacc *SiteAccounts, values url.Values, body []byte, sessio if siteID == "" { return nil, errors.Errorf("no site specified") } - site := siteacc.SitesManager().FindSite(siteID) + _, site := siteacc.OperatorsManager().FindSite(siteID) if site == nil { return nil, errors.Errorf("no site with ID %v exists", siteID) } return map[string]interface{}{"site": site.Clone(false)}, nil } -func handleSiteConfigure(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { - email, _, err := processInvoker(siteacc, values, session) - if err != nil { - return nil, err - } - account, err := siteacc.AccountsManager().FindAccount(manager.FindByEmail, email) - if err != nil { - return nil, err - } - - siteData := &data.Site{} - if err := json.Unmarshal(body, siteData); err != nil { - return nil, errors.Wrap(err, "invalid form data") - } - siteData.ID = account.Site +func handleSitesConfigure(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { + // TODO: + /* + email, _, err := processInvoker(siteacc, values, session) + if err != nil { + return nil, err + } + account, err := siteacc.AccountsManager().FindAccount(manager.FindByEmail, email) + if err != nil { + return nil, err + } - // Configure the site through the sites manager - if err := siteacc.SitesManager().UpdateSite(siteData); err != nil { - return nil, errors.Wrap(err, "unable to configure site") - } + siteData := &data.Site{} + if err := json.Unmarshal(body, siteData); err != nil { + return nil, errors.Wrap(err, "invalid form data") + } + siteData.ID = account.Site + // Configure the sites through the sites manager + if err := siteacc.SitesManager().UpdateSite(siteData); err != nil { + return nil, errors.Wrap(err, "unable to configure sites") + } + */ return nil, nil } @@ -358,8 +361,8 @@ func handleDispatchAlert(siteacc *SiteAccounts, values url.Values, body []byte, return nil, nil } -func handleGrantSiteAccess(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { - return handleGrantAccess((*manager.AccountsManager).GrantSiteAccess, siteacc, values, body, session) +func handleGrantSitesAccess(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { + return handleGrantAccess((*manager.AccountsManager).GrantSitesAccess, siteacc, values, body, session) } func handleGrantGOCDBAccess(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { diff --git a/pkg/siteacc/html/panel.go b/pkg/siteacc/html/panel.go index f4b9fce13b..4409fd49de 100644 --- a/pkg/siteacc/html/panel.go +++ b/pkg/siteacc/html/panel.go @@ -19,6 +19,7 @@ package html import ( + "fmt" "html/template" "net/http" "strings" @@ -149,9 +150,20 @@ func (panel *Panel) prepareTemplate(tpl *template.Template) { "getServerAddress": func() string { return strings.TrimRight(panel.conf.Webserver.URL, "/") }, - "getSiteName": func(siteID string, fullName bool) string { - siteName, _ := data.QuerySiteName(siteID, fullName, panel.conf.Mentix.URL, panel.conf.Mentix.DataEndpoint) - return siteName + "getOperatorName": func(opID string) string { + opName, _ := data.QueryOperatorName(opID, panel.conf.Mentix.URL, panel.conf.Mentix.DataEndpoint) + return opName + }, + "getOperatorSites": func(opID string, fullNames bool) string { + sites, _ := data.QueryOperatorSites(opID, panel.conf.Mentix.URL, panel.conf.Mentix.DataEndpoint) + if fullNames { + for i, s := range sites { + longName, _ := data.QuerySiteName(s, true, panel.conf.Mentix.URL, panel.conf.Mentix.DataEndpoint) + shortName, _ := data.QuerySiteName(s, false, panel.conf.Mentix.URL, panel.conf.Mentix.DataEndpoint) + sites[i] = fmt.Sprintf("%v (%v)", longName, shortName) + } + } + return strings.Join(sites, ", ") }, }) } diff --git a/pkg/siteacc/html/session.go b/pkg/siteacc/html/session.go index 82fc999386..5dfa59955f 100644 --- a/pkg/siteacc/html/session.go +++ b/pkg/siteacc/html/session.go @@ -49,8 +49,8 @@ type Session struct { // SessionUser holds information about the logged in user type SessionUser struct { - Account *data.Account - Site *data.Site + Account *data.Account + Operator *data.Operator } func getRemoteAddress(r *http.Request) string { @@ -68,10 +68,10 @@ func (sess *Session) LoggedInUser() *SessionUser { } // LoginUser logs in the provided user. -func (sess *Session) LoginUser(acc *data.Account, site *data.Site) { +func (sess *Session) LoginUser(acc *data.Account, op *data.Operator) { sess.loggedInUser = &SessionUser{ - Account: acc, - Site: site, + Account: acc, + Operator: op, } } diff --git a/pkg/siteacc/html/sessionmanager.go b/pkg/siteacc/html/sessionmanager.go index 7879c35782..97eaddd5de 100644 --- a/pkg/siteacc/html/sessionmanager.go +++ b/pkg/siteacc/html/sessionmanager.go @@ -160,7 +160,7 @@ func (mngr *SessionManager) migrateSession(session *Session, r *http.Request) (* sessionNew.Data = session.Data if user := session.LoggedInUser(); user != nil { - sessionNew.LoginUser(user.Account, user.Site) + sessionNew.LoginUser(user.Account, user.Operator) } else { sessionNew.LogoutUser() } diff --git a/pkg/siteacc/manager/accmanager.go b/pkg/siteacc/manager/accmanager.go index f4fc30b63b..496586c99e 100644 --- a/pkg/siteacc/manager/accmanager.go +++ b/pkg/siteacc/manager/accmanager.go @@ -38,7 +38,7 @@ const ( FindByEmail = "email" ) -// AccountsManager is responsible for all site account related tasks. +// AccountsManager is responsible for all sites account related tasks. type AccountsManager struct { conf *config.Configuration log *zerolog.Logger @@ -143,7 +143,7 @@ func (mngr *AccountsManager) CreateAccount(accountData *data.Account) error { return errors.Errorf("an account with the specified email address already exists") } - if account, err := data.NewAccount(accountData.Email, accountData.Title, accountData.FirstName, accountData.LastName, accountData.Site, accountData.Role, accountData.PhoneNumber, accountData.Password.Value); err == nil { + if account, err := data.NewAccount(accountData.Email, accountData.Title, accountData.FirstName, accountData.LastName, accountData.Operator, accountData.Role, accountData.PhoneNumber, accountData.Password.Value); err == nil { mngr.accounts = append(mngr.accounts, account) mngr.storage.AccountAdded(account) mngr.writeAllAccounts() @@ -244,8 +244,8 @@ func (mngr *AccountsManager) FindAccountEx(by string, value string, cloneAccount return account, nil } -// GrantSiteAccess sets the Site access status of the account identified by the account email; if no such account exists, an error is returned. -func (mngr *AccountsManager) GrantSiteAccess(accountData *data.Account, grantAccess bool) error { +// GrantSitesAccess sets the Sites access status of the account identified by the account email; if no such account exists, an error is returned. +func (mngr *AccountsManager) GrantSitesAccess(accountData *data.Account, grantAccess bool) error { mngr.mutex.Lock() defer mngr.mutex.Unlock() @@ -254,7 +254,7 @@ func (mngr *AccountsManager) GrantSiteAccess(accountData *data.Account, grantAcc return errors.Wrap(err, "no account with the specified email exists") } - return mngr.grantAccess(account, &account.Data.SiteAccess, grantAccess, email.SendSiteAccessGranted) + return mngr.grantAccess(account, &account.Data.SitesAccess, grantAccess, email.SendSitesAccessGranted) } // GrantGOCDBAccess sets the GOCDB access status of the account identified by the account email; if no such account exists, an error is returned. diff --git a/pkg/siteacc/manager/opsmanager.go b/pkg/siteacc/manager/opsmanager.go new file mode 100644 index 0000000000..c64bba5b0b --- /dev/null +++ b/pkg/siteacc/manager/opsmanager.go @@ -0,0 +1,198 @@ +// Copyright 2018-2022 CERN +// +// 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. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package manager + +import ( + "strings" + "sync" + + "github.com/cs3org/reva/pkg/siteacc/config" + "github.com/cs3org/reva/pkg/siteacc/data" + + "github.com/pkg/errors" + "github.com/rs/zerolog" +) + +// OperatorsManager is responsible for all sites related tasks. +type OperatorsManager struct { + conf *config.Configuration + log *zerolog.Logger + + storage data.Storage + + operators data.Operators + + mutex sync.RWMutex +} + +func (mngr *OperatorsManager) initialize(storage data.Storage, conf *config.Configuration, log *zerolog.Logger) error { + if conf == nil { + return errors.Errorf("no configuration provided") + } + mngr.conf = conf + + if log == nil { + return errors.Errorf("no logger provided") + } + mngr.log = log + + if storage == nil { + return errors.Errorf("no storage provided") + } + mngr.storage = storage + + mngr.operators = make(data.Operators, 0, 32) // Reserve some space for operators + mngr.readAllOperators() + + return nil +} + +func (mngr *OperatorsManager) readAllOperators() { + if ops, err := mngr.storage.ReadOperators(); err == nil { + mngr.operators = *ops + } else { + // Just warn when not being able to read operators + mngr.log.Warn().Err(err).Msg("error while reading operators") + } +} + +func (mngr *OperatorsManager) writeAllOperators() { + if err := mngr.storage.WriteOperators(&mngr.operators); err != nil { + // Just warn when not being able to write operators + mngr.log.Warn().Err(err).Msg("error while writing operators") + } +} + +// GetOperator retrieves the operator with the given ID, creating it first if necessary. +func (mngr *OperatorsManager) GetOperator(id string, clone bool) (*data.Operator, error) { + mngr.mutex.RLock() + defer mngr.mutex.RUnlock() + + op, err := mngr.getOperator(id) + if err != nil { + return nil, err + } + + if clone { + op = op.Clone(false) + } + + return op, nil +} + +// FindOperator returns the operator specified by the ID if one exists. +func (mngr *OperatorsManager) FindOperator(id string) *data.Operator { + op, _ := mngr.findOperator(id) + return op +} + +// FindSite returns the site specified by the ID if one exists. +func (mngr *OperatorsManager) FindSite(id string) (*data.Operator, *data.Site) { + for _, op := range mngr.operators { + for _, site := range op.Sites { + if strings.EqualFold(site.ID, id) { + return op, site + } + } + } + return nil, nil +} + +// UpdateOperator updates the operator identified by the ID; if no such operator exists, one will be created first. +func (mngr *OperatorsManager) UpdateOperator(opData *data.Operator) error { + mngr.mutex.Lock() + defer mngr.mutex.Unlock() + + op, err := mngr.getOperator(opData.ID) + if err != nil { + return errors.Wrap(err, "operator to update not found") + } + + if err := op.Update(opData); err == nil { + mngr.storage.OperatorUpdated(op) + mngr.writeAllOperators() + } else { + return errors.Wrap(err, "error while updating operator") + } + + return nil +} + +// CloneOperators retrieves all operators currently stored by cloning the data, thus avoiding race conflicts and making outside modifications impossible. +func (mngr *OperatorsManager) CloneOperators(eraseCredentials bool) data.Operators { + mngr.mutex.RLock() + defer mngr.mutex.RUnlock() + + clones := make(data.Operators, 0, len(mngr.operators)) + for _, op := range mngr.operators { + clones = append(clones, op.Clone(eraseCredentials)) + } + + return clones +} + +func (mngr *OperatorsManager) getOperator(id string) (*data.Operator, error) { + op, err := mngr.findOperator(id) + if op == nil { + op, err = mngr.createOperator(id) + } + return op, err +} + +func (mngr *OperatorsManager) createOperator(id string) (*data.Operator, error) { + op, err := data.NewOperator(id) + if err != nil { + return nil, errors.Wrap(err, "error while creating operator") + } + mngr.operators = append(mngr.operators, op) + mngr.storage.OperatorAdded(op) + mngr.writeAllOperators() + return op, nil +} + +func (mngr *OperatorsManager) findOperator(id string) (*data.Operator, error) { + if len(id) == 0 { + return nil, errors.Errorf("no search ID specified") + } + + op := mngr.findOperatorByPredicate(func(op *data.Operator) bool { return strings.EqualFold(op.ID, id) }) + if op != nil { + return op, nil + } + + return nil, errors.Errorf("no operator found matching the specified ID") +} + +func (mngr *OperatorsManager) findOperatorByPredicate(predicate func(operator *data.Operator) bool) *data.Operator { + for _, op := range mngr.operators { + if predicate(op) { + return op + } + } + return nil +} + +// NewOperatorsManager creates a new operators manager instance. +func NewOperatorsManager(storage data.Storage, conf *config.Configuration, log *zerolog.Logger) (*OperatorsManager, error) { + mngr := &OperatorsManager{} + if err := mngr.initialize(storage, conf, log); err != nil { + return nil, errors.Wrap(err, "unable to initialize the operators manager") + } + return mngr, nil +} diff --git a/pkg/siteacc/manager/sitesmanager.go b/pkg/siteacc/manager/sitesmanager.go deleted file mode 100644 index 0d5a4eeb8f..0000000000 --- a/pkg/siteacc/manager/sitesmanager.go +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2018-2020 CERN -// -// 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. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package manager - -import ( - "strings" - "sync" - - "github.com/cs3org/reva/pkg/siteacc/config" - "github.com/cs3org/reva/pkg/siteacc/data" - "github.com/pkg/errors" - "github.com/rs/zerolog" -) - -// SitesManager is responsible for all sites related tasks. -type SitesManager struct { - conf *config.Configuration - log *zerolog.Logger - - storage data.Storage - - sites data.Sites - - mutex sync.RWMutex -} - -func (mngr *SitesManager) initialize(storage data.Storage, conf *config.Configuration, log *zerolog.Logger) error { - if conf == nil { - return errors.Errorf("no configuration provided") - } - mngr.conf = conf - - if log == nil { - return errors.Errorf("no logger provided") - } - mngr.log = log - - if storage == nil { - return errors.Errorf("no storage provided") - } - mngr.storage = storage - - mngr.sites = make(data.Sites, 0, 32) // Reserve some space for sites - mngr.readAllSites() - - return nil -} - -func (mngr *SitesManager) readAllSites() { - if sites, err := mngr.storage.ReadSites(); err == nil { - mngr.sites = *sites - } else { - // Just warn when not being able to read sites - mngr.log.Warn().Err(err).Msg("error while reading sites") - } -} - -func (mngr *SitesManager) writeAllSites() { - if err := mngr.storage.WriteSites(&mngr.sites); err != nil { - // Just warn when not being able to write sites - mngr.log.Warn().Err(err).Msg("error while writing sites") - } -} - -// GetSite retrieves the site with the given ID, creating it first if necessary. -func (mngr *SitesManager) GetSite(id string, cloneSite bool) (*data.Site, error) { - mngr.mutex.RLock() - defer mngr.mutex.RUnlock() - - site, err := mngr.getSite(id) - if err != nil { - return nil, err - } - - if cloneSite { - site = site.Clone(false) - } - - return site, nil -} - -// FindSite returns the site specified by the ID if one exists. -func (mngr *SitesManager) FindSite(id string) *data.Site { - site, _ := mngr.findSite(id) - return site -} - -// UpdateSite updates the site identified by the site ID; if no such site exists, one will be created first. -func (mngr *SitesManager) UpdateSite(siteData *data.Site) error { - mngr.mutex.Lock() - defer mngr.mutex.Unlock() - - site, err := mngr.getSite(siteData.ID) - if err != nil { - return errors.Wrap(err, "site to update not found") - } - - if err := site.Update(siteData, mngr.conf.Security.CredentialsPassphrase); err == nil { - mngr.storage.SiteUpdated(site) - mngr.writeAllSites() - } else { - return errors.Wrap(err, "error while updating site") - } - - return nil -} - -// CloneSites retrieves all sites currently stored by cloning the data, thus avoiding race conflicts and making outside modifications impossible. -func (mngr *SitesManager) CloneSites(eraseCredentials bool) data.Sites { - mngr.mutex.RLock() - defer mngr.mutex.RUnlock() - - clones := make(data.Sites, 0, len(mngr.sites)) - for _, site := range mngr.sites { - clones = append(clones, site.Clone(eraseCredentials)) - } - - return clones -} - -func (mngr *SitesManager) getSite(id string) (*data.Site, error) { - site, err := mngr.findSite(id) - if site == nil { - site, err = mngr.createSite(id) - } - return site, err -} - -func (mngr *SitesManager) createSite(id string) (*data.Site, error) { - site, err := data.NewSite(id) - if err != nil { - return nil, errors.Wrap(err, "error while creating site") - } - mngr.sites = append(mngr.sites, site) - mngr.storage.SiteAdded(site) - mngr.writeAllSites() - return site, nil -} - -func (mngr *SitesManager) findSite(id string) (*data.Site, error) { - if len(id) == 0 { - return nil, errors.Errorf("no search ID specified") - } - - site := mngr.findSiteByPredicate(func(site *data.Site) bool { return strings.EqualFold(site.ID, id) }) - if site != nil { - return site, nil - } - - return nil, errors.Errorf("no site found matching the specified ID") -} - -func (mngr *SitesManager) findSiteByPredicate(predicate func(*data.Site) bool) *data.Site { - for _, site := range mngr.sites { - if predicate(site) { - return site - } - } - return nil -} - -// NewSitesManager creates a new sites manager instance. -func NewSitesManager(storage data.Storage, conf *config.Configuration, log *zerolog.Logger) (*SitesManager, error) { - mngr := &SitesManager{} - if err := mngr.initialize(storage, conf, log); err != nil { - return nil, errors.Wrap(err, "unable to initialize the sites manager") - } - return mngr, nil -} diff --git a/pkg/siteacc/manager/usersmanager.go b/pkg/siteacc/manager/usersmanager.go index 4bee439f9d..899e60a8f0 100644 --- a/pkg/siteacc/manager/usersmanager.go +++ b/pkg/siteacc/manager/usersmanager.go @@ -32,15 +32,15 @@ type UsersManager struct { conf *config.Configuration log *zerolog.Logger - sitesManager *SitesManager - accountsManager *AccountsManager + operatorsManager *OperatorsManager + accountsManager *AccountsManager } const ( defaultPasswordLength = 12 ) -func (mngr *UsersManager) initialize(conf *config.Configuration, log *zerolog.Logger, sitesManager *SitesManager, accountsManager *AccountsManager) error { +func (mngr *UsersManager) initialize(conf *config.Configuration, log *zerolog.Logger, opsManager *OperatorsManager, accountsManager *AccountsManager) error { if conf == nil { return errors.Errorf("no configuration provided") } @@ -51,10 +51,10 @@ func (mngr *UsersManager) initialize(conf *config.Configuration, log *zerolog.Lo } mngr.log = log - if sitesManager == nil { - return errors.Errorf("no sites manager provided") + if opsManager == nil { + return errors.Errorf("no operators manager provided") } - mngr.sitesManager = sitesManager + mngr.operatorsManager = opsManager if accountsManager == nil { return errors.Errorf("no accounts manager provided") @@ -81,14 +81,14 @@ func (mngr *UsersManager) LoginUser(name, password string, scope string, session return "", errors.Errorf("no access to the specified scope granted") } - // Get the site the account belongs to - site, err := mngr.sitesManager.GetSite(account.Site, false) + // Get the sites the account belongs to + op, err := mngr.operatorsManager.GetOperator(account.Operator, false) if err != nil { - return "", errors.Wrap(err, "no site with the specified ID exists") + return "", errors.Wrap(err, "no operator with the specified ID exists") } // Store the user account in the session - session.LoginUser(account, site) + session.LoginUser(account, op) // Generate a token that can be used as a "ticket" token, err := generateUserToken(session.LoggedInUser().Account.Email, scope, mngr.conf.Webserver.SessionTimeout) @@ -141,9 +141,9 @@ func (mngr *UsersManager) VerifyUserToken(token string, user string, scope strin } // NewUsersManager creates a new users manager instance. -func NewUsersManager(conf *config.Configuration, log *zerolog.Logger, sitesManager *SitesManager, accountsManager *AccountsManager) (*UsersManager, error) { +func NewUsersManager(conf *config.Configuration, log *zerolog.Logger, opsManager *OperatorsManager, accountsManager *AccountsManager) (*UsersManager, error) { mngr := &UsersManager{} - if err := mngr.initialize(conf, log, sitesManager, accountsManager); err != nil { + if err := mngr.initialize(conf, log, opsManager, accountsManager); err != nil { return nil, errors.Wrap(err, "unable to initialize the users manager") } return mngr, nil diff --git a/pkg/siteacc/siteacc.go b/pkg/siteacc/siteacc.go index ffa7bff7e6..6bfcc6461a 100644 --- a/pkg/siteacc/siteacc.go +++ b/pkg/siteacc/siteacc.go @@ -42,9 +42,9 @@ type SiteAccounts struct { storage data.Storage - sitesManager *manager.SitesManager - accountsManager *manager.AccountsManager - usersManager *manager.UsersManager + operatorsManager *manager.OperatorsManager + accountsManager *manager.AccountsManager + usersManager *manager.UsersManager alertsDispatcher *alerting.Dispatcher @@ -78,11 +78,11 @@ func (siteacc *SiteAccounts) initialize(conf *config.Configuration, log *zerolog siteacc.storage = storage // Create the sites manager instance - smngr, err := manager.NewSitesManager(storage, conf, log) + omngr, err := manager.NewOperatorsManager(storage, conf, log) if err != nil { - return errors.Wrap(err, "error creating the sites manager") + return errors.Wrap(err, "error creating the operators manager") } - siteacc.sitesManager = smngr + siteacc.operatorsManager = omngr // Create the accounts manager instance amngr, err := manager.NewAccountsManager(storage, conf, log) @@ -92,7 +92,7 @@ func (siteacc *SiteAccounts) initialize(conf *config.Configuration, log *zerolog siteacc.accountsManager = amngr // Create the users manager instance - umngr, err := manager.NewUsersManager(conf, log, siteacc.sitesManager, siteacc.accountsManager) + umngr, err := manager.NewUsersManager(conf, log, siteacc.operatorsManager, siteacc.accountsManager) if err != nil { return errors.Wrap(err, "error creating the users manager") } @@ -162,9 +162,9 @@ func (siteacc *SiteAccounts) ShowAccountPanel(w http.ResponseWriter, r *http.Req return siteacc.accountPanel.Execute(w, r, session) } -// SitesManager returns the central sites manager instance. -func (siteacc *SiteAccounts) SitesManager() *manager.SitesManager { - return siteacc.sitesManager +// OperatorsManager returns the central operators manager instance. +func (siteacc *SiteAccounts) OperatorsManager() *manager.OperatorsManager { + return siteacc.operatorsManager } // AccountsManager returns the central accounts manager instance. @@ -185,7 +185,7 @@ func (siteacc *SiteAccounts) AlertsDispatcher() *alerting.Dispatcher { // GetPublicEndpoints returns a list of all public endpoints. func (siteacc *SiteAccounts) GetPublicEndpoints() []string { // TODO: Only for local testing! - // return []string{"/"} + return []string{"/"} endpoints := make([]string, 0, 5) for _, ep := range getEndpoints() { @@ -209,7 +209,7 @@ func New(conf *config.Configuration, log *zerolog.Logger) (*SiteAccounts, error) // Configure the accounts service siteacc := new(SiteAccounts) if err := siteacc.initialize(conf, log); err != nil { - return nil, fmt.Errorf("unable to initialize site accounts: %v", err) + return nil, fmt.Errorf("unable to initialize sites accounts: %v", err) } return siteacc, nil } From cf9d26fb8dd56bc8347d798dc2e0a063c234b499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Mon, 4 Jul 2022 15:40:26 +0200 Subject: [PATCH 04/14] Small fixes --- pkg/siteacc/account/manage/template.go | 10 +++++++--- pkg/siteacc/account/registration/template.go | 2 +- pkg/siteacc/admin/template.go | 6 +++++- pkg/siteacc/html/panel.go | 8 +++++--- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/pkg/siteacc/account/manage/template.go b/pkg/siteacc/account/manage/template.go index 1c71f77bf9..d64cf13260 100644 --- a/pkg/siteacc/account/manage/template.go +++ b/pkg/siteacc/account/manage/template.go @@ -79,7 +79,11 @@ const tplBody = `
    • Name: {{.Account.Title}}. {{.Account.FirstName}} {{.Account.LastName}}
    • Email: {{.Account.Email}}
    • -
    • ScienceMesh Operator: {{getOperatorName .Account.Operator}} ({{getOperatorSites .Account.Operator true}})
    • +
    • + ScienceMesh Operator: {{getOperatorName .Account.Operator}} +
      + Sites: {{getOperatorSites .Account.Operator true}} +
    • Role: {{.Account.Role}}
    • {{if .Account.PhoneNumber}}
    • Phone: {{.Account.PhoneNumber}}
    • @@ -100,8 +104,8 @@ const tplBody = `   - {{if .Account.Data.SiteAccess}} - + {{if .Account.Data.SitesAccess}} +   {{end}} diff --git a/pkg/siteacc/account/registration/template.go b/pkg/siteacc/account/registration/template.go index 43a3075eda..57832eb82c 100644 --- a/pkg/siteacc/account/registration/template.go +++ b/pkg/siteacc/account/registration/template.go @@ -128,7 +128,7 @@ const tplBody = `
      diff --git a/pkg/siteacc/admin/template.go b/pkg/siteacc/admin/template.go index 75b4d74eee..4fa70eeae7 100644 --- a/pkg/siteacc/admin/template.go +++ b/pkg/siteacc/admin/template.go @@ -61,7 +61,11 @@ const tplBody = `
      -
    • ScienceMesh Operator: {{getOperatorName .Operator}} ({{getOperatorSites .Operator true}})
    • +
    • + ScienceMesh Operator: {{getOperatorName .Operator}} +
      + Sites: {{getOperatorSites .Operator true}} +
    • Role: {{.Role}}
    • Phone: {{.PhoneNumber}}
    diff --git a/pkg/siteacc/html/panel.go b/pkg/siteacc/html/panel.go index 4409fd49de..260d5fd16d 100644 --- a/pkg/siteacc/html/panel.go +++ b/pkg/siteacc/html/panel.go @@ -156,11 +156,13 @@ func (panel *Panel) prepareTemplate(tpl *template.Template) { }, "getOperatorSites": func(opID string, fullNames bool) string { sites, _ := data.QueryOperatorSites(opID, panel.conf.Mentix.URL, panel.conf.Mentix.DataEndpoint) - if fullNames { - for i, s := range sites { - longName, _ := data.QuerySiteName(s, true, panel.conf.Mentix.URL, panel.conf.Mentix.DataEndpoint) + for i, s := range sites { + longName, _ := data.QuerySiteName(s, true, panel.conf.Mentix.URL, panel.conf.Mentix.DataEndpoint) + if fullNames { shortName, _ := data.QuerySiteName(s, false, panel.conf.Mentix.URL, panel.conf.Mentix.DataEndpoint) sites[i] = fmt.Sprintf("%v (%v)", longName, shortName) + } else { + sites[i] = longName } } return strings.Join(sites, ", ") From 429533d1f3db051913daf89194c3dd60e05cb8c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Mon, 4 Jul 2022 16:03:47 +0200 Subject: [PATCH 05/14] Template improvements --- pkg/siteacc/account/manage/template.go | 2 +- pkg/siteacc/account/panel.go | 1 + pkg/siteacc/account/sites/template.go | 18 ++++++++++-------- pkg/siteacc/admin/template.go | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/pkg/siteacc/account/manage/template.go b/pkg/siteacc/account/manage/template.go index d64cf13260..7eb598b730 100644 --- a/pkg/siteacc/account/manage/template.go +++ b/pkg/siteacc/account/manage/template.go @@ -82,7 +82,7 @@ const tplBody = `
  • ScienceMesh Operator: {{getOperatorName .Account.Operator}}
    - Sites: {{getOperatorSites .Account.Operator true}} + {{getOperatorSites .Account.Operator true}}
  • Role: {{.Account.Role}}
  • {{if .Account.PhoneNumber}} diff --git a/pkg/siteacc/account/panel.go b/pkg/siteacc/account/panel.go index b0c3f286db..e7d9d963a8 100644 --- a/pkg/siteacc/account/panel.go +++ b/pkg/siteacc/account/panel.go @@ -204,6 +204,7 @@ func (panel *Panel) redirect(path string, w http.ResponseWriter, r *http.Request func (panel *Panel) cloneUserOperator(op *data.Operator) *data.Operator { // Clone the user's operator and decrypt all credentials for the panel + // TODO: Fetch sites?! opClone := op.Clone(true) for _, site := range opClone.Sites { id, secret, err := site.Config.TestClientCredentials.Get(panel.conf.Security.CredentialsPassphrase) diff --git a/pkg/siteacc/account/sites/template.go b/pkg/siteacc/account/sites/template.go index 6e9e61da11..70b3c64447 100644 --- a/pkg/siteacc/account/sites/template.go +++ b/pkg/siteacc/account/sites/template.go @@ -84,23 +84,25 @@ input[type="checkbox"] { const tplBody = `
    -

    Configure your ScienceMesh Site below. These settings affect your entire sites and not just your account.

    +

    Configure your ScienceMesh Sites below. These settings affect the entire sites and not just your account.

     

    Test user settings

    -

    In order to perform automated tests on your sites, a test user has to be configured below. Please note that the user has to exist in your Reva instance! If you do not have a user for automated tests in your instance yet, create one first.

    +

    In order to perform automated tests on your sites, a test user has to be configured below for each site. Please note that the users have to exist in your respective Reva instances! If you do not have users for automated tests in your instances yet, create them first.


    -
    -
    -
    -
    - -
     
    + {{range .Account.Operator.Sites}} +
    +
    +
    +
    + +
     
    + {{end}}
    Fields marked with * are mandatory. diff --git a/pkg/siteacc/admin/template.go b/pkg/siteacc/admin/template.go index 4fa70eeae7..c9aa4a831c 100644 --- a/pkg/siteacc/admin/template.go +++ b/pkg/siteacc/admin/template.go @@ -64,7 +64,7 @@ const tplBody = `
  • ScienceMesh Operator: {{getOperatorName .Operator}}
    - Sites: {{getOperatorSites .Operator true}} + {{getOperatorSites .Operator true}}
  • Role: {{.Role}}
  • Phone: {{.PhoneNumber}}
  • From 2b26b17d2ec50525847c67e8980005701f056818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Thu, 7 Jul 2022 11:51:09 +0200 Subject: [PATCH 06/14] Update sites configuration --- pkg/siteacc/account/panel.go | 53 ++++++++++++++++++++++--- pkg/siteacc/account/sites/template.go | 56 ++++++++++++++++++--------- pkg/siteacc/data/operator.go | 15 ++++++- pkg/siteacc/endpoints.go | 41 ++++++++++---------- pkg/siteacc/html/panel.go | 3 ++ pkg/siteacc/manager/opsmanager.go | 2 +- 6 files changed, 123 insertions(+), 47 deletions(-) diff --git a/pkg/siteacc/account/panel.go b/pkg/siteacc/account/panel.go index e7d9d963a8..db66cf7849 100644 --- a/pkg/siteacc/account/panel.go +++ b/pkg/siteacc/account/panel.go @@ -164,20 +164,28 @@ func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, session *htm Account *data.Account Params map[string]string - Titles []string Operators []data.OperatorInformation + Sites map[string]string + Titles []string } tplData := TemplateData{ Operator: nil, Account: nil, Params: flatValues, - Titles: []string{"Mr", "Mrs", "Ms", "Prof", "Dr"}, Operators: availOps, + Sites: make(map[string]string, 10), + Titles: []string{"Mr", "Mrs", "Ms", "Prof", "Dr"}, } if user := session.LoggedInUser(); user != nil { - tplData.Operator = panel.cloneUserOperator(user.Operator) + availSites, err := panel.fetchAvailableSites(user.Operator) + if err != nil { + return errors.Wrap(err, "unable to query available sites") + } + + tplData.Operator = panel.cloneUserOperator(user.Operator, availSites) tplData.Account = user.Account + tplData.Sites = availSites } return tplData } @@ -202,10 +210,25 @@ func (panel *Panel) redirect(path string, w http.ResponseWriter, r *http.Request return html.AbortExecution } -func (panel *Panel) cloneUserOperator(op *data.Operator) *data.Operator { +func (panel *Panel) fetchAvailableSites(op *data.Operator) (map[string]string, error) { + ids, err := data.QueryOperatorSites(op.ID, panel.conf.Mentix.URL, panel.conf.Mentix.DataEndpoint) + if err != nil { + return nil, err + } + sites := make(map[string]string, 10) + for _, id := range ids { + if siteName, _ := data.QuerySiteName(id, true, panel.conf.Mentix.URL, panel.conf.Mentix.DataEndpoint); err == nil { + sites[id] = siteName + } else { + sites[id] = id + } + } + return sites, nil +} + +func (panel *Panel) cloneUserOperator(op *data.Operator, sites map[string]string) *data.Operator { // Clone the user's operator and decrypt all credentials for the panel - // TODO: Fetch sites?! - opClone := op.Clone(true) + opClone := op.Clone(false) for _, site := range opClone.Sites { id, secret, err := site.Config.TestClientCredentials.Get(panel.conf.Security.CredentialsPassphrase) if err == nil { @@ -213,6 +236,24 @@ func (panel *Panel) cloneUserOperator(op *data.Operator) *data.Operator { site.Config.TestClientCredentials.Secret = secret } } + + // Add missing sites + for id, _ := range sites { + siteFound := false + for _, site := range opClone.Sites { + if strings.EqualFold(site.ID, id) { + siteFound = true + break + } + } + if !siteFound { + opClone.Sites = append(opClone.Sites, &data.Site{ + ID: id, + Config: data.SiteConfiguration{}, + }) + } + } + return opClone } diff --git a/pkg/siteacc/account/sites/template.go b/pkg/siteacc/account/sites/template.go index 70b3c64447..e390dcf153 100644 --- a/pkg/siteacc/account/sites/template.go +++ b/pkg/siteacc/account/sites/template.go @@ -20,15 +20,21 @@ package sites const tplJavaScript = ` function verifyForm(formData) { - if (formData.getTrimmed("clientID") == "") { - setState(STATE_ERROR, "Please enter the name of the test user.", "form", "clientID", true); + {{$parent := .}} + {{range .Operator.Sites}} + {{$site := index $parent.Sites .ID}} + {{$clientID := print "clientID-" .ID}} + if (formData.getTrimmed("{{$clientID}}") == "") { + setState(STATE_ERROR, "Please enter the name of the test user for site {{$site}}.", "form", "{{$clientID}}", true); return false; } - if (formData.get("secret") == "") { - setState(STATE_ERROR, "Please enter the password of the test user.", "form", "secret", true); + {{$secret := print "secret-" .ID}} + if (formData.get("{{$secret}}") == "") { + setState(STATE_ERROR, "Please enter the password of the test user for site {{$site}}.", "form", "{{$secret}}", true); return false; } + {{end}} return true; } @@ -54,14 +60,19 @@ function handleAction(action) { } } - var postData = { - "config": { - "testClientCredentials": { - "id": formData.getTrimmed("clientID"), - "secret": formData.get("secret") + var postData = [ + {{range .Operator.Sites}} + { + "id": "{{.ID}}", + "config": { + "testClientCredentials": { + "id": formData.getTrimmed({{print "clientID-" .ID}}), + "secret": formData.get({{print "secret-" .ID}}) + } } - } - }; + }, + {{end}} + ]; xhr.send(JSON.stringify(postData)); } @@ -95,19 +106,26 @@ const tplBody = `
    - {{range .Account.Operator.Sites}} -
    -
    -
    -
    + {{$row := 2}}{{$parent := .}} + {{range $index, $elem := .Operator.Sites}} +
    {{index $parent.Sites .ID}} ({{.ID}})
    + + {{$clientID := print "clientID-" .ID}} +
    +
    + {{$secret := print "secret-" .ID}} +
    +
    -
     
    +
     
    + + {{$row = add $row 4}} {{end}} -
    +
    Fields marked with * are mandatory.
    -
    +
    diff --git a/pkg/siteacc/data/operator.go b/pkg/siteacc/data/operator.go index b5eb6a52d5..b75a47ae7f 100644 --- a/pkg/siteacc/data/operator.go +++ b/pkg/siteacc/data/operator.go @@ -18,6 +18,10 @@ package data +import ( + "github.com/pkg/errors" +) + // Operator represents the global operator-specific settings stored in the service. type Operator struct { ID string `json:"id"` @@ -29,7 +33,16 @@ type Operator struct { type Operators = []*Operator // Update copies the data of the given operator to this operator. -func (op *Operator) Update(other *Operator) error { +func (op *Operator) Update(other *Operator, credsPassphrase string) error { + // Clear currently stored sites and clone over the new ones + op.Sites = make([]*Site, 0, len(other.Sites)) + for _, otherSite := range other.Sites { + site := otherSite.Clone(true) + if err := site.Update(otherSite, credsPassphrase); err != nil { + return errors.Wrapf(err, "unable to update site %v", site.ID) + } + op.Sites = append(op.Sites, site) + } return nil } diff --git a/pkg/siteacc/endpoints.go b/pkg/siteacc/endpoints.go index 9d1a005dac..da02f21d45 100644 --- a/pkg/siteacc/endpoints.go +++ b/pkg/siteacc/endpoints.go @@ -248,28 +248,29 @@ func handleSiteGet(siteacc *SiteAccounts, values url.Values, body []byte, sessio } func handleSitesConfigure(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { - // TODO: - /* - email, _, err := processInvoker(siteacc, values, session) - if err != nil { - return nil, err - } - account, err := siteacc.AccountsManager().FindAccount(manager.FindByEmail, email) - if err != nil { - return nil, err - } + email, _, err := processInvoker(siteacc, values, session) + if err != nil { + return nil, err + } + account, err := siteacc.AccountsManager().FindAccount(manager.FindByEmail, email) + if err != nil { + return nil, err + } - siteData := &data.Site{} - if err := json.Unmarshal(body, siteData); err != nil { - return nil, errors.Wrap(err, "invalid form data") - } - siteData.ID = account.Site + sitesData := &[]*data.Site{} + if err := json.Unmarshal(body, sitesData); err != nil { + return nil, errors.Wrap(err, "invalid form data") + } + + // Configure the sites through the operators manager + opData := &data.Operator{ + ID: account.Operator, + Sites: *sitesData, + } + if err := siteacc.OperatorsManager().UpdateOperator(opData); err != nil { + return nil, errors.Wrap(err, "unable to configure operator") + } - // Configure the sites through the sites manager - if err := siteacc.SitesManager().UpdateSite(siteData); err != nil { - return nil, errors.Wrap(err, "unable to configure sites") - } - */ return nil, nil } diff --git a/pkg/siteacc/html/panel.go b/pkg/siteacc/html/panel.go index 260d5fd16d..3a5376bf91 100644 --- a/pkg/siteacc/html/panel.go +++ b/pkg/siteacc/html/panel.go @@ -147,6 +147,9 @@ func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, session *Ses func (panel *Panel) prepareTemplate(tpl *template.Template) { // Add some custom helper functions to the template tpl.Funcs(template.FuncMap{ + "add": func(x, y int) int { + return x + y + }, "getServerAddress": func() string { return strings.TrimRight(panel.conf.Webserver.URL, "/") }, diff --git a/pkg/siteacc/manager/opsmanager.go b/pkg/siteacc/manager/opsmanager.go index c64bba5b0b..e0335e377e 100644 --- a/pkg/siteacc/manager/opsmanager.go +++ b/pkg/siteacc/manager/opsmanager.go @@ -124,7 +124,7 @@ func (mngr *OperatorsManager) UpdateOperator(opData *data.Operator) error { return errors.Wrap(err, "operator to update not found") } - if err := op.Update(opData); err == nil { + if err := op.Update(opData, mngr.conf.Security.CredentialsPassphrase); err == nil { mngr.storage.OperatorUpdated(op) mngr.writeAllOperators() } else { From d640b7c04896a2c048f050bbb83889412077b162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Thu, 7 Jul 2022 12:57:16 +0200 Subject: [PATCH 07/14] Remove test code --- pkg/siteacc/siteacc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/siteacc/siteacc.go b/pkg/siteacc/siteacc.go index 6bfcc6461a..f099f4dac5 100644 --- a/pkg/siteacc/siteacc.go +++ b/pkg/siteacc/siteacc.go @@ -185,7 +185,7 @@ func (siteacc *SiteAccounts) AlertsDispatcher() *alerting.Dispatcher { // GetPublicEndpoints returns a list of all public endpoints. func (siteacc *SiteAccounts) GetPublicEndpoints() []string { // TODO: Only for local testing! - return []string{"/"} + // return []string{"/"} endpoints := make([]string, 0, 5) for _, ep := range getEndpoints() { From b15f82ffb1cd3acd73688fb457eada4baae71bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Thu, 7 Jul 2022 13:50:46 +0200 Subject: [PATCH 08/14] Include operator in alerting --- pkg/siteacc/alerting/dispatcher.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pkg/siteacc/alerting/dispatcher.go b/pkg/siteacc/alerting/dispatcher.go index 26700c4449..57e2dd7670 100644 --- a/pkg/siteacc/alerting/dispatcher.go +++ b/pkg/siteacc/alerting/dispatcher.go @@ -102,13 +102,15 @@ func (dispatcher *Dispatcher) dispatchAlert(alert template.Alert, account *data. "EndDate": alert.EndsAt.String(), "Fingerprint": alert.Fingerprint, - "Name": alert.Labels["alertname"], - "Service": alert.Labels["service_type"], - "Instance": alert.Labels["instance"], - "Job": alert.Labels["job"], - "Severity": alert.Labels["severity"], - "Site": alert.Labels["sites"], - "SiteID": alert.Labels["site_id"], + "Name": alert.Labels["alertname"], + "Service": alert.Labels["service_type"], + "Instance": alert.Labels["instance"], + "Job": alert.Labels["job"], + "Severity": alert.Labels["severity"], + "Operator": alert.Labels["operator"], + "OperatorID": alert.Labels["operator_id"], + "Site": alert.Labels["sites"], + "SiteID": alert.Labels["site_id"], "Description": alert.Annotations["description"], "Summary": alert.Annotations["summary"], From 45c02265004f92c11d34c57c4a1858637e4e4c93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Mon, 11 Jul 2022 10:05:14 +0200 Subject: [PATCH 09/14] Fix sites label in alerts --- pkg/siteacc/alerting/dispatcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/siteacc/alerting/dispatcher.go b/pkg/siteacc/alerting/dispatcher.go index 57e2dd7670..46ec1f44b4 100644 --- a/pkg/siteacc/alerting/dispatcher.go +++ b/pkg/siteacc/alerting/dispatcher.go @@ -109,7 +109,7 @@ func (dispatcher *Dispatcher) dispatchAlert(alert template.Alert, account *data. "Severity": alert.Labels["severity"], "Operator": alert.Labels["operator"], "OperatorID": alert.Labels["operator_id"], - "Site": alert.Labels["sites"], + "Site": alert.Labels["site"], "SiteID": alert.Labels["site_id"], "Description": alert.Annotations["description"], From a186baa500201689c2fe9c0559d59eb8f8863d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Thu, 14 Jul 2022 09:23:36 +0200 Subject: [PATCH 10/14] Add changelog --- changelog/unreleased/mesh-metadata-ops.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/unreleased/mesh-metadata-ops.md diff --git a/changelog/unreleased/mesh-metadata-ops.md b/changelog/unreleased/mesh-metadata-ops.md new file mode 100644 index 0000000000..c94c93a50a --- /dev/null +++ b/changelog/unreleased/mesh-metadata-ops.md @@ -0,0 +1,3 @@ +Enhancement: To better support sites that run multiple instances, the meta data have been extended to include a new hierarchy layer called 'operators'. This PR brings all necessary changes in the Mentix and site accounts services. + +https://github.com/cs3org/reva/pull/3072 From e4763464717365db7539588f9b5a265614e5f81e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Thu, 14 Jul 2022 09:25:06 +0200 Subject: [PATCH 11/14] Hound fixes --- pkg/siteacc/account/panel.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/siteacc/account/panel.go b/pkg/siteacc/account/panel.go index db66cf7849..a1d27ac45b 100644 --- a/pkg/siteacc/account/panel.go +++ b/pkg/siteacc/account/panel.go @@ -238,7 +238,7 @@ func (panel *Panel) cloneUserOperator(op *data.Operator, sites map[string]string } // Add missing sites - for id, _ := range sites { + for id := range sites { siteFound := false for _, site := range opClone.Sites { if strings.EqualFold(site.ID, id) { From 6d0ddf1f074c3cb5f12146e627223dc160fc4514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Thu, 14 Jul 2022 09:27:03 +0200 Subject: [PATCH 12/14] Fix changelog --- changelog/unreleased/mesh-metadata-ops.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog/unreleased/mesh-metadata-ops.md b/changelog/unreleased/mesh-metadata-ops.md index c94c93a50a..98db51062f 100644 --- a/changelog/unreleased/mesh-metadata-ops.md +++ b/changelog/unreleased/mesh-metadata-ops.md @@ -1,3 +1,5 @@ -Enhancement: To better support sites that run multiple instances, the meta data have been extended to include a new hierarchy layer called 'operators'. This PR brings all necessary changes in the Mentix and site accounts services. +Enhancement: Mesh meta data operators + +To better support sites that run multiple instances, the meta data have been extended to include a new hierarchy layer called 'operators'. This PR brings all necessary changes in the Mentix and site accounts services. https://github.com/cs3org/reva/pull/3072 From 2826fe0c5ff849a2ff93eb5003fdc27a6dec2e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Thu, 14 Jul 2022 10:34:55 +0200 Subject: [PATCH 13/14] Add recycle bin ops to the SDK --- pkg/sdk/action/fileops.go | 10 ++++++ pkg/sdk/action/recycleops.go | 69 ++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 pkg/sdk/action/recycleops.go diff --git a/pkg/sdk/action/fileops.go b/pkg/sdk/action/fileops.go index 06a3ad83a8..1e85171ccb 100644 --- a/pkg/sdk/action/fileops.go +++ b/pkg/sdk/action/fileops.go @@ -35,6 +35,16 @@ type FileOperationsAction struct { action } +// GetHome retrieves the home directory path of the current user. +func (action *FileOperationsAction) GetHome() (string, error) { + req := &provider.GetHomeRequest{} + res, err := action.session.Client().GetHome(action.session.Context(), req) + if err := net.CheckRPCInvocation("querying home directory", res, err); err != nil { + return "", err + } + return res.Path, nil +} + // Stat queries the file information of the specified remote resource. func (action *FileOperationsAction) Stat(path string) (*storage.ResourceInfo, error) { ref := &provider.Reference{Path: path} diff --git a/pkg/sdk/action/recycleops.go b/pkg/sdk/action/recycleops.go new file mode 100644 index 0000000000..a12bdf9738 --- /dev/null +++ b/pkg/sdk/action/recycleops.go @@ -0,0 +1,69 @@ +// Copyright 2018-2022 CERN +// +// 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. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package action + +import ( + "fmt" + + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/sdk" + "github.com/cs3org/reva/pkg/sdk/common/net" +) + +// RecycleOperationsAction offers recycle bin operations. +type RecycleOperationsAction struct { + action +} + +// Purge purges the entire recycle bin of the current user. +func (action *RecycleOperationsAction) Purge() error { + // Get the home directory to purge the entire recycle bin + fileOpsAct := MustNewFileOperationsAction(action.session) + homePath, err := fileOpsAct.GetHome() + if err != nil { + return err + } + + // Send purge request + ref := &provider.Reference{Path: homePath} + req := &provider.PurgeRecycleRequest{Ref: ref} + res, err := action.session.Client().PurgeRecycle(action.session.Context(), req) + if err := net.CheckRPCInvocation("purging recycle bin", res, err); err != nil { + return err + } + return nil +} + +// NewRecycleOperationsAction creates a new recycle operations action. +func NewRecycleOperationsAction(session *sdk.Session) (*RecycleOperationsAction, error) { + action := &RecycleOperationsAction{} + if err := action.initAction(session); err != nil { + return nil, fmt.Errorf("unable to create the RecycleOperationsAction: %v", err) + } + return action, nil +} + +// MustNewRecycleOperationsAction creates a new recycle operations action and panics on failure. +func MustNewRecycleOperationsAction(session *sdk.Session) *RecycleOperationsAction { + action, err := NewRecycleOperationsAction(session) + if err != nil { + panic(err) + } + return action +} From a943357c6295ae1eb7f4557915a56657fff125fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Thu, 14 Jul 2022 10:46:22 +0200 Subject: [PATCH 14/14] Cleanup --- pkg/sdk/action/recycleops.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pkg/sdk/action/recycleops.go b/pkg/sdk/action/recycleops.go index a12bdf9738..04ed6fccf7 100644 --- a/pkg/sdk/action/recycleops.go +++ b/pkg/sdk/action/recycleops.go @@ -44,10 +44,7 @@ func (action *RecycleOperationsAction) Purge() error { ref := &provider.Reference{Path: homePath} req := &provider.PurgeRecycleRequest{Ref: ref} res, err := action.session.Client().PurgeRecycle(action.session.Context(), req) - if err := net.CheckRPCInvocation("purging recycle bin", res, err); err != nil { - return err - } - return nil + return net.CheckRPCInvocation("purging recycle bin", res, err) } // NewRecycleOperationsAction creates a new recycle operations action.