Commit ae733bda authored by Marcel Nellesen's avatar Marcel Nellesen
Browse files

Merge branch 'Product/1290-dfnaai' into 'Sprint/2021-03'

Product/1290 dfnaai

See merge request !61
parents 7e1c1ce6 61f1a760
......@@ -8,7 +8,8 @@
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
"lint": "vue-cli-service lint",
"fetch-dfn": "node scripts/conversion.js"
},
"dependencies": {
"@coscine/api-connection": "^1.25.0",
......@@ -25,7 +26,8 @@
"markdown-it": "^12.0.4",
"vue": "^2.6.12",
"vue-i18n": "^8.22.0",
"vue-markdown": "^2.2.4"
"vue-markdown": "^2.2.4",
"vue-multiselect": "^2.1.6"
},
"devDependencies": {
"@hutson/semantic-delivery-gitlab": "^9.1.0",
......@@ -39,9 +41,11 @@
"@typescript-eslint/parser": "^4.12.0",
"@vue/cli-plugin-typescript": "^4.5.7",
"@vue/cli-service": "^4.5.7",
"axios": "^0.21.1",
"conventional-changelog-eslint": "3.0.9",
"core-js": "^3.8.2",
"eslint": "^7.17.0",
"fast-xml-parser": "^3.18.0",
"semantic-release": "^17.3.1",
"typescript": "^4.0.3",
"vue-template-compiler": "^2.6.12"
......
const parser = require('fast-xml-parser');
const axios = require('axios');
const fs = require('fs').promises;
async function main(){
const response = await axios.get('https://www.aai.dfn.de/fileadmin/metadata/dfn-aai-basic-metadata.xml');
const allEntities = parser.parse(response.data, {ignoreAttributes : false});
const obj = allEntities.EntitiesDescriptor.EntityDescriptor
.filter(e => e.IDPSSODescriptor)
.reduce((o, e) => {
return {...o , ...entityToObject(e)};
}, {});
await fs.writeFile("src/data/dfnaai.json", JSON.stringify(obj, null, 2));
}
function entityToObject(e){
const o = {};
o[e['@_entityID']] = {
DisplayName : getDisplayName(e),
Logo : getLogo(e),
}
return o;
}
function getDisplayName(entity){
const dns = makeArray(entity.IDPSSODescriptor.Extensions['mdui:UIInfo']['mdui:DisplayName']);
const o = {};
for(let i=0; i<dns.length; i++){
const dn = dns[i];
o[dn['@_xml:lang']] = dn['#text'];
}
return o;
}
function getLogo(entity){
const logos = makeArray(entity.IDPSSODescriptor.Extensions['mdui:UIInfo']['mdui:Logo']);
let logo = undefined;
let size = -1;
for(let i=0; i<logos.length; i++){
const s = logos[i]['@_height'] * logos[i]['@_width'] || 0;
if(s > size){
size = s;
logo = logos[i]['#text'];
}
}
return logo;
}
function makeArray(x){
return x ? Array.isArray(x) ? x : [ x ] : [];
}
main();
......@@ -31,15 +31,12 @@
</div>
<div class="col-sm-12 col-md-5">
<div class="row">
<div class="card bg-light mb-3 w-100">
<div class="card bg-light w-100" style="margin-bottom: 1rem">
<div class="card-body">
<transition name="component-fade" mode="out-in">
<component
:is="(logoutState) ? logoutComponent : currentInputCard"
:returnUrl="this.returnUrl"
:shibbolethReturnUrl="this.shibbolethReturnUrl"
:testShibbolethReturnUrl="this.testShibbolethReturnUrl"
:fhShibbolethReturnUrl="this.fhShibbolethReturnUrl"
:idpUrl="this.idpUrl"
:loggedInWithShibboleth="this.loggedInWithShibboleth"
:mergeReturnUrl="mergeReturnUrl"
......@@ -85,10 +82,10 @@
<div class="col-xl-2"></div>
</div>
<coscine-page-footer
:contact="$t('contact')"
:disclaimer="$t('disclaimer')"
:help="$t('help')"
:imprint="$t('imprint')"
:contact="$t('contact')"
:disclaimer="$t('disclaimer')"
:help="$t('help')"
:imprint="$t('imprint')"
/>
</div>
</template>
......@@ -110,6 +107,7 @@ import phoneGreyPath from './assets/phone_grey.svg';
import faxGreyPath from './assets/fax_grey.svg';
import { NoticeApi } from '@coscine/api-connection';
import * as linkUtil from './util/linkUtil';
let scriptPath = '';
let imageEnabled = false;
......@@ -120,67 +118,6 @@ if (document.currentScript !== undefined) {
const scriptUrl = (scriptPath === '') ? '/' : scriptPath.substring(0, scriptPath.indexOf('app.js'));
const rootUrl = (scriptPath.indexOf('/js') !== -1) ? scriptUrl.replace('/js', '') : scriptUrl;
function getReturnUrlParam() {
const hookupElement = document.getElementById('loginpage');
const returnUrlParameters =
hookupElement != null ? hookupElement.getAttribute('returnUrl') : '';
return returnUrlParameters;
}
function getReturnUrl(method: string, entityId: string = '') {
let returnUrl = '/coscine/api/Coscine.Api.STS/' + method + '/login?returnUrl=' + getReturnUrlParam();
if (entityId !== '') {
returnUrl += '&entityId=' + entityId;
}
return encodeURI(returnUrl);
}
function getMergeReturnUrl() {
return getReturnUrl('Merge');
}
function getTOSReturnUrl() {
return encodeURI('' + getReturnUrlParam());
}
function getAccountReturnUrl() {
return getReturnUrl('Account');
}
function getShibbolethReturnUrl() {
return getReturnUrl('Shibboleth', 'https://login-test.rz.rwth-aachen.de/shibboleth');
}
function getIdpUrl() {
const hookupElement = document.getElementById('loginpage');
const idpUrl =
hookupElement != null ? hookupElement.getAttribute('IdpUrl') : '';
return idpUrl;
}
function getORCiDUrl() {
const hookupElement = document.getElementById('loginpage');
const orcidUrlString =
(hookupElement != null ? hookupElement.getAttribute('orcidUrl') : '') as string;
const orcidUrl = encodeURI(orcidUrlString);
return orcidUrl;
}
function getIsLogout() {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.has('logout');
}
function getIsTOS() {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.has('tos');
}
function getLoggedInWithShibboleth() {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.has('loggedInWithShibboleth');
}
export default Vue.extend({
name: 'loginapp',
components: {
......@@ -206,20 +143,18 @@ export default Vue.extend({
coscineImageBlue: rootUrl + coscineImageBluePath,
rwthImage: rootUrl + rwthImagePath,
imageEnabled,
returnUrl: getAccountReturnUrl(),
shibbolethReturnUrl: getReturnUrl('Shibboleth', 'https://login.rz.rwth-aachen.de/shibboleth'),
testShibbolethReturnUrl: getReturnUrl('Shibboleth', 'https://login-test.rz.rwth-aachen.de/shibboleth'),
fhShibbolethReturnUrl: getReturnUrl('Shibboleth', 'https://login.fh-aachen.de/idp/shibboleth'),
tosReturnUrl: getTOSReturnUrl(),
mergeReturnUrl: getMergeReturnUrl(),
idpUrl: getIdpUrl(),
orcidUrl: getORCiDUrl(),
logoutState: getIsLogout(),
loggedInWithShibboleth: getLoggedInWithShibboleth(),
returnUrl: linkUtil.getAccountReturnUrl(),
tosReturnUrl: linkUtil.getTOSReturnUrl(),
mergeReturnUrl: linkUtil.getMergeReturnUrl(),
idpUrl: linkUtil.getIdpUrl(),
orcidUrl: linkUtil.getORCiDUrl(),
logoutState: linkUtil.getIsLogout(),
loggedInWithShibboleth: linkUtil.getLoggedInWithShibboleth(),
currentInputCard: 'LoginMain',
logoutComponent: 'LogoutMain',
tos: getIsTOS(),
tos: linkUtil.getIsTOS(),
news: '',
storedUrl: {} as any,
};
},
watch: {
......@@ -231,8 +166,9 @@ export default Vue.extend({
retrieveNotices() {
NoticeApi.getNotices('changelog', this.$i18n.locale, (response: any) => {
let splitArray = response.data.data.body.split(/\s(?:#{3})\s/g, 5);
splitArray = splitArray.join('### ');
splitArray = splitArray.join('##### ');
this.news = splitArray.toString();
this.news= this.news.replace('## ', '##### ');
});
},
loginBack() {
......@@ -328,7 +264,7 @@ export default Vue.extend({
.headline {
text-align: left;
padding-left: 0.4em;
padding-left: 0;
margin-bottom: 1em;
}
......@@ -372,4 +308,7 @@ export default Vue.extend({
text-align: left;
border-radius: .2rem;
}
.alert.alert-warning {
border-radius: .2rem;
}
</style>
<template>
<div class="institutes">
<b-form class="w-100" method="post">
<v-multiselect
id="instituteSelection"
v-model="selected"
:placeholder="$t('InstituteSelection')"
:options="options"
label="DisplayName"
track-by="DisplayName"
:multiple="false"
/>
<b-form-group>
<b-button
id="backBtn"
name="back"
@click.prevent="clickBack"
>{{ $t('back') }}</b-button>
<b-button
id="continueBtn"
variant="primary"
name="continue"
@click="changePage"
:disabled = "selected.length === 0"
>{{ $t('continue') }}</b-button>
</b-form-group>
</b-form>
</div>
</template>
<script lang='ts'>
import Vue from 'vue';
import {BootstrapVue, BootstrapVueIcons} from 'bootstrap-vue';
import Institutes from '@/data/dfnaai.json';
import * as linkUtil from '@/util/linkUtil';
import Multiselect from 'vue-multiselect';
import 'vue-multiselect/dist/vue-multiselect.min.css';
Vue.component('v-multiselect', Multiselect);
Vue.use(BootstrapVue);
Vue.use(BootstrapVueIcons);
export default Vue.extend({
name: 'LoginInstitute',
data() {
return {
devMachine: false,
selected: [] as any,
instituteList: Institutes,
options: [] as object [],
};
},
created() {
this.devMachine = window.location.href.toString().indexOf('d-sp') !== -1;
},
mounted() {
this.fillInstituteList();
},
watch: {
'$i18n.locale'() {
this.fillInstituteList();
},
},
methods: {
fillInstituteList() {
this.options = [];
const institutes = JSON.parse(JSON.stringify(this.instituteList));
if (this.devMachine === true) {
this.options.push({
entityID: 'https://login-test.rz.rwth-aachen.de/shibboleth',
DisplayName: 'Test Shibboleth',
Logo: '',
});
};
if (this.$i18n.locale === 'de') {
for (let institute in institutes) {
this.options.push({
entityID: institute,
DisplayName: institutes[institute].DisplayName.de,
Logo: institutes[institute].Logo,
});
}
} else {
for (let institute in institutes) {
this.options.push({
entityID: institute,
DisplayName: institutes[institute].DisplayName.en,
Logo: institutes[institute].Logo,
});
}
}
},
changePage(){
localStorage.setItem('coscine.login.storedData', JSON.stringify(this.selected));
const entityId = this.selected.entityID;
const returnUrl = linkUtil.getReturnUrl('Shibboleth', entityId);
window.location.replace(returnUrl);
},
clickBack() {
this.$emit('closeInstituteSelection');
},
},
});
</script>
<style scoped>
#continueBtn {
float: right;
margin: 0;
}
#backBtn {
margin: 0;
}
#instituteSelection {
margin-bottom: 1.25rem;
}
.form-group {
margin-bottom: 0;
}
.multiselect {
margin-bottom: 0.75rem;
}
</style>
\ No newline at end of file
<template>
<div>
<h5 class="card-title">{{ $t('login_headline') }}</h5>
<div class="row">
<button class="btn btn-primary w-100" name="getORCiDForm" @click.prevent="clickGetORCiDForm">
<span>
<img alt="ORCID logo" id="orcid-id-logo" src="https://orcid.org/sites/default/files/images/orcid_24x24.png" width="25" height="25" />
</span>
<span> {{ $t('login_button_orcid') }}</span>
</button>
<div v-if="showInstitutes">
<login-institute @closeInstituteSelection="changeInstitutesVisibility"></login-institute>
</div>
<form class="w-100" :action="devMachine === true ? testShibbolethReturnUrl : shibbolethReturnUrl" method="post">
<div class="row">
<input class="btn btn-primary w-100" type="Submit" :value="$t('login_rwth')" />
<input type="hidden" name="wa" value="wsignin1.0" />
<div v-else>
<div>
<button class="btn btn-primary w-100" name="getORCiDForm" @click.prevent="clickGetORCiDForm">
<span>
<img alt="ORCID logo" id="orcid-id-logo" src="https://orcid.org/sites/default/files/images/orcid_24x24.png" width="23" height="20" />
</span>
<span> {{ $t('login_button_orcid') }}</span>
</button>
</div>
</form>
<form class="w-100" :action="fhShibbolethReturnUrl" method="post">
<div class="row">
<input class="btn btn-primary w-100" type="Submit" :value="$t('login_fh')" />
<input type="hidden" name="wa" value="wsignin1.0" />
<div v-if="wasPreviouslyAuthenticated">
<b-button class="w-100" variant="primary" @click="loginInstitutePage">{{ storedValue.DisplayName }}</b-button>
<input type="hidden" name="wa" value="wsignin1.0" />
</div>
</form>
<div>
<b-button class="w-100" variant="primary" @click="changeInstitutesVisibility">{{ $t('other_institutes') }}</b-button>
</div>
</div>
</div>
</template>
<script lang='ts'>
import Vue from 'vue';
import LoginInstitute from './LoginInstitute.vue';
import * as linkUtil from '@/util/linkUtil';
export default Vue.extend({
name: 'LoginMain',
data: {
devMachine: false,
components: {
LoginInstitute,
},
data() {
return {
wasPreviouslyAuthenticated: false,
showInstitutes: false,
storedValue: null as Object | null,
};
},
props: {
returnUrl: String,
shibbolethReturnUrl: String,
testShibbolethReturnUrl: String,
fhShibbolethReturnUrl: String,
},
created() {
const storedValue = localStorage.getItem('coscine.login.storedData');
if (storedValue !== null) {
this.storedValue = JSON.parse(storedValue);
this.wasPreviouslyAuthenticated = true;
}
},
methods: {
loginInstitutePage() {
const entityId = (this.storedValue as any).entityID;
if (entityId !== null) {
window.location.href = linkUtil.getReturnUrl('Shibboleth', entityId);
}
},
clickGetORCiDForm() {
this.$emit('clickGetORCiDForm');
},
changeInstitutesVisibility() {
this.showInstitutes = !this.showInstitutes;
},
},
created() {
this.devMachine = window.location.href.toString().indexOf('d-sp') !== -1;
}
});
</script>
<style scoped>
.btn {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
\ No newline at end of file
This diff is collapsed.
export default {
headline: 'Willkommen bei ',
description_headline: 'Informationen für CoScInE Nutzer',
description_text: `Sie können sich mit ihrem Nutzernamen (eg. ab1234567)
und dem Passwort, das Sie für RWTH Single Sign-On benutzen,
anmelden oder die Anmeldeinformationen anderer sozialer Netzwerke für den Login.`,
description_help_headline: 'Technische Fragen oder allgemeines Feedback?',
description_headline: 'Coscine-Anmeldeinformationen',
description_text: 'Sie können sich entweder über DFN-AAI mit Ihrem institutionellen Account (z.B. RWTH Single Sign-On) oder über ORCID anmelden.',
description_help: 'Schreiben Sie an ',
description_help_headline: 'Technische Fragen oder allgemeines Feedback?',
login_headline: 'Login',
login_button_with_account: 'Login-Zugang',
login_button_orcid: 'Anmelden mit ORCID',
......@@ -15,8 +13,11 @@ export default {
login_button_without_account: 'Weiter ohne Login',
logout_button_orcid: 'Logout von ORCID',
logout_shibboleth: 'Logout von Shibboleth',
other_institutes: 'Anderes Institutionelles Konto',
signup: 'Anmelden',
back: 'Zurück',
continue: 'Weiter',
InstituteSelection: 'Institution auswählen',
login_form_headline: 'Login mit Nutzer-ID',
login_form_submit: 'Senden',
login_form_id_required: 'Das UserId-Feld wird benötigt.',
......@@ -27,15 +28,15 @@ export default {
contact: 'Kontakt',
otherLocale: 'En',
alert_headline: 'Informationen zur Pilotphase',
alert_text2: '] senden.',
alert_text1: `CoScInE befindet sich derzeit in der ersten Pilotphase. Sie können sich also gerne
alert_text2: ' senden.',
alert_text1: `Coscine befindet sich derzeit in der ersten Pilotphase. Sie können sich also gerne
einloggen und das System erkunden, aber benutzen Sie es noch nicht für Ihre wichtigen Forschungsdaten!
Bitte kontaktieren Sie uns, wenn Sie an dem Pilotprogramm teilnehmen möchten. Feedback und
Verbesserungsvorschläge können Sie gerne an [`,
Verbesserungsvorschläge können Sie gerne an `,
tosppTitle: 'Nutzungsbedingungen und Datenschutzerklärung',
tosppCancel: 'Abbrechen',
tosppContinue: 'Fortfahren',
tosBodyStart: 'Um CoScInE nutzen zu können, müssen Sie den Nutzungsbedingungen (',
tosBodyStart: 'Um Coscine nutzen zu können, müssen Sie den Nutzungsbedingungen (',
tosBodyLink: '',
tosBodyEnd: ') zustimmen.',
tosBodyCheckBox: 'Ich akzeptiere die Nutzungsbedingungen',
......
export default {
headline: 'Welcome to ',
description_headline: 'Information for CoScInE users',
description_text: `You can log in using your username (eg. ab1234567)
and the password yo use for RWTH Single Sign-On or signup
using your credentials from other social network platforms.)`,
description_headline: 'Coscine login information',
description_text: 'You can log in using either DFN-AAI with your institutional account (e.g. RWTH Single Sign-On) or via ORCID.',
description_help_headline: 'Technical question or general feedback?',
description_help: 'Write us at ',
login_headline: 'Login',
......@@ -15,8 +13,11 @@ export default {
login_button_without_account: 'Continue without Login',
logout_button_orcid: 'Logout from ORCID',
logout_shibboleth: 'Logout from Shibboleth',
other_institutes: 'Other Institutional Account',
signup: 'Sign In',
back: 'Back',
continue: 'Continue',
InstituteSelection: 'Select Institution',
login_form_headline: 'Login with user ID',
login_form_submit: 'Submit',
login_form_id_required: 'The UserId field is required.',
......@@ -27,15 +28,15 @@ export default {
contact: 'Contact',
otherLocale: 'De',
alert_headline: 'Information about the pilot phase',
alert_text2: ']',
alert_text1: `CoScInE is currently in the first pilot phase. So you are welcome to
alert_text2: '.',
alert_text1: `Coscine is currently in the first pilot phase. So you are welcome to
log in and explore the system, but do not use it for your important
research data yet! Please contact us if you want to be part of the
pilot program. If you have feedback you are welcome to send it to [`,
pilot program. If you have feedback you are welcome to send it to `,
tosppTitle: 'Terms of use and privacy policy',
tosppCancel: 'Cancel',
tosppContinue: 'Continue',
tosBodyStart: 'In order to access CoScInE you need to accept the current Terms of Use (',
tosBodyStart: 'In order to access Coscine you need to accept the current Terms of Use (',
tosBodyLink: '',
tosBodyEnd: ').',
tosBodyCheckBox: 'I accept the Terms of Use',
......
......@@ -6,7 +6,7 @@ declare module '*.vue' {
declare module '@coscine/api-connection';
declare module '@coscine/component-library';
declare module 'vue-markdown';
declare module 'vue-multiselect';
declare module '@itcenter-layout/bootstrap';
declare module '*.png' {
const value: any;
......
function getReturnUrlParam() {
const hookupElement = document.getElementById('loginpage');
const returnUrlParameters =
hookupElement != null ? hookupElement.getAttribute('returnUrl') : '';
return returnUrlParameters;
}
const returnUrlParams = getReturnUrlParam();
export function getReturnUrl(method: string, entityId: string = '') {
let returnUrl = '/coscine/api/Coscine.Api.STS/' + method + '/login?returnUrl=' + returnUrlParams;
if (entityId !== '') {
returnUrl += '&entityId=' + entityId;
}
return encodeURI(returnUrl);
}
export function getMergeReturnUrl() {
return getReturnUrl('Merge');
}
export function getTOSReturnUrl() {
return encodeURI('' + getReturnUrlParam());
}
export function getAccountReturnUrl() {
return getReturnUrl('Account');
}
export function getIdpUrl() {