Skip to content

Commit

Permalink
Add site account registration panel (#1595)
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel-WWU-IT committed Mar 30, 2021
1 parent 4fd0229 commit eb3025e
Show file tree
Hide file tree
Showing 17 changed files with 497 additions and 111 deletions.
5 changes: 5 additions & 0 deletions changelog/unreleased/siteacc-reg-panel.md
@@ -0,0 +1,5 @@
Enhancement: Add site account registration panel

This PR adds a site account registration panel to the site accounts service. It also removes site registration from the xcloud metrics driver.

https://github.com/cs3org/reva/pull/1595
18 changes: 18 additions & 0 deletions docs/content/en/docs/config/http/services/siteacc/_index.md
Expand Up @@ -19,6 +19,15 @@ prefix = "/siteacc"
{{< /highlight >}}
{{% /dir %}}

{{% dir name="enable_registration_form" type="string" default="false" %}}
If set to true, the service will expose a simple form for account registration.

{{< highlight toml >}}
[http.services.siteacc]
enable_registration_form = true
{{< /highlight >}}
{{% /dir %}}

{{% dir name="notifications_mail" type="string" default="" %}}
An email address where all notifications are sent to.
{{< highlight toml >}}
Expand Down Expand Up @@ -93,3 +102,12 @@ The file location.
file = "/var/reva/accounts.json"
{{< /highlight >}}
{{% /dir %}}

## Site registration settings
{{% dir name="url" type="string" default="" %}}
The registration service URL.
{{< highlight toml >}}
[http.services.siteacc.sitereg]
url = "https://iop.example.com/sitereg"
{{< /highlight >}}
{{% /dir %}}
4 changes: 1 addition & 3 deletions examples/metrics/xcloud.toml
Expand Up @@ -5,8 +5,6 @@ address = "0.0.0.0:5550"
metrics_data_driver_type = "xcloud"
metrics_record_interval = 5000
xcloud_instance = "http://localhost"
xcloud_interval = 5
xcloud_catalog = 'https://sciencemesh-test.uni-muenster.de/api/mentix/sites?action=register'
insecure_skip_verify = true
xcloud_pull_interval = 60

[http.services.prometheus]
2 changes: 2 additions & 0 deletions examples/siteacc/siteacc.toml
Expand Up @@ -2,6 +2,8 @@
address = "0.0.0.0:9600"

[http.services.siteacc]
# If this is set to true, the service will expose a simple form for account registration
enable_registration_form = true
# All notification emails are sent to this email
notifications_mail = "science.mesh@example.com"

Expand Down
6 changes: 6 additions & 0 deletions internal/http/services/siteacc/config/config.go
Expand Up @@ -32,6 +32,12 @@ type Configuration struct {
} `mapstructure:"file"`
} `mapstructure:"storage"`

EnableRegistrationForm bool `mapstructure:"enable_registration_form"`

SMTP *smtpclient.SMTPCredentials `mapstructure:"smtp"`
NotificationsMail string `mapstructure:"notifications_mail"`

SiteRegistration struct {
URL string `mapstructure:"url"`
} `mapstructure:"sitereg"`
}
6 changes: 5 additions & 1 deletion internal/http/services/siteacc/config/endpoints.go
Expand Up @@ -21,6 +21,8 @@ package config
const (
// EndpointPanel is the endpoint path of the web interface panel.
EndpointPanel = "/panel"
// EndpointRegistration is the endpoint path of the web interface registration form.
EndpointRegistration = "/register"

// EndpointGenerateAPIKey is the endpoint path of the API key generator.
EndpointGenerateAPIKey = "/generate-api-key"
Expand All @@ -41,8 +43,10 @@ const (
// EndpointRemove is the endpoint path for account removal.
EndpointRemove = "/remove"

// EndpointAuthorize is the endpoint path for account authorization
// EndpointAuthorize is the endpoint path for account authorization.
EndpointAuthorize = "/authorize"
// EndpointIsAuthorized is the endpoint path used to check the authorization status of an account.
EndpointIsAuthorized = "/is-authorized"
// EndpointUnregisterSite is the endpoint path for site unregistration.
EndpointUnregisterSite = "/unregister-site"
)
3 changes: 1 addition & 2 deletions internal/http/services/siteacc/data/account.go
Expand Up @@ -19,7 +19,6 @@
package data

import (
"strings"
"time"

"github.com/pkg/errors"
Expand Down Expand Up @@ -51,7 +50,7 @@ type Accounts = []*Account

// GetSiteID returns the site ID (generated from the API key) for the given account.
func (acc *Account) GetSiteID() key.SiteIdentifier {
if id, err := key.CalculateSiteID(acc.Data.APIKey, strings.ToLower(acc.Email)); err == nil {
if id, err := key.CalculateSiteID(acc.Data.APIKey, key.SaltFromEmail(acc.Email)); err == nil {
return id
}

Expand Down
44 changes: 41 additions & 3 deletions internal/http/services/siteacc/manager.go
Expand Up @@ -33,6 +33,8 @@ import (
"github.com/cs3org/reva/internal/http/services/siteacc/data"
"github.com/cs3org/reva/internal/http/services/siteacc/email"
"github.com/cs3org/reva/internal/http/services/siteacc/panel"
"github.com/cs3org/reva/internal/http/services/siteacc/registration"
"github.com/cs3org/reva/internal/http/services/siteacc/sitereg"
"github.com/cs3org/reva/pkg/mentix/key"
"github.com/cs3org/reva/pkg/smtpclient"
)
Expand All @@ -54,8 +56,9 @@ type Manager struct {
accounts data.Accounts
storage data.Storage

panel *panel.Panel
smtp *smtpclient.SMTPCredentials
panel *panel.Panel
registrationForm *registration.Form
smtp *smtpclient.SMTPCredentials

mutex sync.RWMutex
}
Expand Down Expand Up @@ -88,6 +91,13 @@ func (mngr *Manager) initialize(conf *config.Configuration, log *zerolog.Logger)
return errors.Wrap(err, "unable to create panel")
}

// Create the web interface registrationForm
if frm, err := registration.NewForm(conf, log); err == nil {
mngr.registrationForm = frm
} else {
return errors.Wrap(err, "unable to create registrationForm")
}

// Create the SMTP client
if conf.SMTP != nil {
mngr.smtp = smtpclient.NewSMTPCredentials(conf.SMTP)
Expand Down Expand Up @@ -163,6 +173,11 @@ func (mngr *Manager) ShowPanel(w http.ResponseWriter) error {
return mngr.panel.Execute(w, &accounts)
}

// ShowRegistrationForm writes the registration registrationForm HTTP output directly to the response writer.
func (mngr *Manager) ShowRegistrationForm(w http.ResponseWriter) error {
return mngr.registrationForm.Execute(w)
}

// CreateAccount creates a new account; if an account with the same email address already exists, an error is returned.
func (mngr *Manager) CreateAccount(accountData *data.Account) error {
mngr.mutex.Lock()
Expand Down Expand Up @@ -261,7 +276,7 @@ func (mngr *Manager) AssignAPIKeyToAccount(accountData *data.Account, flags int)
}

for {
apiKey, err := key.GenerateAPIKey(strings.ToLower(account.Email), flags) // Use the (lowered) email address as the key's salt value
apiKey, err := key.GenerateAPIKey(key.SaltFromEmail(account.Email), flags)
if err != nil {
return errors.Wrap(err, "error while generating API key")
}
Expand All @@ -283,6 +298,29 @@ func (mngr *Manager) AssignAPIKeyToAccount(accountData *data.Account, flags int)
return nil
}

// UnregisterAccountSite unregisters the site associated with the given account.
func (mngr *Manager) UnregisterAccountSite(accountData *data.Account) error {
mngr.mutex.RLock()
defer mngr.mutex.RUnlock()

account, err := mngr.findAccount(FindByEmail, accountData.Email)
if err != nil {
return errors.Wrap(err, "no account with the specified email exists")
}

salt := key.SaltFromEmail(account.Email)
siteID, err := key.CalculateSiteID(account.Data.APIKey, salt)
if err != nil {
return errors.Wrap(err, "unable to get site ID")
}

if err := sitereg.UnregisterSite(mngr.conf.SiteRegistration.URL, account.Data.APIKey, siteID, salt); err != nil {
return errors.Wrap(err, "error while unregistering the site")
}

return nil
}

// RemoveAccount removes the account identified by the account email; if no such account exists, an error is returned.
func (mngr *Manager) RemoveAccount(accountData *data.Account) error {
mngr.mutex.Lock()
Expand Down
7 changes: 5 additions & 2 deletions internal/http/services/siteacc/panel/template.go
Expand Up @@ -89,11 +89,14 @@ const panelTemplate = `
<button type="button" onClick="handleAction('assign-api-key?isScienceMesh', '{{.Email}}');" {{if .Data.APIKey}}disabled{{end}}>ScienceMesh API Key</button>
{{if .Data.Authorized}}
<button type="button" onClick="handleAction('authorize?status=false', '{{.Email}}');">Unauthorize</button>
<button type="button" onClick="handleAction('authorize?status=false', '{{.Email}}');" {{if not .Data.APIKey}}disabled{{end}}>Unauthorize</button>
{{else}}
<button type="button" onClick="handleAction('authorize?status=true', '{{.Email}}');">Authorize</button>
<button type="button" onClick="handleAction('authorize?status=true', '{{.Email}}');" {{if not .Data.APIKey}}disabled{{end}}>Authorize</button>
{{end}}
<span style="width: 25px;">&nbsp;</span>
<button type="button" onClick="handleAction('unregister-site', '{{.Email}}');" {{if not .Data.APIKey}}disabled{{end}}>Unregister site</button>
<span style="width: 25px;">&nbsp;</span>
<button type="button" onClick="handleAction('remove', '{{.Email}}');">Remove</button>
</form>
Expand Down
76 changes: 76 additions & 0 deletions internal/http/services/siteacc/registration/form.go
@@ -0,0 +1,76 @@
// 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 registration

import (
"html/template"
"net/http"

"github.com/pkg/errors"
"github.com/rs/zerolog"

"github.com/cs3org/reva/internal/http/services/siteacc/config"
)

// Form represents the web interface form for user account registration.
type Form struct {
conf *config.Configuration
log *zerolog.Logger

tpl *template.Template
}

func (form *Form) initialize(conf *config.Configuration, log *zerolog.Logger) error {
if conf == nil {
return errors.Errorf("no configuration provided")
}
form.conf = conf

if log == nil {
return errors.Errorf("no logger provided")
}
form.log = log

// Create the form template
form.tpl = template.New("form")
if _, err := form.tpl.Parse(formTemplate); err != nil {
return errors.Wrap(err, "error while parsing form template")
}

return nil
}

// Execute generates the HTTP output of the form and writes it to the response writer.
func (form *Form) Execute(w http.ResponseWriter) error {
type TemplateData struct {
}

tplData := TemplateData{}

return form.tpl.Execute(w, tplData)
}

// NewForm creates a new web interface form.
func NewForm(conf *config.Configuration, log *zerolog.Logger) (*Form, error) {
form := &Form{}
if err := form.initialize(conf, log); err != nil {
return nil, errors.Wrapf(err, "unable to initialize the form")
}
return form, nil
}

0 comments on commit eb3025e

Please sign in to comment.