PageSourceSearch

https://davidjackson.info/static/components/token/controllers/tokenControllers.js?v=20250709T184513

js davidjackson.info collected 2026-10-02 19:50:29 UTC 37,963 bytes, 867 lines download raw bytes

1/**
2 * http://www.privacyidea.org
3 * (c) cornelius kölbel, [email protected]
4 *
5 * 2015-01-11 Cornelius Kölbel, <[email protected]>
6 *
7 * This code is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
9 * License as published by the Free Software Foundation; either
10 * version 3 of the License, or any later version.
11 *
12 * This code is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public
18 * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 *
20 */
21
22myApp.controller("tokenMenuController", ['$scope', '$location', '$rootScope', 'AuthFactory', 'ConfigFactory',
23    function ($scope, $location, $rootScope, AuthFactory, ConfigFactory) {
24        $scope.loggedInUser = AuthFactory.getUser();
25
26        // set default path
27        if ($location.path() === "/token") {
28            $location.path("/token/list");
29            $scope.tokenMenu = true;
30        }
31
32        // watch the location to change the side menu from token to container
33        $rootScope.$on('$locationChangeSuccess', function () {
34            if ($location.path().includes("container")) {
35                $scope.tokenMenu = false;
36            } else {
37                $scope.tokenMenu = true;
38            }
39        })
40    }]);
41
42myApp.controller("tokenController", ['TokenFactory', 'ConfigFactory', '$scope',
43    '$location', 'AuthFactory', 'instanceUrl', '$rootScope',
44    function (TokenFactory, ConfigFactory, $scope, $location, AuthFactory, instanceUrl, $rootScope) {
45        $scope.tokensPerPage = $scope.token_page_size;
46        $scope.params = {page: 1, sortdir: "asc"};
47        $scope.reverse = false;
48        $scope.loggedInUser = AuthFactory.getUser();
49        $scope.selectedToken = {serial: null};
50        $scope.clientpart = "";
51
52        // Change the pagination
53        $scope.pageChanged = function () {
54            //debug: console.log('Page changed to: ' + $scope.params.page);
55            $scope.get();
56        };
57
58        // This function fills $scope.tokendata
59        $scope.get = function (live_search) {
60            if ((!$rootScope.search_on_enter) || ($rootScope.search_on_enter && !live_search)) {
61                $scope.params.serial = "*" + ($scope.serialFilter || "") + "*";
62                $scope.params.tokenrealm = "*" + ($scope.tokenrealmFilter || "") + "*";
63                $scope.params.type = "*" + ($scope.typeFilter || "") + "*";
64                $scope.params.description = "*" + ($scope.descriptionFilter || "") + "*";
65                $scope.params.rollout_state = "*" + ($scope.rolloutStateFilter || "") + "*";
66                $scope.params.userid = "*" + ($scope.userIdFilter || "") + "*";
67                $scope.params.resolver = "*" + ($scope.resolverFilter || "") + "*";
68                $scope.params.pagesize = $scope.token_page_size;
69                $scope.params.sortby = $scope.sortby;
70                if ($scope.reverse) {
71                    $scope.params.sortdir = "desc";
72                } else {
73                    $scope.params.sortdir = "asc";
74                }
75                TokenFactory.getTokens(function (data) {
76                    if (data) {
77                        $scope.tokendata = data.result.value;
78                    }
79                }, $scope.params);
80            }
81        };
82
83        // single token function
84        $scope.reset = function (serial) {
85            TokenFactory.reset(serial, $scope.get);
86        };
87        $scope.disable = function (serial) {
88            TokenFactory.disable(serial, $scope.get);
89        };
90        $scope.enable = function (serial) {
91            TokenFactory.enable(serial, $scope.get);
92        };
93
94        // go to the list view by default
95        if ($location.path() === "/token") {
96            $location.path("/token/list");
97        }
98        if ($location.path() === "/token/list") {
99            $scope.get();
100        }
101        // go to token.wizard, if the wizard is defined
102        if ($scope.token_wizard) {
103            $location.path("/token/wizard");
104        }
105
106        // go to change PIN, if we should change the PIN
107        if ($scope.pin_change) {
108            $location.path("/pinchange");
109        }
110
111        // listen to the reload broadcast
112        $scope.$on("piReload", function () {
113            /* Due to the parameter "live_search" in the get function
114            we can not bind the get-function to piReload below. This
115            will break in Chrome, would work in Firefox.
116            So we need this wrapper function
117            */
118            $scope.get();
119        });
120
121    }]);
122
123
124myApp.controller("tokenAssignController", ['$scope', 'TokenFactory',
125    '$stateParams', 'AuthFactory', 'UserFactory', '$state',
126    function tokenAssignController($scope, TokenFactory, $stateParams, AuthFactory, UserFactory, $state) {
127        $scope.assignToken = function () {
128            TokenFactory.assign({
129                serial: fixSerial($scope.newToken.serial),
130                pin: $scope.newToken.pin
131            }, function () {
132                $state.go('token.list');
133            });
134        };
135    }]);
136
137myApp.controller("tokenEnrollController", ["$scope", "TokenFactory", "$timeout", "$stateParams", "AuthFactory",
138    "UserFactory", "$state", "ConfigFactory", "instanceUrl", "$http", "hotkeys", "gettextCatalog", "inform",
139    "U2fFactory", "webAuthnToken", "versioningSuffixProvider", "$location",
140    function tokenEnrollController($scope, TokenFactory, $timeout, $stateParams, AuthFactory, UserFactory, $state,
141                                   ConfigFactory, instanceUrl, $http, hotkeys, gettextCatalog, inform, U2fFactory,
142                                   webAuthnToken, versioningSuffixProvider, $location) {
143
144        hotkeys.bindTo($scope).add({
145            combo: 'alt+e',
146            description: gettextCatalog.getString('Enroll a new token'),
147            callback: function (event, hotkey) {
148                event.preventDefault();
149                $state.go('token.enroll');
150                $scope.enrolledToken = null;
151            }
152        });
153        hotkeys.bindTo($scope).add({
154            combo: 'alt+r',
155            description: gettextCatalog.getString('Roll the token'),
156            callback: function () {
157                $scope.enrollToken();
158            }
159        });
160
161        $scope.qrCodeWidth = 250;
162
163        // Available SMS gateways. We do this here to avoid javascript loops
164        $scope.smsGateways = $scope.getRightsValue('sms_gateways', '').split(' ');
165
166        if ($state.includes('token.wizard') && !$scope.show_seed) {
167            $scope.qrCodeWidth = 300;
168        }
169        $scope.checkRight = AuthFactory.checkRight;
170        $scope.loggedInUser = AuthFactory.getUser();
171        $scope.newUser = {};
172        $scope.tempData = {};
173        $scope.instanceUrl = instanceUrl;
174        $scope.click_wait = true;
175        $scope.U2FToken = {};
176        $scope.webAuthnToken = {};
177        // System default values for enrollment
178        $scope.systemDefault = {};
179        // questions for questionnaire token
180        $scope.questions = [];
181        $scope.num_questions = 5;
182        $scope.fileVersionSuffix = versioningSuffixProvider.$get();
183        // These are values that are also sent to the backend!
184        $scope.form = {
185            timeStep: 30,
186            otplen: 6,
187            genkey: true,
188            type: $scope.default_tokentype,
189            hashlib: "sha1",
190            'radius.system_settings': true,
191            container_serial: null,
192        };
193        if ($state.includes('token.rollover')) {
194            $scope.form.serial = $stateParams.tokenSerial;
195            $scope.form.type = $stateParams.tokenType;
196            $scope.form.container_serial = $stateParams.containerSerial;
197        }
198        $scope.vasco = {
199            // Note: A primitive does not work in the ng-model of the checkbox!
200            useIt: false
201        };
202        $scope.enrolling = false;
203        $scope.containerSerial = $stateParams.containerSerial;
204
205        $scope.formInit = {
206            tokenTypes: {},  // will be set later with response from server
207            timesteps: [30, 60],
208            otplens: [6, 8],
209            hashlibs: ["sha1", "sha256", "sha512"],
210            service_ids: {}
211        };
212
213        $scope.loadAvailableServiceIDs = function () {
214            ConfigFactory.getServiceid("", function (data) {
215                let serviceids = data.result.value;
216                angular.forEach(serviceids, function (serviceid_data, name) {
217                    $scope.formInit.service_ids[name] = name + ": " + serviceid_data.description;
218                });
219            })
220        }
221
222        $scope.setVascoSerial = function () {
223            if ($scope.form.otpkey.length === 496) {
224                //console.log('DEBUG: got 496 hexlify otpkey, check vasco serialnumber!');
225
226                // convert hexlified input blob to ascii and use the serialnumber (first 10 chars)
227                const vasco_hex = $scope.form.otpkey.toString();//force conversion
228                let vasco_otpstr = '';
229                for (let i = 0; i < vasco_hex.length; i += 2)
230                    vasco_otpstr += String.fromCharCode(parseInt(vasco_hex.substr(i, 2), 16));
231                const vasco_serial = vasco_otpstr.slice(0, 10);
232                //console.log(vasco_serial);
233                $scope.vascoSerial = vasco_serial;
234                if ($scope.vasco.useIt) {
235                    $scope.form.serial = vasco_serial;
236                } else {
237                    delete $scope.form.serial;
238                }
239            } else {
240                // If we do not have 496 characters this might be no correct vasco blob.
241                // So we reset the serial
242                $scope.vascoSerial = "";
243                delete $scope.form.serial;
244            }
245        };
246
247        // These token need to PIN
248        // TODO: This is also contained in the tokentype class!
249        $scope.changeTokenType = function () {
250            //debug: console.log("Token Type Changed.");
251            $scope.hidePin = ["sshkey", "certificate"].indexOf($scope.form.type) >= 0;
252            if ($scope.form.type === "hotp") {
253                // preset HOTP hashlib
254                $scope.form.hashlib = $scope.systemDefault['hotp.hashlib'] || 'sha1';
255            } else if ($scope.form.type === "totp") {
256                // preset TOTP hashlib
257                $scope.form.hashlib = $scope.systemDefault['totp.hashlib'] || 'sha1';
258                $scope.form.timeStep = parseInt($scope.systemDefault['totp.timeStep'] || '30');
259            } else if ($scope.form.type === "daypassword") {
260                // preset DayPassword hashlib
261                $scope.form.hashlib = $scope.systemDefault['daypassword.hashlib'] || 'sha1';
262                $scope.form.timeStep = parseInt($scope.systemDefault['daypassword.timeStep'] || '60');
263            }
264            $scope.form.genkey = $scope.form.type !== "vasco";
265            if ($scope.form.type === "applspec") {
266                $scope.loadAvailableServiceIDs();
267            }
268            if ($scope.form.type === "yubikey") {
269                // save the original otp length
270                $scope.old_otplen = $scope.form.otplen;
271                // set the default otp length for yubikeys in AES mode to 44
272                // (12 characters (6 bytes) UID and 32 characters (16 bytes) OTP)
273                $scope.form.otplen = 44;
274            } else {
275                // restore old otp length if available
276                if (typeof $scope.old_otplen != "undefined") {
277                    $scope.form.otplen = $scope.old_otplen;
278                    delete $scope.old_otplen;
279                }
280            }
281
282            $scope.preset_indexedsecret();
283
284            if ($scope.form.type === "radius") {
285                // only load RADIUS servers when the user actually tries to enroll a RADIUS token,
286                // because the user might not be allowed to list RADIUS servers
287                $scope.getRADIUSIdentifiers();
288            }
289            if ($scope.form.type === "remote") {
290                // fetch the privacyIDEA servers
291                $scope.getPrivacyIDEAServers();
292            }
293            if ($scope.form.type === "certificate") {
294                $scope.getCAConnectors();
295            }
296            // preset twostep enrollment
297            $scope.setTwostepEnrollmentDefault();
298        };
299
300        // helper function for setting indexed secret attribute
301        $scope.preset_indexedsecret = function () {
302            if ($scope.form.type === "indexedsecret") {
303                // in case of indexedsecret we do never generate a key from the UI
304                $scope.form.genkey = false;
305                // Only fetch, if a preset_attribute is defined
306                if ($scope.tokensettings.indexedsecret.preset_attribute) {
307                    // In case of a normal logged in user, an empty params is fine
308                    let params = {};
309                    if (AuthFactory.getRole() === 'admin') {
310                        params = {
311                            realm: $scope.newUser.realm,
312                            username: fixUser($scope.newUser.user)
313                        };
314                    }
315                    UserFactory.getUsers(params,
316                        function (data) {
317                            const userObject = data.result.value[0];
318                            // preset for indexedsecret token
319                            $scope.form.otpkey = userObject[$scope.tokensettings.indexedsecret.preset_attribute];
320                        });
321                }
322            }
323        };
324
325        // Set the default value of the "2stepinit" field if twostep enrollment should be forced
326        $scope.setTwostepEnrollmentDefault = function () {
327            $scope.form["2stepinit"] = $scope.checkRight($scope.form.type + "_2step=force");
328        };
329
330        // Initially set the default value
331        $scope.setTwostepEnrollmentDefault();
332
333        // A watch function to change the form data in case another user is selected
334        $scope.$watch(function (scope) {
335                return scope.newUser.email;
336            },
337            function (newValue, oldValue) {
338                if (newValue !== '') {
339                    $scope.form.email = newValue;
340                }
341            });
342        $scope.$watch(function (scope) {
343                return scope.newUser.mobile;
344            },
345            function (newValue, oldValue) {
346                if (newValue !== '') {
347                    $scope.form.phone = newValue;
348                }
349            });
350        $scope.$watch(function (scope) {
351                return fixUser(scope.newUser.user);
352            },
353            function (newValue, oldValue) {
354                // The newUser was changed
355                $scope.preset_indexedsecret();
356            });
357
358        // Helper function to populate user information
359        $scope.get_user_infos = function (data) {
360            const userObject = data.result.value[0];
361            $scope.form.email = userObject.email;
362            if (typeof userObject.mobile === "string") {
363                $scope.form.phone = userObject.mobile;
364            } else {
365                $scope.phone_list = userObject.mobile;
366                if ($scope.phone_list && $scope.phone_list.length === 1) {
367                    $scope.form.phone = $scope.phone_list[0];
368                }
369            }
370            return userObject;
371        }
372
373        // Get the realms and fill the realm dropdown box
374        if (AuthFactory.getRole() === 'admin') {
375            ConfigFactory.getRealms(function (data) {
376                $scope.realms = data.result.value;
377                // Set the default realm
378                angular.forEach($scope.realms, function (realm, realmname) {
379                    // if there is a default realm, preset the default realm
380                    if (realm.default && !$stateParams.realmname) {
381                        $scope.newUser = {user: "", realm: realmname};
382                    }
383                });
384
385                // init the user, if token.enroll was called from the user.details
386                if ($stateParams.realmname) {
387                    $scope.newUser.realm = $stateParams.realmname;
388                }
389                if ($stateParams.username) {
390                    $scope.newUser.user = $stateParams.username;
391                    // preset the mobile and email for SMS or EMAIL token
392                    UserFactory.getUsers({
393                            realm: $scope.newUser.realm,
394                            username: $scope.newUser.user
395                        },
396                        function (data) {
397                            $scope.get_user_infos(data)
398                        });
399                }
400            });
401        } else if (AuthFactory.getRole() === 'user') {
402            // init the user, if token.enroll was called as a normal user
403            $scope.newUser.user = AuthFactory.getUser().username;
404            $scope.newUser.realm = AuthFactory.getUser().realm;
405            if ($scope.checkRight('userlist')) {
406                UserFactory.getUserDetails({}, function (data) {
407                    $scope.User = $scope.get_user_infos(data);
408                });
409            }
410        }
411
412        // Read the tokentypes from the server
413        TokenFactory.getEnrollTokens(function (data) {
414            //console.log("getEnrollTokens");
415            //console.log(data);
416            $scope.formInit["tokenTypes"] = data.result.value;
417            // set the default tokentype
418            if (!$scope.formInit.tokenTypes.hasOwnProperty(
419                $scope.default_tokentype)) {
420                // if HOTP does not exist, we set another default type
421                for (const tkey in $scope.formInit.tokenTypes) {
422                    // set the first key to be the default tokentype
423                    $scope.form.type = tkey;
424                    // Set the 2step enrollment value
425                    $scope.setTwostepEnrollmentDefault();
426                    // Initialize token specific settings
427                    $scope.changeTokenType();
428                    break;
429                }
430            }
431        });
432
433        $scope.CAConnectors = [];
434        $scope.CATemplates = {};
435        $scope.radioCSR = 'csrgenerate';
436
437        // default enrollment callback
438        $scope.callback = function (data) {
439            let blob;
440            $scope.U2FToken = {};
441            $scope.webAuthnToken = {};
442            $scope.enrolledToken = data.detail;
443            $scope.click_wait = false;
444            if ($scope.enrolledToken.otps) {
445                const otps_count = Object.keys($scope.enrolledToken.otps).length;
446                $scope.otp_row_count = parseInt(otps_count / 5 + 0.5);
447                $scope.otp_rows = Object.keys($scope.enrolledToken.otps).slice(0, $scope.otp_row_count);
448            } else {
449                $scope.otp_rows = null;
450            }
451            if ($scope.enrolledToken.certificate) {
452                blob = new Blob([$scope.enrolledToken.certificate],
453                    {type: 'text/plain'});
454                $scope.certificateBlob = (window.URL || window.webkitURL).createObjectURL(blob);
455            }
456            if ($scope.enrolledToken.pkcs12) {
457                const bytechars = atob($scope.enrolledToken.pkcs12);
458                const byteNumbers = new Array(bytechars.length);
459                for (let i = 0; i < bytechars.length; i++) {
460                    byteNumbers[i] = bytechars.charCodeAt(i);
461                }
462                const byteArray = new Uint8Array(byteNumbers);
463                blob = new Blob([byteArray], {type: 'application/x-pkcs12'});
464                $scope.pkcs12Blob = (window.URL || window.webkitURL).createObjectURL(blob);
465            }
466            if ($scope.enrolledToken.u2fRegisterRequest) {
467                // This is the first step of U2F registering, save serial.
468                $scope.serial = data.detail.serial;
469
470                $scope.register_fido($scope.enrolledToken.u2fRegisterRequest, U2fFactory, $scope.U2FToken);
471            }
472            if ($scope.enrolledToken.webAuthnRegisterRequest) {
473                // This is the first step of U2F registering, save serial.
474                $scope.serial = data.detail.serial;
475
476                $scope.register_fido($scope.enrolledToken.webAuthnRegisterRequest, webAuthnToken, $scope.webAuthnToken);
477            }
478            if ($scope.enrolledToken.rollout_state === "clientwait" && !$scope.form["2stepinit"]) {
479                $scope.pollTokenInfo();
480            }
481            // Passkey
482            $scope.bytesToBase64 = function (bytes) {
483                const binString = Array.from(bytes, (byte) =>
484                    String.fromCodePoint(byte),).join("");
485                return btoa(binString);
486            };
487            $scope.base64URLToBytes = function (base64URLString) {
488                const base64 = base64URLString.replace(/-/g, '+').replace(/_/g, '/');
489                const padLength = (4 - (base64.length % 4)) % 4;
490                const padded = base64.padEnd(base64.length + padLength, '=');
491                const binary = atob(padded);
492                const buffer = new ArrayBuffer(binary.length);
493                const bytes = new Uint8Array(buffer);
494                for (let i = 0; i < binary.length; i++) {
495                    bytes[i] = binary.charCodeAt(i);
496                }
497                return buffer;
498            }
499
500            if ($scope.enrolledToken.passkey_registration) {
501                $scope.click_wait = true;
502                //console.log($scope.enrolledToken.passkey_registration);
503                let options = $scope.enrolledToken.passkey_registration;
504                let excludedCredentials = [];
505                for (const cred of options.excludeCredentials) {
506                    excludedCredentials.push({
507                        id: $scope.base64URLToBytes(cred.id),
508                        type: cred.type,
509                    });
510                }
511                navigator.credentials.create({
512                    publicKey: {
513                        rp: options.rp,
514                        user: {
515                            id: $scope.base64URLToBytes(options.user.id),
516                            name: options.user.name,
517                            displayName: options.user.displayName
518                        },
519                        challenge: Uint8Array.from(options.challenge, c => c.charCodeAt(0)),
520                        pubKeyCredParams: options.pubKeyCredParams,
521                        excludeCredentials: excludedCredentials,
522                        authenticatorSelection: options.authenticatorSelection,
523                        timeout: options.timeout,
524                        extensions: {
525                            credProps: true,
526                        },
527                        attestation: options.attestation
528                    }
529                }).then(function (publicKeyCred) {
530                    //console.log("Successfully registered passkey");
531                    //console.log(publicKeyCred);
532                    let params = {
533                        user: $scope.newUser.user,
534                        realm: $scope.newUser.realm,
535                        transaction_id: data.detail.transaction_id,
536                        serial: data.detail.serial,
537                        type: "passkey",
538                        credential_id: publicKeyCred.id,
539                        rawId: $scope.bytesToBase64(new Uint8Array(publicKeyCred.rawId)),
540                        authenticatorAttachment: publicKeyCred.authenticatorAttachment,
541                        attestationObject: $scope.bytesToBase64(
542                            new Uint8Array(publicKeyCred.response.attestationObject)),
543                        clientDataJSON: $scope.bytesToBase64(new Uint8Array(publicKeyCred.response.clientDataJSON)),
544                    }
545                    if (publicKeyCred.response.attestationObject) {
546                        params.attestationObject = $scope.bytesToBase64(
547                            new Uint8Array(publicKeyCred.response.attestationObject));
548                    }
549                    const extResults = publicKeyCred.getClientExtensionResults();
550                    if (extResults.credProps) {
551                        params.credProps = extResults.credProps;
552                    }
553                    TokenFactory.initToken(params, function (response) {
554                        $scope.click_wait = false;
555                    });
556                }, function (error) {
557                    console.log("Error while registering passkey");
558                    console.log(error);
559                    inform.add("Error while registering passkey, the token will not be created!",
560                        {type: "danger", ttl: 10000});
561                    if (AuthFactory.checkRight("delete")) {
562                        TokenFactory.delete(data.detail.serial, function (response) {
563                            $state.go('token.list');
564                        });
565                    }
566                });
567            }
568            // End Passkey
569            $('html,body').scrollTop(0);
570        }
571
572        $scope.enrollToken = function () {
573            $scope.enrolling = true;
574            //debug: console.log($scope.newUser.user);
575            //debug: console.log($scope.newUser.realm);
576            //debug: console.log($scope.newUser.pin);
577            $scope.newUser.user = fixUser($scope.newUser.user);
578            // convert the date object to a string
579            $scope.form.validity_period_start = date_object_to_string($scope.form.validity_period_start);
580            $scope.form.validity_period_end = date_object_to_string($scope.form.validity_period_end);
581
582            if ($scope.containerSerial !== "createnew" && $scope.containerSerial !== "none") {
583                $scope.form.container_serial = $scope.containerSerial;
584            } else {
585                // Do not send the container_serial if it has no value
586                delete $scope.form.container_serial;
587            }
588
589            TokenFactory.enroll($scope.newUser,
590                $scope.form, $scope.callback,
591                function (data) {
592                    $scope.enrolling = false;
593                }
594            );
595        };
596
597        $scope.pollTokenInfo = function () {
598            TokenFactory.getTokenForSerial($scope.enrolledToken.serial, function (data) {
599                if (data.result.value && data.result.value.tokens && data.result.value.tokens.length > 0) {
600                    $scope.enrolledToken.rollout_state = data.result.value.tokens[0].rollout_state;
601                }
602                // Poll the data after 2.5 seconds again
603                if ($scope.enrolledToken.rollout_state === "clientwait" && $location.path().indexOf("/token/enroll") > -1) {
604                    $timeout($scope.pollTokenInfo, 2500);
605                }
606            })
607        };
608
609        $scope.regenerateToken = function (serial) {
610            const params = $scope.form;
611            if (serial) {
612                params.serial = serial;
613            } else {
614                params.serial = $scope.enrolledToken.serial;
615            }
616            TokenFactory.enroll(null, params, $scope.callback);
617        };
618
619        $scope.sendClientPart = function () {
620            const params = {
621                "otpkey": $scope.clientpart.replace(/ /g, ""),
622                "otpkeyformat": "base32check",
623                "serial": $scope.enrolledToken.serial,
624                "type": $scope.form.type,
625                // Send the rollover parameter as well to avoid a possible PIN check
626                "rollover": $scope.form.rollover
627            };
628            TokenFactory.enroll($scope.newUser, params, function (data) {
629                $scope.clientpart = "";
630                $scope.callback(data);
631            });
632        };
633
634        $scope.sendVerifyResponse = function () {
635            const params = {
636                "serial": $scope.enrolledToken.serial,
637                "verify": $scope.verifyResponse,
638                "type": $scope.form.type
639            };
640            TokenFactory.enroll($scope.newUser, params, function (data) {
641                if (data.result.value === true) {
642                    inform.add(gettextCatalog.getString("Token successfully verified"),
643                        {type: "success", ttl: 10000});
644                }
645                $scope.verifyResponse = "";
646                $scope.callback(data);
647            });
648        };
649
650        // Special Token functions
651        $scope.sshkeyChanged = function () {
652            const keyArr = $scope.form.sshkey.split(" ");
653            $scope.form.description = keyArr.slice(2).join(" ");
654        };
655
656        $scope.yubikeyGetLen = function () {
657            let yktestdatalen = $scope.tempData.yubikeyTest.trim().length;
658            if (yktestdatalen >= 32) {
659                $scope.form.otplen = yktestdatalen;
660            }
661        };
662
663        // U2F and WebAuthn
664        $scope.register_fido = function (registerRequest, Factory, token) {
665            // We need to send the 2nd stage of the U2F enroll
666            Factory.register_request(registerRequest, function (params) {
667                params.serial = $scope.serial;
668                TokenFactory.enroll($scope.newUser,
669                    params, function (response) {
670                        $scope.click_wait = false;
671                        token.subject
672                            = (response.detail.u2fRegisterResponse || response.detail.webAuthnRegisterResponse).subject;
673                        token.vendor = token.subject.split(" ")[0];
674                        //console.log(token);
675                    });
676            }, function (error) {
677                if (AuthFactory.checkRight("delete")) {
678                    TokenFactory.delete($scope.serial, function (response) {
679                        $state.go('token.list');
680                    });
681                }
682            });
683            $scope.click_wait = true;
684        };
685
686        // get the list of configured RADIUS server identifiers
687        $scope.getRADIUSIdentifiers = function () {
688            ConfigFactory.getRadiusNames(function (data) {
689                $scope.radiusIdentifiers = data.result.value;
690            });
691        };
692
693        // get the list of configured privacyIDEA server identifiers
694        $scope.getPrivacyIDEAServers = function () {
695            ConfigFactory.getPrivacyidea(function (data) {
696                $scope.privacyIDEAServers = data.result.value;
697            });
698        };
699
700        // get the list of configured CA connectors
701        $scope.getCAConnectors = function () {
702            ConfigFactory.getCAConnectorNames(function (data) {
703                const CAConnectors = data.result.value;
704                angular.forEach(CAConnectors, function (value, key) {
705                    $scope.CAConnectors.push(value.connectorname);
706                    $scope.form.ca = value.connectorname;
707                    $scope.CATemplates[value.connectorname] = value;
708                });
709                //debug: console.log($scope.CAConnectors);
710            });
711        };
712
713        // If the user is admin, he can read the config.
714        ConfigFactory.loadSystemConfig(function (data) {
715            /* Default config values like
716                radius.server, radius.secret...
717               are stored in systemDefault and $scope.form
718             */
719            $scope.systemDefault = data.result.value;
720            //debug: console.log("system default config");
721            //debug: console.log(systemDefault);
722            // TODO: The entries should be handled automatically.
723            const entries = ["radius.server", "radius.secret", "remote.server",
724                "radius.identifier", "email.mailserver",
725                "email.mailfrom", "yubico.id", "tiqr.regServer"];
726            entries.forEach(function (entry) {
727                if (!$scope.form[entry]) {
728                    // preset the UI
729                    $scope.form[entry] = $scope.systemDefault[entry];
730                }
731            });
732            // Default HOTP hashlib
733            $scope.form.hashlib = $scope.systemDefault["hotp.hashlib"] || 'sha1';
734            // Now add the questions
735            angular.forEach($scope.systemDefault, function (value, key) {
736                if (key.indexOf("question.question.") === 0) {
737                    $scope.questions.push(value);
738                }
739            });
740            $scope.num_answers = $scope.systemDefault["question.num_answers"];
741            //debug: console.log($scope.questions);
742            //debug: console.log($scope.form);
743        });
744
745        // open the window to generate the key pair
746        $scope.openCertificateWindow = function () {
747            const params = {
748                authtoken: AuthFactory.getAuthToken(),
749                ca: $scope.form.ca
750            };
751            const tabWindowId = window.open('about:blank', '_blank');
752            $http.post(instanceUrl + '/certificate', params).then(
753                function (response) {
754                    //debug: console.log(response);
755                    tabWindowId.document.write(response.data);
756                    //tabWindowId.location.href = response.headers('Location');
757                });
758        };
759
760        // print the paper token
761        $scope.printOtp = function () {
762            const serial = $scope.enrolledToken.serial;
763            const myWindow = window.open('', 'otpPrintingWindow', 'height=400,width=600');
764            const css = '<link' +
765                ' href="' + instanceUrl +
766                '/static/css/papertoken.css"' +
767                ' rel="stylesheet">';
768            myWindow.document.write('<html><head><title>' + serial + '</title>');
769            myWindow.document.write(css);
770            myWindow.document.write('</head>' +
771                '<body onload="window.print(); window.close()">');
772            myWindow.document.write($('#paperOtpTable').html());
773            myWindow.document.write('</body></html>');
774            myWindow.document.close(); // necessary for IE >= 10
775            myWindow.focus(); // necessary for IE >= 10
776            return true;
777        };
778
779        $scope.copyPKCS12PasswordToClipboard = function (text) {
780            navigator.clipboard.writeText(text).then(function () {
781                inform.add(gettextCatalog.getString("PKCS12 Password copied to clipboard"),
782                    {type: "info", ttl: 3000})
783            });
784        }
785
786        // ===========================================================
787        // ===============  Date stuff ===============================
788        // ===========================================================
789
790        $scope.openDate = function ($event) {
791            $event.stopPropagation();
792            return true;
793        };
794
795        $scope.today = new Date();
796        $scope.dateOptions = {
797            formatYear: 'yy',
798            startingDay: 1
799        };
800    }
801]);
802
803myApp.controller("tokenImportController", ['$scope', 'Upload', 'AuthFactory', 'tokenUrl', 'ConfigFactory', 'inform',
804    'gettextCatalog',
805    function ($scope, Upload, AuthFactory, tokenUrl, ConfigFactory, inform, gettextCatalog) {
806        $scope.formInit = {
807            fileTypes: ["aladdin-xml", "OATH CSV", "Yubikey CSV", "pskc"]
808        };
809
810        $scope.verify_pskc_opts = {
811            no_check: gettextCatalog.getString('Do not verify the authenticity'),
812            check_fail_soft: gettextCatalog.getString('Skip tokens that can not be verified'),
813            check_fail_hard: gettextCatalog.getString('Abort operation on unverifiable token'),
814        }
815
816        // These are values that are also sent to the backend!
817        $scope.form = {
818            type: "OATH CSV",
819            realm: ""
820        };
821
822        // get Realms
823        ConfigFactory.getRealms(function (data) {
824            $scope.realms = data.result.value;
825            // Preset the default realm
826            angular.forEach($scope.realms, function (realm, realmname) {
827                if (realm.default) {
828                    $scope.form.realm = realmname;
829                }
830            });
831        });
832
833        // get PGP keys
834        ConfigFactory.getPGPKeys(function (data) {
835            $scope.pgpkeys = data.result.value;
836        });
837
838        $scope.upload = function (file) {
839            if (file) {
840                Upload.upload({
841                    url: tokenUrl + '/load/filename',
842                    headers: {'PI-Authorization': AuthFactory.getAuthToken()},
843                    data: {
844                        file: file,
845                        type: $scope.form.type,
846                        psk: $scope.form.psk,
847                        pskcValidateMAC: $scope.form.validateMAC,
848                        password: $scope.form.password,
849                        tokenrealms: $scope.form.realm
850                    },
851                }).then(function (resp) {
852                    $scope.uploadedFile = resp.config.data.file.name;
853                    $scope.uploadedTokens = resp.data.result.value.n_imported;
854                    $scope.notImportedTokens = resp.data.result.value.n_not_imported;
855                }, function (error) {
856                    if (error.data.result.error.code === -401) {
857                        $state.go('login');
858                    } else {
859                        inform.add(error.data.result.error.message,
860                            {type: "danger", ttl: 10000});
861                    }
862                }, function (evt) {
863                    $scope.uploadProgress = parseInt(100.0 * evt.loaded / evt.total)
864                });
865            }
866        };
867    }]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.