Skip to content

Commit

Permalink
Merge pull request rdmorganiser#588 from rdmorganiser/dev-1.9.2
Browse files Browse the repository at this point in the history
Dev 1.9.2
  • Loading branch information
triole authored Feb 23, 2023
2 parents fb7f2dc + f8e4235 commit 8a13ccd
Show file tree
Hide file tree
Showing 12 changed files with 157 additions and 17 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## RDMO 1.9.2 (Feb 23, 2023)

* Fix URL in invite emails in the multi site setup (#576)
* Check permissions for parent project on project page (#576)
* Fix project invite timeout (#580)
* Restore missing commits from last release

## RDMO 1.9.1 (Feb 03, 2023)

* Fix overlays if tasks/views are not available for a project
Expand Down
2 changes: 1 addition & 1 deletion rdmo/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__title__ = 'rdmo'
__version__ = '1.9.1'
__version__ = '1.9.2'
__author__ = 'RDMO Arbeitsgemeinschaft'
__email__ = 'rdmo-team@listserv.dfn.de'
__license__ = 'Apache-2.0'
Expand Down
1 change: 1 addition & 0 deletions rdmo/domain/serializers/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ class Meta:
fields = (
'id',
'uri',
'uri_prefix',
'key',
'path'
)
6 changes: 3 additions & 3 deletions rdmo/projects/models/invite.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ def get_absolute_url(self):

@property
def is_expired(self):
if settings.PROJECT_INVITE_TIMEOUT is None:
return False
else:
if settings.PROJECT_INVITE_TIMEOUT:
return (now() - self.timestamp).total_seconds() > settings.PROJECT_INVITE_TIMEOUT
else:
return False

def make_token(self):
self.token = salted_hmac(self.key_salt, self._make_hash_value()).hexdigest()[::2]
Expand Down
3 changes: 2 additions & 1 deletion rdmo/projects/serializers/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ class Meta:
'authors',
'guests',
'created',
'updated'
'updated',
'site'
)
read_only_fields = (
'snapshots',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{% load i18n %}
{% load rules %}
{% load projects_tags %}

{% if can_change_project %}
Expand All @@ -13,13 +14,19 @@
{% for node in project_tree %}
<li>
{% projects_indent node.level %}
<a href="{% url 'project' node.id %}">
{% if node.id == project.id %}
<strong>{{ node.title }}</strong>
{% else %}
{{ node.title }}
{% endif %}
</a>

{% has_perm 'projects.view_project_object' request.user node as can_view_parent_project %}
{% if can_view_parent_project %}
<a href="{% url 'project' node.id %}">
{% if node.id == project.id %}
<strong>{{ node.title }}</strong>
{% else %}
{{ node.title }}
{% endif %}
</a>
{% else %}
{{ node.title }}
{% endif %}
</li>
{% endfor %}
</ul>
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ <h2 id="import-project">{% trans 'Import values' %}</h2>
{% url 'project_update_import' project.id as upload_url %}
{% include 'core/upload_form.html' with upload_url=upload_url label=True %}
</li>
{% if settings.NESTED_PROJECTS and project.get_ancestors %}
{% if settings.NESTED_PROJECTS and ancestors_import %}
<li>
<p>
<strong>{% trans 'Import from parent project' %}</strong>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<input type="hidden" name="method" value="import_project">

<select class="form-control" name="source">
{% for project in project.get_ancestors %}
{% for project in ancestors_import %}
<option value="{{ project.id }}">{{ project.title }}</option>
{% endfor %}
</select>
Expand Down
101 changes: 101 additions & 0 deletions rdmo/projects/tests/test_view_membership_multisite.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import pytest

from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site
from django.core import mail
from django.urls import reverse

from ..models import Invite, Project
from ..utils import get_invite_email_project_path
users = (
('owner', 'owner'),
('manager', 'manager'),
('author', 'author'),
('guest', 'guest'),
('user', 'user'),
('site', 'site'),
('anonymous', None),
)

add_membership_permission_map = {
'owner': [1, 2, 3, 4, 5],
'api': [1, 2, 3, 4, 5],
'site': [1, 2, 3, 4, 5]
}

projects = [1, 2, 3, 4, 5]
memberships = [1, 2, 3, 4]

membership_roles = ('owner', 'manager', 'author', 'guest')

sites_domains = ('example.com', 'foo.com', 'bar.com')

@pytest.fixture()
def multisite_setting(settings):
settings.MULTISITE = True

@pytest.mark.parametrize('username,password', users)
@pytest.mark.parametrize('project_id', projects)
@pytest.mark.parametrize('membership_role', membership_roles)
@pytest.mark.parametrize('site_domain', sites_domains)
def test_get_invite_email_project_path_function(db, client, username, password, project_id, membership_role, site_domain, multisite_setting):
client.login(username=username, password=password)

current_site = Site.objects.get_current()
foo_site, _created = Site.objects.get_or_create(domain=site_domain, name=site_domain)
foo_username = f'{site_domain}-test-user'
foo_email = f'{foo_username}@{site_domain}'
foo_user, _created = get_user_model().objects.get_or_create(username=foo_username, email=foo_email, password=foo_username)
foo_user.role.member.set([foo_site])
project = Project.objects.get(pk=project_id)

invite = Invite(project=project, user=foo_user, role=membership_role)
invite.make_token()
invite.save()

invite_email_project_path = get_invite_email_project_path(invite)
if current_site.domain == site_domain:
assert invite_email_project_path.startswith('/projects')
else:
assert invite_email_project_path.startswith('http://' + site_domain + '/projects')

@pytest.mark.parametrize('username,password', users)
@pytest.mark.parametrize('project_id', projects)
@pytest.mark.parametrize('membership_role', membership_roles)
@pytest.mark.parametrize('site_domain', sites_domains)
def test_invite_email_project_path_email_body(db, client, username, password, project_id, membership_role, site_domain, multisite_setting):
client.login(username=username, password=password)

current_site = Site.objects.get_current()
foo_site, _created = Site.objects.get_or_create(domain=site_domain, name=site_domain)
foo_username = f'{site_domain}-user'
foo_email = f'{foo_username}@{site_domain}'
foo_user, _created = get_user_model().objects.get_or_create(username=f'{site_domain}-user', email=foo_email, password=foo_username)
foo_user.role.member.set([foo_site])
project = Project.objects.get(pk=project_id)

url = reverse('membership_create', args=[project_id])
data = {
'username_or_email': foo_email,
'role': membership_role
}
response = client.post(url, data)

invite = Invite(project=project, user=foo_user, role=membership_role)
invite.make_token()
invite.save()

if project_id in add_membership_permission_map.get(username, []):
if current_site.domain == site_domain:
assert 'http://testserver/' in mail.outbox[0].body
else:
assert f'http://{foo_site.domain}/' in mail.outbox[0].body

assert response.status_code == 302
assert len(mail.outbox) == 1
else:
if password:
assert response.status_code == 403
else:
assert response.status_code == 302
assert len(mail.outbox) == 0
17 changes: 17 additions & 0 deletions rdmo/projects/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import logging
from pathlib import Path

from django.conf import settings
from django.contrib.sites.models import Site
from django.urls import reverse

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -128,3 +132,16 @@ def save_import_tasks(project, tasks):
def save_import_views(project, views):
for view in views:
project.views.add(view)


def get_invite_email_project_path(invite) -> str:
project_invite_path = reverse('project_join', args=[invite.token])
# check if the invited user exists and the multisite environment is enabled
if invite.user is not None and settings.MULTISITE:
# do nothing if user is a member of the current site
current_site = Site.objects.get_current()
if not invite.user.role.member.filter(id=current_site.id).exists():
# else take first site
invited_user_member_domain = invite.user.role.member.first().domain
project_invite_path = 'http://' + invited_user_member_domain + project_invite_path
return project_invite_path
6 changes: 4 additions & 2 deletions rdmo/projects/views/membership.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from ..forms import MembershipCreateForm
from ..models import Membership, Project
from ..utils import is_last_owner
from ..utils import is_last_owner, get_invite_email_project_path

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -48,8 +48,10 @@ def get_success_url(self):
def form_valid(self, form):
invite = form.save()
if invite is not None:

project_invite_path = get_invite_email_project_path(invite)
context = {
'invite_url': self.request.build_absolute_uri(reverse('project_join', args=[invite.token])),
'invite_url': self.request.build_absolute_uri(project_invite_path),
'invite_user': invite.user,
'project': invite.project,
'user': self.request.user,
Expand Down
6 changes: 5 additions & 1 deletion rdmo/projects/views/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,18 @@ def get_context_data(self, **kwargs):
.filter_catalog(self.object.catalog) \
.filter_group(self.request.user) \
.filter_availability(self.request.user).exists()
ancestors_import = []
for instance in ancestors.exclude(id=project.id):
if self.request.user.has_perm('projects.view_project_object', instance):
ancestors_import.append(instance)
context['ancestors_import'] = ancestors_import
context['memberships'] = memberships.order_by('user__last_name', '-project__level')
context['integrations'] = integrations.order_by('provider_key', '-project__level')
context['providers'] = get_plugins('PROJECT_ISSUE_PROVIDERS')
context['issues'] = [issue for issue in project.issues.all() if issue.resolve(values)]
context['snapshots'] = project.snapshots.all()
context['invites'] = project.invites.all()
context['membership'] = Membership.objects.filter(project=project, user=self.request.user).first()

return context


Expand Down

0 comments on commit 8a13ccd

Please sign in to comment.