CINXE.COM

KIT-CA Zertifikatsuche

<!DOCTYPE html> <html lang="en"> <head> <!-- prevent IE from falling back to IE 8 engine --> <meta http-equiv="x-ua-compatible" content="IE=Edge"/> <!-- Basic Page Needs –––––––––––––––––––––––––––––––––––––––––––––––––– --> <meta charset="utf-8"> <title>KIT-CA Zertifikatsuche</title> <meta name="description" content=""> <meta name="author" content=""> <!-- Mobile Specific Metas –––––––––––––––––––––––––––––––––––––––––––––––––– --> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- FONT –––––––––––––––––––––––––––––––––––––––––––––––––– --> <link rel="stylesheet" href="css/local-google-fonts.css"> <!-- CSS –––––––––––––––––––––––––––––––––––––––––––––––––– --> <link rel="stylesheet" href="css/normalize.css"> <link rel="stylesheet" href="css/skeleton.css"> <link rel="stylesheet" href="css/font-awesome.min.css"> <style> th.active .arrow { opacity: 1; } .arrow { display: inline-block; vertical-align: middle; width: 0; height: 0; margin-left: 5px; opacity: 0.66; } .arrow.asc { border-left: 4px solid transparent; border-right: 4px solid transparent; border-bottom: 4px solid; } .arrow.dsc { border-top: 4px solid; border-left: 4px solid transparent; border-right: 4px solid transparent; } .good { color: green; } .neutral { color: orange; } .bad { color: red; } .valid { color: black; } .invalid { color: grey; } .icon { line-height: 1em !important; height: 1em; width: 1em; text-align: center; vertical-align: middle; padding: 5px; border-radius: 4px; border: 1px solid #b3e0da; background-color: #b3e0da; color: #FFF; } .icon-active { background-color: #009682; border-color: #009682; color: #FFF; } /* TODO: asd*/ .fa { font-size: 1.33333333em; line-height: 0.75em; vertical-align: -15%; } </style> <!-- vue.js --> <script src="js/vue.min.js"></script> </head> <body> <div class="container"> <div class="row" style="margin-bottom: 0em; margin-top: 5%"> <div class="one-half column"> <img src="images/KIT-CA-Logo.svg" height="60px" alt="KIT-CA Logo" /> </div> <div class="one-half column" style="text-align: right"> <h1>Zertifikatsuche</h1> </div> </div> <div id="ca-search"> <div class="row"> <div class="twelve columns" style="text-align: right"> <div v-if="visitorLoggedIn" style="display: flex; align-items: center"> <div style="flex: 1"></div> <div style="margin-right: 1rem;">Angemeldet als {{ visitorGivenName && visitorSn ? `${visitorGivenName} ${visitorSn}` : `${visitorEppn}` }}</div> <a class="button" style="margin-bottom: 0" :href="'/logout?q=' + query">Abmelden</a> </div> <a v-else :href="'/Shibboleth.sso/Login?target=/?q=' + query"><span class="button">Anmelden</span></a> </div> </div> <div class="row" style="margin-top: 3em;"> <div class="u-full-width"> <div v-if="!visitorLoggedIn" style="font-size: 2rem; padding-bottom: 1.25rem"><b>Für Zertifikate, die nach dem 30.08.2023 ausgestellt wurden, müssen Sie sich <a :href="'/Shibboleth.sso/Login?target=/?q=' + query">anmelden</a>.</b></div> <div style="padding-bottom: 1rem">Bitte geben Sie den Namen oder die exakte E-Mail-Adresse aus dem Zertifikat ein, das Sie suchen.</div> </div> </div> <form v-on:submit.prevent="search()" style="display: flex"> <div style="flex: 1"> <label for="query" style="display: none">Zertifikatssuche</label> <input name="q" class="u-full-width" type="text" placeholder="Name oder E-Mail-Adresse" id="query" v-model="query" autofocus> </div> <div style="flex: none; margin-left: 1rem"> <button class="button-primary" type="submit" @click="search()" v-if="!loading">Suchen</button> <button class="button-primary" type="button" disabled="disabled" v-if="loading">Suche...</button> </div> </form> <div class="row"> <div class="ten columns"> <span v-for="category in filterShow" class="one-third column" v-if="!category.hideInPublicSearch || visitorLoggedIn"> <strong>{{ category.name }}:</strong><br/> <span v-for="filter in category.filters" v-if="!filter.hideInPublicSearch || visitorLoggedIn"> <span class="icon fa" :title="filter.displayName" v-bind:class="[filter.displayIcon, { 'icon-active': filter.buttonState }]" v-on:click="enableFilter(filter)" v-on:dblclick="enableOnlyFilter(filter, category.filters)" style="margin: 5px" > <!-- <small>{{ filter.resultCount}}</small> --> </span> </span> </span> </div> </div> <div class="alert alert-danger" role="alert" v-if="error"> <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> &#x26A0; {{ error }} </div> <div class="row" style="margin-top: 4em"> <em>Zeige {{ filteredData.length }} von {{ resultData.length }} Treffern</em> </div> <table class="u-full-width" v-if="filteredData.length"> <thead> <tr> <th /> <th v-for="entry in tableHeaders" v-if="!entry.hideInPublicSearch || visitorLoggedIn" @click="sortBy(entry.field)" :class="{ active: sortKey == entry.field }"> {{ entry.description }} <span class="arrow" :class="sortOrders[entry.field] > 0 ? 'asc' : 'dsc'"> </span> </th> </tr> </thead> <tbody> <tr v-for="entry in filteredData" style="vertical-align: top"> <td style="text-align: right"> <template v-if="!visitorLoggedIn"> <a :href="'https://search.ca.kit.edu/pubdownload/pem/' + entry.serial"> <i class="fa fa-download" title="Download"></i> </a> </template> <template v-if="visitorLoggedIn"> <a :href="'https://search.ca.kit.edu/download/pem/' + entry.serial" > <i class="fa fa-download" title="Download"></i> </a> </template> <!-- <i class="fa fa&#45;info&#45;circle" aria&#45;hidden="true"></i> --> </td> <td style="display: flex; flex-direction: column"> <div> <span v-if="entry.cn">{{entry.cn}}</span> <span v-else-if="entry.emailaddresses.length > 1">{{entry.emailaddresses[0]}}</span> <span v-else><em>Kein Anzeigename gefunden</em></span> <span v-if="entry.ou">&nbsp;({{entry.ou}})</span> </div> <div><small><em>Serial&nbsp;(dec)</em>:&nbsp;{{entry.serial}}</small></div> <div> <small><em>Serial&nbsp;(hex)</em>:&nbsp;{{entry.hexserial.replace("0x", "").match(/.{1,2}/g).join(":").toLocaleUpperCase()}}</small> </div> <div> <small><em>Serial&nbsp;(hex)</em>:&nbsp;{{entry.hexserial.replace("0x", "").toLocaleUpperCase()}}</small> </div> <div> <small><em>Fingerprint</em>:&nbsp;{{entry.fingerprintsha1.replace("0x", "").match(/.{1,2}/g).join(":").toLocaleUpperCase()}}</small> </div> <div v-if="entry.cageneration"><small><em>CA</em>:&nbsp;{{entry.cageneration}}</small></div> </td> <td> <span v-for="email in entry.emailaddresses">{{email}}<br></span> <!-- TODO: --> </td> <td> <span :title="entry.notafter">{{ entry.notafterduration }}</span><br/> <small>{{entry.notafter}}</small> </td> <td> <i v-if="entry.valid=='valid'" :title="entry.valid" class="fa fa-check good"></i> <i v-if="entry.valid=='expired'" :title="entry.valid" class="fa fa-times neutral"></i> <i v-if="entry.valid=='revoked'" :title="entry.valid" class="fa fa-ban bad"></i> </td> <td> <i v-if="entry.type=='Benutzer'" class="fa fa-user" :title="entry.type"></i> <i v-if="entry.type=='Gruppe'" class="fa fa-users" :title="entry.type"></i> <i v-if="entry.type=='Pseudonym'" class="fa fa-user-secret" :title="entry.type"></i> <i v-if="entry.type=='Extern'" class="fa fa-user-plus" :title="entry.type"></i> <i v-if="entry.type=='Unbekannt'" class="fa fa-question-circle" :title="entry.type"></i> <i v-if="entry.type=='Server'" class="fa fa-server" :title="entry.type"></i> </td> <td v-if="visitorLoggedIn"> <i v-if="entry.public=='public'" class="fa fa-eye" :title="entry.public"></i> <i v-if="entry.public=='private'" class="fa fa-eye-slash" :title="entry.public"></i> <i v-if="entry.public=='unknown'" class="fa fa-low-vision" :title="entry.public"></i> </td> </tr> </tbody> </table> </div> </div> <script type="text/javascript"> var caSearch = new Vue({ el: '#ca-search', data: { tableHeaders: [ {'field': 'cn', 'description': 'Name'}, {'field': 'emailaddresses', 'description': 'E-Mail'}, {'field': 'notafterepoch', 'description': 'Ablaufdatum'}, {'field': 'valid', 'description': ''}, {'field': 'type', 'description': ''}, {'field': 'public', 'description': '', 'hideInPublicSearch': true}, ], resultData: [], loading: false, error: false, query: '', sortKey: String, sortOrders: {}, visitorLoggedIn: false, visitorGivenName: null, visitorSn: null, visitorMail: null, visitorEppn: null, filterShow: { 'validity': { 'name': 'Gültigkeit', 'filters': { 'v_valid': { 'displayName': 'gültig', 'displayIcon': 'fa-check', filterfunction: function(element) {return element.valid != 'valid' }, 'buttonState': true, 'resultCount': 0 }, 'v_expired': { 'displayName': 'abgelaufen', 'displayIcon': 'fa-times', filterfunction: function(element) {return element.valid != 'expired' }, 'buttonState': false, 'resultCount': 0 }, 'v_revoked': { 'displayName': 'gesperrt', 'displayIcon': 'fa-ban', filterfunction: function(element) {return element.valid != 'revoked' }, 'buttonState': false, 'resultCount': 0 } } }, 'types': { 'name': 'Typ', 'filters': { 't_user': { 'displayName': 'Nutzer', 'displayIcon': 'fa-user', filterfunction: function(element) {return element.type != 'Benutzer' }, 'buttonState': true, 'resultCount': 0 }, 't_pseudonym': { 'displayName': 'Pseudonym', 'displayIcon': 'fa-user-secret', filterfunction: function(element) {return element.type != 'Pseudonym' }, 'buttonState': true, 'resultCount': 0 }, 't_group': { 'displayName': 'Gruppe', 'displayIcon': 'fa-users', filterfunction: function(element) {return element.type != 'Gruppe' }, 'buttonState': true, 'resultCount': 0 }, 't_extern': { 'displayName': 'Extern', 'displayIcon': 'fa-user-plus', filterfunction: function(element) {return element.type != 'Extern' }, 'buttonState': false, 'resultCount': 0 }, 't_server': { 'displayName': 'Server', 'displayIcon': 'fa-server', 'hideInPublicSearch': true, filterfunction: function(element) {return element.type != 'Server' }, 'buttonState': false, 'resultCount': 0 } } }, 'visibility': { 'name': 'Sichtbarkeit', 'hideInPublicSearch': true, 'filters': { 'vis_public': { 'displayName': 'öffentlich', 'displayIcon': 'fa-eye', filterfunction: function(element) {return element.public != 'public' }, 'buttonState': true, 'resultCount': 0 }, 'vis_private': { 'displayName': 'nicht-öffentlich', 'displayIcon': 'fa-eye-slash', filterfunction: function(element) {return element.public == 'public' }, 'buttonState': true, 'resultCount': 0 } } } } }, computed: { filteredData: function () { var sortKey = this.sortKey var order = this.sortOrders[sortKey] || 1 var data = this.resultData // filter for (var filterCategory in this.filterShow) { for (var elem in this.filterShow[filterCategory].filters) { // TODO: UUGLLYYYYY. Fix it. var filter = this.filterShow[filterCategory].filters[elem] if (filter.buttonState == false) { data = data.filter(filter.filterfunction) } } } // sort if (sortKey) { data = data.slice().sort(function (a, b) { a = a[sortKey] b = b[sortKey] return (a === b ? 0 : a > b ? 1 : -1) * order }) } return data } }, methods: { search: function() { this.error = ''; this.resultData = []; // Let the UI know we're busy this.loading = true; if (this.visitorLoggedIn) { url = location.protocol + '//' + location.host + '/search/v1/json?q='; } else { url = location.protocol + '//' + location.host + '/pubsearch/v1/json?q='; } // Query our API fetch(url + encodeURIComponent(this.query)).then(async (response) => { let parsedBody; if (response.headers.get("content-type") === "application/json") { parsedBody = await response.json(); } else { parsedBody = await response.text(); } response.status !== 200 ? this.error = parsedBody : this.resultData = parsedBody.results; // Let the UI know we're done this.loading = false; // bin search results this.resultData.forEach( function (entry) { for (var filterCategory in this.filterShow) { for (var elem in this.filterShow[filterCategory].filters) { var filter = this.filterShow[filterCategory].filters[elem] if (!filter.filterfunction(entry)) { filter.resultCount++; } } } }, this ) } ); // TODO: error handling }, // provisorium. TODO: replace with URLSearchParams polyfill or VueRouter getUrlParameter: function(name) { name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); var regex = new RegExp('[\\?&]' + name + '=([^&#]*)'); var results = regex.exec(location.search); return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' ')); }, getShibSessionData: function() { url = location.protocol + '//' + location.host + '/Shibboleth.sso/Session'; // Query our API fetch(url).then( async (response) => { //var res = JSON.parse(response.body); //TODO: this is only needed for local development var res = await response.json(); //TODO if (res.hasOwnProperty('expiration') && res.hasOwnProperty('attributes')) { this.visitorLoggedIn = true; res.attributes.forEach( function(item) { switch (item.name) { case 'givenName': this.visitorGivenName = item.values[0]; break; case 'sn': this.visitorSn = item.values[0]; break; case 'mail': this.visitorMail = item.values[0]; break; case 'eppn': this.visitorEppn = item.values[0]; break; } }, this ); this.search(); // run search again, cause it might give different results, now that we're logged in. } else { this.visitorLoggedIn = false; } }, function(response) { // handle error console.log('error') } ); /* }, response => { // error callback if (response.status == 401) { this.visitorLoggedIn = false; this.visitorName = null; this.visitorEmail = null; } else { this.error = "Couldn't figure out whether or not you're logged in. Will assume that you're not." } */ }, enableFilter: function(filter) { filter.buttonState = !filter.buttonState }, disableAllFilters: function() { this.setAllFilters(true) }, enableAllFilters: function() { this.setAllFilters(false) }, enableOnlyFilter: function(filter, filters) { this.setAllFilters(false, filters) this.enableFilter(filter) }, setAllFilters: function(state, filters) { if (filters) { // modify only the explicit set that was given for (var elem in filters) { filters[elem].buttonState = state } return } else { // modify all the filters we have for (var filterCategory in this.filterShow) { for (var elem in this.filterShow[filterCategory].filters) { this.filterShow[filterCategory].filters[elem].buttonState = state // TODO: check type of "state" } } } }, sortBy: function (key) { this.sortKey = key this.sortOrders[key] = this.sortOrders[key] * -1 } }, created: function () { var sortOrders = {}; this.tableHeaders.forEach(function (key) { sortOrders[key.field] = 1; }); this.sortOrders = sortOrders; this.getShibSessionData(); if (this.getUrlParameter('filter') == 'none') { this.disableAllFilters() } this.query = this.getUrlParameter('q') if (this.query != '') { this.search() // this should ideally be bound to a promise of "getShibSessionData()", so we don't have to run the search twice to avoid concurrency glitches. } } }); </script> </body>