Skip to content

Commit e67647a

Browse files
authored
Merge pull request #11645 from Ostap-Zherebetskyi/feature/sso_login_url
[ENG-10634] Add a button to get the SSO login shortcut URL for eligible institutions
2 parents 1c709f6 + ff32e01 commit e67647a

3 files changed

Lines changed: 74 additions & 1 deletion

File tree

admin/institutions/views.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def get_context_data(self, *args, **kwargs):
5656
institution_dict = model_to_dict(institution)
5757
kwargs.setdefault('page_number', self.request.GET.get('page', '1'))
5858
kwargs['institution'] = institution_dict
59+
kwargs['cas_login_url'] = institution.cas_login_url
5960
kwargs['logo_path'] = institution.logo_path
6061
kwargs['banner_path'] = institution.banner_path
6162
fields = institution_dict

admin/templates/institutions/detail.html

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,33 @@
11
{% extends "base.html" %}
22
{% load static %}
33
{% block top_includes %}
4-
<link rel="stylesheet" type="text/css" href="/static/css/institutions.css" />
4+
<link rel="stylesheet" type="text/css" href="/static/css/institutions.css" />
5+
<style>
6+
#copy-modal {
7+
display: none; /* hidden by default */
8+
position: fixed;
9+
z-index: 2000;
10+
inset: 0;
11+
}
12+
#copy-modal.show_modal {
13+
display: flex;
14+
align-items: center;
15+
justify-content: center;
16+
padding: 16px;
17+
}
18+
#copy-modal .modal-content {
19+
background: white;
20+
width: 100%;
21+
max-width: 600px;
22+
max-height: 80vh;
23+
padding: 20px;
24+
border-radius: 10px;
25+
display: flex;
26+
flex-direction: column;
27+
gap: 10px;
28+
overflow: hidden;
29+
}
30+
</style>
531
{% endblock %}
632
{% load comment_extras %}
733
{% block title %}
@@ -33,6 +59,18 @@
3359
{% if perms.osf.change_institution %}
3460
<a class="btn btn-primary" href={% url 'institutions:list_and_add_admin' institution.id %}>Manage Admins</a>
3561
{% endif %}
62+
{% if cas_login_url %}
63+
<button class="btn btn-primary" onclick="openCopyPopup('{{ cas_login_url|escapejs }}')">
64+
Copy SSO URL
65+
</button>
66+
<div id="copy-modal" class="modal">
67+
<div class="modal-content">
68+
<span class="close" onclick="closeCopyPopup()">&times;</span>
69+
<p>Value copied. You can also copy manually:</p>
70+
<textarea id="copy-input" readonly></textarea>
71+
</div>
72+
</div>
73+
{% endif %}
3674
</div>
3775
</div>
3876

@@ -168,5 +206,27 @@ <h3>Are you sure you want to run monthly report for this institution?</h3>
168206
});
169207
});
170208
});
209+
210+
window.openCopyPopup = function(text) {
211+
const modal = document.getElementById("copy-modal");
212+
const input = document.getElementById("copy-input");
213+
input.value = text;
214+
modal.classList.add("show_modal");
215+
navigator.clipboard.writeText(text).catch(() => {});
216+
input.focus();
217+
input.select();
218+
};
219+
220+
window.closeCopyPopup = function() {
221+
document.getElementById("copy-modal").classList.remove("show_modal");
222+
};
223+
224+
// Close on outside click
225+
window.onclick = function(event) {
226+
const modal = document.getElementById("copy-modal");
227+
if (event.target === modal) {
228+
modal.classList.remove("show_modal");
229+
}
230+
};
171231
</script>
172232
{% endblock %}

osf/models/institution.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from .validators import validate_email
2525
from osf.utils.fields import NonNaiveDateTimeField, LowercaseEmailField
2626
from website import settings as website_settings
27+
from urllib.parse import quote
2728

2829
logger = logging.getLogger(__name__)
2930

@@ -208,6 +209,17 @@ def banner_path(self):
208209
except InstitutionAssetFile.DoesNotExist:
209210
return '/static/img/institutions/banners/placeholder-banner.png'
210211

212+
@property
213+
def cas_login_url(self):
214+
if self.delegation_protocol == IntegrationType.NONE.value:
215+
return None
216+
if 'localhost' in website_settings.DOMAIN:
217+
next_param = quote(website_settings.PROTOCOL + website_settings.LOCAL_ANGULAR_URL, safe='')
218+
else:
219+
next_param = quote(website_settings.DOMAIN, safe='')
220+
service_url = quote(f'{website_settings.DOMAIN}login?next={next_param}', safe='')
221+
return f'{website_settings.CAS_SERVER_URL}/login?campaign=institution&institutionId={self._id}&service={service_url}'
222+
211223
def update_search(self):
212224
from website.search.search import update_institution
213225
from website.search.exceptions import SearchUnavailableError

0 commit comments

Comments
 (0)