Skip to content

Commit

Permalink
add project form remote site controls (#817), fix #1429
Browse files Browse the repository at this point in the history
  • Loading branch information
mikkonie committed Jun 17, 2024
1 parent a0808d9 commit 960d475
Show file tree
Hide file tree
Showing 15 changed files with 806 additions and 332 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ Added
- ``is_source_site()`` and ``is_target_site()`` rule predicates
- ``settings_link`` kwarg in ``send_generic_email()`` (#1418)
- ``addremotesite`` and ``syncgroups`` command tests (#352)
- ``RemoteSite.owner_modifiable`` field (#817)
- ``assert_displayed()`` UI test helper
- **Timeline**
- ``sodar_uuid`` field in ``TimelineEventObjectRef`` model (#1415)
- REST API views (#1350)
Expand Down Expand Up @@ -98,6 +100,7 @@ Fixed
- ``ProjectStarringAjaxView`` creating redundant database objects (#1416)
- ``addremotesite`` crash in ``TimelineAPI.add_event()`` (#1425)
- ``addremotesite`` allows creation of site with mode identical to host (#1426)
- Public guest access field not correctly hidden in project form (#1429)
- **Sodarcache**
- REST API set view ``app_name`` incorrectly set (#1405)

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 48 additions & 13 deletions docs/source/app_projectroles_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,16 @@ specifically granting it.
Public guest access can only be set for projects. Categories will be visible
for users with access to any category or project under them.

Access on Remote Sites
----------------------

In the project create/update view, owners and delegates can modify remote site
access to projects. This is available for sites where these controls have been
enabled by administrators. The sites will appear as checkboxes as
:guilabel:`Enable project on {SITE-NAME}`.

For more information, see :ref:`app_projectroles_usage_remote`.

App Settings
------------

Expand Down Expand Up @@ -334,6 +344,8 @@ out of the LDAP server. Use the ``-h`` flag to see additional options.
$ ./manage.py checkusers
.. _app_projectroles_usage_remote:

Remote Projects
===============

Expand Down Expand Up @@ -372,10 +384,33 @@ specifying the remote site. A secret string is generated automatically. You
need to provide this to the administrator of the target site in question for
accessing your site.

Here you also have the option to hide the remote project link from your users.
Users viewing the project on the source site then won't see a link to the target
site. Owners and superusers will still see the link (greyed out). This is most
commonly used for internal test sites which only needs to be used by admins.
Fields for target remote site creation:

Name
Name of the remote site.
URL
URL for the remote site, e.g. ``https://sodar-core-site.example.com``.
Description
Text description for the site.
User display
If set false, this will hide the remote project links from your users.
Users viewing the project on the source site then won't see a link to the
target site. Owners and superusers will still see the link (greyed out).
This is most commonly used for internal test sites which only needs to be
used by admins.
Owner modifiable
If this and :guilabel:`User display` are checked, owners and delegates can
control project visibility on this site in the project create/update view.
Secret
Secret token for the project, which must be set to an identical value
between source and target sites.

.. figure:: _static/app_projectroles/sodar_remote_site_form.png
:align: center
:scale: 50%

Remote site create/update view viewed as a source site


Once created, you can access the list of projects on your site in regards to the
created target site. For each project, you may select an access level, of which
Expand All @@ -391,6 +426,15 @@ Revoked Access
remain in the target site, but only superusers, the project owner or the
project delegate(s) can access it.

Once desired access to specific projects has been granted and confirmed, the
target site will sync the data by sending a request to the source site.

.. figure:: _static/app_projectroles/sodar_remote_projects.png
:align: center
:scale: 50%

Remote project list viewed as a source site

.. note::

The *read roles* access level also provides metadata of the categories above
Expand All @@ -415,15 +459,6 @@ Revoked Access
explicitly set by the project owner, delegate or a superuser in the
:guilabel:`Update Project` form.

Once desired access to specific projects has been granted and confirmed, the
target site will sync the data by sending a request to the source site.

.. figure:: _static/app_projectroles/sodar_remote_projects.png
:align: center
:scale: 50%

Remote project list in source mode

As Target Site
--------------

Expand Down
1 change: 1 addition & 0 deletions docs/source/major_changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Release Highlights
- Add admin alert email sending to all users
- Add improved additional email address management and verification
- Add user opt-out settings for email notifications
- Add remote sync controls for owners and delegates in project form
- Add target site user UUID updating in remote sync
- Add remote sync of existing target local users
- Add remote sync of USER scope app settings
Expand Down
63 changes: 55 additions & 8 deletions projectroles/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
RoleAssignment,
ProjectInvite,
RemoteSite,
RemoteProject,
SODAR_CONSTANTS,
ROLE_RANKING,
APP_SETTING_VAL_MAXLENGTH,
Expand Down Expand Up @@ -50,6 +51,7 @@
SITE_MODE_SOURCE = SODAR_CONSTANTS['SITE_MODE_SOURCE']
SITE_MODE_TARGET = SODAR_CONSTANTS['SITE_MODE_TARGET']
APP_SETTING_SCOPE_PROJECT = SODAR_CONSTANTS['APP_SETTING_SCOPE_PROJECT']
REMOTE_LEVEL_READ_ROLES = SODAR_CONSTANTS['REMOTE_LEVEL_READ_ROLES']

# Local constants
APP_NAME = 'projectroles'
Expand Down Expand Up @@ -359,9 +361,36 @@ def _get_parent_choices(cls, instance, user):
ret += [(c.sodar_uuid, c.full_title) for c in categories]
return sorted(ret, key=lambda x: x[1])

def _set_app_setting_widget(self, plugin_name, s_field, s_key, s_val):
def _init_remote_sites(self):
"""
Internal helper for setting app setting widget and value.
Initialize remote site fields in the form.
"""
p_display = get_display_name(PROJECT_TYPE_PROJECT)
for site in RemoteSite.objects.filter(
mode=SITE_MODE_TARGET, user_display=True, owner_modifiable=True
).order_by('name'):
f_name = 'remote_site.{}'.format(site.sodar_uuid)
f_label = 'Enable {} on {}'.format(p_display, site.name)
f_help = 'Make {} available on remote site "{}" ({})'.format(
p_display, site.name, site.url
)
f_initial = False
if self.instance.pk:
rp = RemoteProject.objects.filter(
site=site, project=self.instance
).first()
# NOTE: Only "read roles" is supported at the moment
f_initial = rp and rp.level == REMOTE_LEVEL_READ_ROLES
self.fields[f_name] = forms.BooleanField(
label=f_label,
help_text=f_help,
initial=f_initial,
required=False,
)

def _set_app_setting_field(self, plugin_name, s_field, s_key, s_val):
"""
Internal helper for setting app setting field, widget and value.
:param plugin_name: App plugin name
:param s_field: Form field name
Expand Down Expand Up @@ -500,6 +529,9 @@ def _set_app_setting_notes(self, s_field, s_val, plugin):
)

def _init_app_settings(self):
"""
Initialize app settings fields in the form.
"""
# Set up setting query kwargs
self.p_kwargs = {}
# Show unmodifiable settings to superusers
Expand All @@ -522,8 +554,8 @@ def _init_app_settings(self):
)
for s_key, s_val in p_settings.items():
s_field = 'settings.{}.{}'.format(plugin_name, s_key)
# Set widget and value
self._set_app_setting_widget(plugin_name, s_field, s_key, s_val)
# Set field, widget and value
self._set_app_setting_field(plugin_name, s_field, s_key, s_val)
# Set label notes
self._set_app_setting_notes(s_field, s_val, plugin)

Expand Down Expand Up @@ -601,12 +633,18 @@ def __init__(self, project=None, current_user=None, *args, **kwargs):
# Get current user for checking permissions for form items
if current_user:
self.current_user = current_user
# Add settings fields
self._init_app_settings()
# Access parent project if present
parent_project = None
if project:
parent_project = Project.objects.filter(sodar_uuid=project).first()
# Add remote site fields if on source site
if settings.PROJECTROLES_SITE_MODE == SITE_MODE_SOURCE and (
parent_project
or (self.instance.pk and self.instance.type == PROJECT_TYPE_PROJECT)
):
self._init_remote_sites()
# Add settings fields
self._init_app_settings()

# Update help texts to match DISPLAY_NAMES
self.fields['title'].help_text = 'Title'
Expand Down Expand Up @@ -777,7 +815,7 @@ def clean(self):
'Public guest access is not allowed for categories',
)

# Verify settings fields
# Verify remote site fields
cleaned_data, errors = self._validate_app_settings(
self.cleaned_data,
self.app_plugins,
Expand Down Expand Up @@ -1181,7 +1219,14 @@ class RemoteSiteForm(SODARModelForm):

class Meta:
model = RemoteSite
fields = ['name', 'url', 'description', 'user_display', 'secret']
fields = [
'name',
'url',
'description',
'user_display',
'owner_modifiable',
'secret',
]

def __init__(self, current_user=None, *args, **kwargs):
"""Override for form initialization"""
Expand All @@ -1199,8 +1244,10 @@ def __init__(self, current_user=None, *args, **kwargs):
if settings.PROJECTROLES_SITE_MODE == SITE_MODE_SOURCE:
self.fields['secret'].widget.attrs['readonly'] = True
self.fields['user_display'].widget = forms.CheckboxInput()
self.fields['owner_modifiable'].widget = forms.CheckboxInput()
elif settings.PROJECTROLES_SITE_MODE == SITE_MODE_TARGET:
self.fields['user_display'].widget = forms.HiddenInput()
self.fields['owner_modifiable'].widget = forms.HiddenInput()
self.fields['user_display'].initial = True

# Creation
Expand Down
11 changes: 11 additions & 0 deletions projectroles/management/commands/addremotesite.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ def add_arguments(self, parser):
type=bool,
help='User display of the remote site',
)
parser.add_argument(
'-o',
'--owner-modifiable',
dest='owner_modifiable',
default=True,
required=False,
type=bool,
help='Allow owners and delegates to modify project access for this '
'site',
)
# Additional Arguments
parser.add_argument(
'-s',
Expand Down Expand Up @@ -137,6 +147,7 @@ def handle(self, *args, **options):
'description': options['description'],
'secret': options['secret'],
'user_display': options['user_display'],
'owner_modifiable': options['owner_modifiable'],
}
site = RemoteSite.objects.create(**create_kw)

Expand Down
26 changes: 26 additions & 0 deletions projectroles/migrations/0031_remotesite_owner_modifiable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Django 4.2.11 on 2024-06-11 14:30

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("projectroles", "0030_populate_sodaruseradditionalemail"),
]

operations = [
migrations.AddField(
model_name="remotesite",
name="owner_modifiable",
field=models.BooleanField(
default=True,
help_text="Allow owners and delegates to modify project access for this site",
),
),
migrations.AlterField(
model_name="remotesite",
name="user_display",
field=models.BooleanField(default=True, help_text="Display site to users"),
),
]
18 changes: 13 additions & 5 deletions projectroles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1144,18 +1144,26 @@ class RemoteSite(models.Model):
help_text='Secret token for connecting to the source site',
)

#: RemoteSite visibilty to users
user_display = models.BooleanField(
default=True, unique=False, help_text='Display site to users'
)

#: RemoteSite project access modifiability for owners and delegates
owner_modifiable = models.BooleanField(
default=True,
unique=False,
help_text='Allow owners and delegates to modify project access for '
'this site',
)

#: RemoteSite relation UUID (local)
sodar_uuid = models.UUIDField(
default=uuid.uuid4,
unique=True,
help_text='RemoteSite relation UUID (local)',
)

#: RemoteSite's link visibilty for users
user_display = models.BooleanField(
default=True, unique=False, help_text='RemoteSite visibility to users'
)

class Meta:
ordering = ['name']
unique_together = ['url', 'mode', 'secret']
Expand Down
Loading

0 comments on commit 960d475

Please sign in to comment.