diff --git a/.gitmodules b/.gitmodules index 6f9ccfa7037066bdeda69184d6e836d400cd97b5..bfc4ea1c918b16999a633bc4c949951e7a45d091 100644 --- a/.gitmodules +++ b/.gitmodules @@ -29,3 +29,6 @@ [submodule "functions/parsedown"] path = functions/parsedown url = https://github.com/erusev/parsedown.git +[submodule "functions/webauthn-php"] + path = functions/webauthn-php + url = https://github.com/Firehed/webauthn-php.git diff --git a/app/admin/groups/edit-group-result.php b/app/admin/groups/edit-group-result.php index 585abfd4c0833146dff396ffc7d3f07cc08cef04..9528d929a2ea465cee6436574251935ab7d8c9e4 100755 --- a/app/admin/groups/edit-group-result.php +++ b/app/admin/groups/edit-group-result.php @@ -93,6 +93,4 @@ if (!is_blank($_POST['gmembers'])) { $Admin->add_group_to_user ($gid, $user->id); } } -} - -?> +} \ No newline at end of file diff --git a/app/admin/settings/index.php b/app/admin/settings/index.php index 7c8d95a123d3e545a66c8604a7df70f3fb8ece29..fc1d69ded7c53bac636d2e8f3f44bd1af6e53ccc 100755 --- a/app/admin/settings/index.php +++ b/app/admin/settings/index.php @@ -552,6 +552,17 @@ $(document).ready(function() { </td> </tr> +<!-- Passkeys --> +<tr> + <td class="title"><?php print _("Enable Passkeys"); ?></td> + <td> + <input type="checkbox" class="input-switch" value="1" name="passkeys" <?php if($settings['passkeys'] == 1) print 'checked'; ?>> + </td> + <td class="info2"> + <?php print _('Enable passkeys for passwordless login'); ?> + </td> +</tr> + <!-- ICPM --> <tr class="settings-title"> diff --git a/app/admin/settings/settings-save.php b/app/admin/settings/settings-save.php index 4526465322797d595c94c3af363e88c177ad6848..5a0797fec97ad392d649d99f42c31aa379790ff9 100755 --- a/app/admin/settings/settings-save.php +++ b/app/admin/settings/settings-save.php @@ -87,6 +87,7 @@ $values = array("id"=>1, "enforceUnique" =>$Admin->verify_checkbox(@$_POST['enforceUnique']), "enableRouting" =>$Admin->verify_checkbox(@$_POST['enableRouting']), "enableVaults" =>$Admin->verify_checkbox(@$_POST['enableVaults']), + "passkeys" =>$Admin->verify_checkbox(@$_POST['passkeys']), //"enableDHCP" =>$Admin->verify_checkbox(@$_POST['enableDHCP']), "enableFirewallZones" =>$Admin->verify_checkbox(@$_POST['enableFirewallZones']), "maintaneanceMode" =>$Admin->verify_checkbox(@$_POST['maintaneanceMode']), diff --git a/app/admin/users/edit-result.php b/app/admin/users/edit-result.php index 041e08395d662f9dd55065fcdd8ab9edcd2d239d..38a59bacd4ab1dcf37bf67080a8b743549d348ee 100755 --- a/app/admin/users/edit-result.php +++ b/app/admin/users/edit-result.php @@ -31,7 +31,6 @@ $User->Crypto->csrf_cookie ("validate", "user", $_POST['csrf_cookie']) === false $auth_method = $Admin->fetch_object ("usersAuthMethod", "id", $_POST['authMethod']); $auth_method!==false ? : $Result->show("danger", _("Invalid authentication method"), true); - /* checks */ # ID must be numeric @@ -106,7 +105,6 @@ if(sizeof($myFields) > 0) { $values = array( "id" =>@$_POST['userId'], "real_name" =>$_POST['real_name'], - "username" =>$_POST['username'], "email" =>$_POST['email'], "role" =>$_POST['role'], "authMethod" =>$_POST['authMethod'], @@ -118,6 +116,10 @@ $values = array( ); +# username only on add +if($_POST['action']=="add") { + $values['username'] = $_POST['username']; +} # custom fields if (sizeof($myFields)>0) { @@ -161,6 +163,27 @@ foreach ($User->get_modules_with_permissions() as $m) { # formulate permissions $values['module_permissions'] = json_encode($permissions); +# 2fa +if ($User->settings->{'2fa_provider'}!=='none') { + if(!isset($_POST['2fa'])) { + $values['2fa'] = 0; + $values['2fa_secret'] = NULL; + } +} + +# passkeys +$passkeys_to_remove = []; +foreach($_POST as $key=>$post) { + if(substr($key, 0,15) == "delete-passkey-") { + $passkeys_to_remove[] = str_replace("delete-passkey-", "", $key); + } +} + +# passkey only +if ($User->settings->{'passkeys'}==1) { + $values['passkey_only'] = !isset($_POST['passkey_only']) ? 0 : 1; +} + # execute if(!$Admin->object_modify("users", $_POST['action'], "id", $values)) { $Result->show("danger", _("User")." ".$_POST["action"]." "._("failed").'!', true); @@ -169,5 +192,13 @@ else { $Result->show("success", _("User")." ".$_POST["action"]." "._("successful").'!', false); } +# remove passkeys if required +if (sizeof($passkeys_to_remove)>0) { + // lalala + foreach ($passkeys_to_remove as $pk) { + $User->delete_passkey ($pk); + } +} + # mail user if($Admin->verify_checkbox(@$_POST['notifyUser'])!="0") { include("edit-notify.php"); } diff --git a/app/admin/users/edit.php b/app/admin/users/edit.php index 9cb461a3e4094b9f6c8d48a681bcc6c72c130011..b85c92f3830d369cd69b478e9df1d16023034f25 100755 --- a/app/admin/users/edit.php +++ b/app/admin/users/edit.php @@ -51,6 +51,12 @@ else { //set default lang $user['lang']=$User->settings->defaultLang; } + +# disabled +$disabled = $_POST['action']=="delete" ? "disabled" : ""; + +# passkeys +$user_passkeys = $User->get_user_passkeys($user['id']); ?> <script> @@ -84,16 +90,18 @@ $(document).ready(function(){ <!-- real name --> <tr> <td><?php print _('Real name'); ?></td> - <td><input type="text" class="form-control input-sm" name="real_name" value="<?php print @$user['real_name']; ?>"></td> + <td><input type="text" class="form-control input-sm" name="real_name" value="<?php print @$user['real_name']; ?>" <?php print $disabled; ?>></td> <td class="info2"><?php print _('Enter users real name'); ?></td> </tr> <!-- username --> <tr> <td><?php print _('Username'); ?></td> - <td><input type="text" class="form-control input-sm" name="username" value="<?php print @$user['username']; ?>" <?php if($_POST['action']=="edit"||$_POST['action']=="delete") print 'readonly'; ?>></td> + <td><input type="text" class="form-control input-sm" name="username" value="<?php print @$user['username']; ?>" <?php if($_POST['action']=="edit"||$_POST['action']=="delete") print 'readonly disabled'; ?> <?php print $disabled; ?>></td> <td class="info2"> + <?php if($_POST['action']=="add") { ?> <a class='btn btn-xs btn-default adsearchuser' rel='tooltip' title='Search AD for user details'><i class='fa fa-search'></i></a> + <?php } ?> <?php print _('Enter username'); ?> </td> </tr> @@ -101,10 +109,16 @@ $(document).ready(function(){ <!-- email --> <tr> <td><?php print _('e-mail'); ?></td> - <td><input type="text" class="form-control input-sm input-w-250" name="email" value="<?php print @$user['email']; ?>"></td> + <td><input type="text" class="form-control input-sm input-w-250" name="email" value="<?php print @$user['email']; ?>" <?php print $disabled; ?>></td> <td class="info2"><?php print _('Enter users email address'); ?></td> </tr> + <?php if($_POST['action']!="delete") { ?> + + <tr> + <td colspan="3"><hr></td> + </tr> + <!-- Status --> <tr> <td><?php print _('Status'); ?></td> @@ -159,6 +173,42 @@ $(document).ready(function(){ <td class="info2"><?php print _("Select authentication method for user"); ?></td> </tr> + <?php if ($User->settings->{'2fa_provider'}!=='none' && $user['2fa'] == "1") { ?> + + <tr> + <td style="padding-top:10px;"><?php print _('2fa enabled'); ?></td> + <td style="padding-top:10px;"><input type="checkbox" value="1" class="input-switch" name="2fa" <?php if($user['2fa'] == "1") { print 'checked'; } else { print "disabled"; } ?>></td> + <td style="padding-top:10px;" class="info2"><?php print _('Disable 2fa for user'); ?></td> + </tr> + <?php } ?> + + + <?php if ($User->settings->{'passkeys'}=="1" && sizeof($user_passkeys)>0 && $_POST['action']!=="delete") { ?> + <tr> + <td colspan="3"><hr></td> + <tr> + <td style="padding-top:10px;"><?php print _('Passkeys'); ?></td> + <td style="padding-top:10px;"> + <?php + foreach ($user_passkeys as $passkey) { + $passkey->comment = is_null($passkey->comment) ? "-- Unknown --" : $passkey->comment; + print "<input type='checkbox' name='delete-passkey-".$passkey->id."' value='1'> "; + print $User->strip_input_tags($passkey->comment)."<br>"; + } + ?> + </td> + <td style="padding-top:10px;" class="info2"><?php print _('Check passkey you want to remove'); ?></td> + </tr> + + <tr> + <td style="padding-top:10px;"><?php print _('Passkey login only'); ?></td> + <td style="padding-top:10px;"><input type="checkbox" value="1" class="input-switch" name="passkey_only" <?php if($user['passkey_only'] == "1") { print 'checked'; } ?>></td> + <td style="padding-top:10px;" class="info2"><?php print _('Select to only allow account login with passkey'); ?></td> + </tr> + </tr> + + <?php } ?> + <tr> <td colspan="3"><hr></td> </tr> @@ -407,6 +457,7 @@ $(document).ready(function(){ } ?> + <?php } ?> </table> </form> diff --git a/app/admin/users/print-all.php b/app/admin/users/print-all.php index 1fe6ee0efb64442aeb4733c910a2792d7d331670..89a2c469d4addab191ce3a23f6fa014d308f0ce2 100644 --- a/app/admin/users/print-all.php +++ b/app/admin/users/print-all.php @@ -12,7 +12,7 @@ $users = $Admin->fetch_all_objects("users", "username"); # fetch custom fields $custom = $Tools->fetch_custom_fields('users'); -/* check customfields */ +// check customfields $ffields = pf_json_decode($User->settings->hiddenCustomFields, true); $ffields = is_array(@$ffields['users']) ? $ffields['users'] : array(); ?> @@ -60,6 +60,15 @@ $ffields = is_array(@$ffields['users']) ? $ffields['users'] : array(); foreach ($users as $user) { //cast $user = (array) $user; + + // passkeys + if ($User->settings->{'passkeys'}=="1") { + // get user passkeys + $user_passkeys = $User->get_user_passkeys($user['id']); + // set passkey_only flag + $passkey_only = $User->settings->{'passkeys'}=="1" && sizeof($user_passkeys)>0 && $user['passkey_only']=="1" ? true : false; + } + print '<tr>' . "\n"; # set icon based on normal user or admin @@ -88,8 +97,27 @@ foreach ($users as $user) { $auth_method = $Admin->fetch_object("usersAuthMethod", "id", $user['authMethod']); //false print "<td>"; - if($auth_method===false) { print "<span class='text-muted'>No auth method</span>"; } - else { print $auth_method->type." <span class='text-muted'>(".$auth_method->description."</a>)"; } + if($auth_method===false) { print "<span class='text-muted'>No auth method</span>"; } + elseif($passkey_only) { print "<span class='badge badge1 badge5 alert-success'>"._("Passkey only")."</span>"; } + else { print "<span class='badge badge1 badge5 alert-success'>".$auth_method->type."</span> <span class='text-muted'>(".$auth_method->description."</a>)"; } + // 2fa + if ($User->settings->{'2fa_provider'}!=='none' && $passkey_only!==true) { + if (!is_null($user['2fa_secret']) && $user['2fa']=="1") { + print "<br><span class='badge badge1 badge5 alert-success'>"._("2fa enabled")."</span>"; + } + else { + print "<br><span class='badge badge1 badge5 alert-warning'>"._("2fa disabled")."</span>"; + } + } + + // passkeys + if ($User->settings->{'passkeys'}=="1") { + // get user passkeys + $user_passkeys = $User->get_user_passkeys($user['id']); + if (sizeof($user_passkeys)>0) { + print "<br><span class='badge badge1 badge5 alert-success'>".sizeof($user_passkeys)." "._("Passkeys")."</span>"; + } + } print "</span></td>"; # Module permisisons @@ -98,7 +126,9 @@ foreach ($users as $user) { } else { print "<td>"; + print "<btn class='btn btn-xs btn-default toggle-module-permissions'>Show <i class='fa fa-angle-down'></i></btn><div class='hidden module-permissions'>"; include("print_module_permissions.php"); + print "</div>"; print "</td>"; } diff --git a/app/admin/users/print_module_permissions.php b/app/admin/users/print_module_permissions.php index c223fac25ff1792ecd0eaebaca9c0921f4378033..4aceef18f61253f5b375fdd709cceea76c63baac 100644 --- a/app/admin/users/print_module_permissions.php +++ b/app/admin/users/print_module_permissions.php @@ -23,55 +23,76 @@ foreach ($User->get_modules_with_permissions() as $m) { } } -print "<table class='table-noborder popover_table'>"; - // VLAN -print "<tr><td>"._("VLAN")."</td><td>".$User->print_permission_badge($user['perm_vlan'])."</td></tr>"; - +$perm_names['perm_vlan'] = "VLAN"; // L2Domains -print "<tr><td>"._("L2Domains")."</td><td>".$User->print_permission_badge($user['perm_l2dom'])."</td></tr>"; - +$perm_names['perm_l2dom'] = "L2 Domains"; // VRF -print "<tr><td>"._("VRF")."</td><td>".$User->print_permission_badge($user['perm_vrf'])."</td></tr>"; - +$perm_names['perm_vrf'] = "VRF"; // PDNS if ($User->settings->enablePowerDNS==1) -print "<tr><td>"._("PowerDNS")."</td><td>".$User->print_permission_badge($user['perm_pdns'])."</td></tr>"; - +$perm_names['perm_pdns'] = "PowerDNS"; // Devices -print "<tr><td>"._("Devices")."</td><td>".$User->print_permission_badge($user['perm_devices'])."</td></tr>"; - +$perm_names['perm_devices'] = "Devices"; // Racks if ($User->settings->enableRACK==1) -print "<tr><td>"._("Racks")."</td><td>".$User->print_permission_badge($user['perm_racks'])."</td></tr>"; - +$perm_names['perm_racks'] = "Racks"; // Circuits if ($User->settings->enableCircuits==1) -print "<tr><td>"._("Circuits")."</td><td>".$User->print_permission_badge($user['perm_circuits'])."</td></tr>"; - +$perm_names['perm_circuits'] = "Circuits"; // NAT if ($User->settings->enableNAT==1) -print "<tr><td>"._("NAT")."</td><td>".$User->print_permission_badge($user['perm_nat'])."</td></tr>"; - +$perm_names['perm_nat'] = "NAT"; // Customers if ($User->settings->enableCustomers==1) -print "<tr><td>"._("Customers")."</td><td>".$User->print_permission_badge($user['perm_customers'])."</td></tr>"; - +$perm_names['perm_customers'] = "Customers"; // Locations if ($User->settings->enableLocations==1) -print "<tr><td>"._("Locations")."</td><td>".$User->print_permission_badge($user['perm_locations'])."</td></tr>"; - +$perm_names['perm_locations'] = "Locations"; // pstn if ($User->settings->enablePSTN==1) -print "<tr><td>"._("PSTN")."</td><td>".$User->print_permission_badge($user['perm_pstn'])."</td></tr>"; - +$perm_names['perm_pstn'] = "PSTN"; // routing if ($User->settings->enableRouting==1) -print "<tr><td>"._("Routing")."</td><td>".$User->print_permission_badge($user['perm_routing'])."</td></tr>"; - +$perm_names['perm_routing'] = "Routing"; // vaults if ($User->settings->enableVaults==1) -print "<tr><td>"._("Vaults")."</td><td>".$User->print_permission_badge($user['perm_vaults'])."</td></tr>"; - -print "</table>"; \ No newline at end of file +$perm_names['perm_vaults'] = "Vaults"; + + +// user page +if((@$_GET['page']=="administration" && @$_GET['section']=="users" && @$_GET['sPage']=="modules") || ($_GET['section']=="user-menu")) { + + print '<div class="panel panel-default" style="max-width:600px;min-width:350px;">'; + print '<div class="panel-heading">'._("User permissions for phpipam modules").'</div>'; + print ' <ul class="list-group">'; + + foreach ($user as $key=>$u) { + if(strpos($key, "perm_")!==false && array_key_exists($key, $perm_names)) { + print '<li class="list-group-item">'; + // title + print "<span style='padding-top:8px;' class='pull-l1eft'>"; + print "<strong>"._($perm_names[$key])."</strong>"; + print "</span>"; + // perms + print ' <strong class="btn-group pull-right">'; + print $User->print_permission_badge($user[$key]); + print ' </strong>'; + print '</li>'; + + print "<div class='clearfix'></div>"; + } + } + print ' </ul>'; + print '</div>'; +} +else { + print "<table class='table-noborder popover_table'>"; + foreach ($user as $key=>$u) { + if(strpos($key, "perm_")!==false && array_key_exists($key, $perm_names)) { + print "<tr><td>"._($perm_names[$key])."</td><td>".$User->print_permission_badge($user[$key])."</td></tr>"; + } + } + print "</table>"; +} \ No newline at end of file diff --git a/app/login/index.php b/app/login/index.php index 403e2c7ffeee30bbf10515e24e33e8a84c827d7b..7f5c2b3b66f92106df81df7a6eac400cf4b83448 100755 --- a/app/login/index.php +++ b/app/login/index.php @@ -133,41 +133,6 @@ if(@$config['requests_public']===false) { else { $_GET['subnetId'] = "404"; print "<div id='error'>"; include_once('app/error.php'); print "</div>"; } ?> - <!-- login response --> - <div id="loginCheck"> - <?php - # deauthenticate user - if ( $User->is_authenticated()===true ) { - # print result - if(isset($_GET['section']) && $_GET['section']=="timeout") - $Result->show("success", _('You session has timed out')); - else - $Result->show("success", _('You have logged out')); - - # write log - $Log->write( _("User logged out"), _("User")." ".$User->username." "._("has logged out"), 0, $User->username ); - - # destroy session - $User->destroy_session(); - } - - //check if SAML2 login is possible - $saml2settings=$Tools->fetch_object("usersAuthMethod", "type", "SAML2"); - - if ($saml2settings!=false) { - $version = pf_json_decode(@file_get_contents(dirname(__FILE__).'/../../functions/php-saml/src/Saml2/version.json'), true); - $version = $version['php-saml']['version']; - - if ($version < 3.4) { - $Result->show("danger", _('php-saml library missing, please update submodules')); - } else { - $Result->show("success", _('You can login with SAML2').' <a href="'.create_link('saml2').'">'._('here').'</a>!'); - } - } - - ?> - </div> - </div> </div> diff --git a/app/login/login_form.php b/app/login/login_form.php index 366d4d3d5df28eedd50726e5021f79447b8dd3c0..072df17b670a8359e64b90679eddf3a231dca31f 100755 --- a/app/login/login_form.php +++ b/app/login/login_form.php @@ -46,11 +46,75 @@ </div> <?php } ?> - <div class="col-xs-12"> - <hr> - <input type="submit" value="<?php print _('Login'); ?>" class="btn btn-sm btn-default pull-right"></input> + <div class="col-xs-12" style="padding-top:15px;"> + <!-- <hr style="margin-top:5px;margin-bottom:10px;"> --> + <input type="submit" value="<?php print _('Login'); ?>" class="btn btn-sm btn-success" style="width:100%"></input> </div> + + <!-- login response --> + <div id="loginCheck" class="col-xs-12 text-center"> + <?php + # deauthenticate user + if ( $User->is_authenticated()===true ) { + # print result + if(isset($_GET['section']) && $_GET['section']=="timeout") + $Result->show("success", _('You session has timed out')); + else + $Result->show("success", _('You have logged out')); + + # write log + $Log->write( _("User logged out"), _("User")." ".$User->username." "._("has logged out"), 0, $User->username ); + + # destroy session + $User->destroy_session(); + } + + //check if SAML2 login is possible + $saml2settings=$Tools->fetch_object("usersAuthMethod", "type", "SAML2"); + + if ($saml2settings!=false) { + $version = pf_json_decode(@file_get_contents(dirname(__FILE__).'/../../functions/php-saml/src/Saml2/version.json'), true); + $version = $version['php-saml']['version']; + + if ($version < 3.4) { + $Result->show("danger", _('php-saml library missing, please update submodules')); + } else { + $Result->show("success", _('You can login with SAML2').' <a href="'.create_link('saml2').'">'._('here').'</a>!'); + } + } + + ?> + </div> + + + <?php if($User->settings->{'passkeys'}=="1") { ?> + <div class="col-xs-12" style="padding-top:20px;"> + + <div style="width: 45%;" class='text-center pull-left'> + <hr style="padding-top: 3px"> + </div> + <div style="width: 10%;" class='text-center pull-left'> + or + </div> + <div style="width: 45%;" class='text-center pull-left'> + <hr style="padding-top: 3px"> + </div> + + <button class="btn btn-sm btn-default passkey_login" style="width:100%;margin-top:20px;"> + <svg height="14" aria-hidden="true" viewBox="0 -3 32 24" version="1.1" width="20" data-view-component="true" class="octicon octicon-passkey-fill"> + <path d="M9.496 2a5.25 5.25 0 0 0-2.519 9.857A9.006 9.006 0 0 0 .5 20.228a.751.751 0 0 0 .728.772h5.257c3.338.001 6.677.002 10.015 0a.5.5 0 0 0 .5-.5v-4.669a.95.95 0 0 0-.171-.551 9.02 9.02 0 0 0-4.814-3.423A5.25 5.25 0 0 0 9.496 2Z"></path> + <path d="M23.625 10.313c0 1.31-.672 2.464-1.691 3.134a.398.398 0 0 0-.184.33v.886a.372.372 0 0 1-.11.265l-.534.534a.188.188 0 0 0 0 .265l.534.534c.071.07.11.166.11.265v.347a.374.374 0 0 1-.11.265l-.534.534a.188.188 0 0 0 0 .265l.534.534a.37.37 0 0 1 .11.265v.431a.379.379 0 0 1-.097.253l-1.2 1.319a.781.781 0 0 1-1.156 0l-1.2-1.319a.379.379 0 0 1-.097-.253v-5.39a.398.398 0 0 0-.184-.33 3.75 3.75 0 1 1 5.809-3.134ZM21 9.75a1.125 1.125 0 1 0-2.25 0 1.125 1.125 0 0 0 2.25 0Z"></path> + </svg> + <span> + <?php print _("Login with a passkey"); ?> + </span> + </button> + + </div> + <div id="loginCheckPasskeys" class="col-xs-12 text-center"></div> + <?php } ?> + </div> </form> diff --git a/app/login/passkey_login_check.php b/app/login/passkey_login_check.php new file mode 100644 index 0000000000000000000000000000000000000000..af58b038a6765ea7e327d3164a7306f8eb7f6f97 --- /dev/null +++ b/app/login/passkey_login_check.php @@ -0,0 +1,99 @@ +<?php + +/** + * + * Save users passkey + * + */ + + +# include composer +require __DIR__ . '/../../functions/vendor/autoload.php'; + +// phpipam stuff +require_once( dirname(__FILE__) . '/../../functions/functions.php' ); + +# initialize required objects +$Database = new Database_PDO; +$User = new User ($Database); + +// set header typw +header('Content-Type: text/html; charset=utf-8'); + +// webauthn modules +use Firehed\WebAuthn\{ + ChallengeManagerInterface, + Codecs, + CredentialContainer, + RelyingParty, + SessionChallengeManager, + SingleOriginRelyingParty, + ResponseParser, + BinaryString +}; + +// process request json +$json = file_get_contents('php://input'); +$data = json_decode($json, true); + + +// parser +$parser = new ResponseParser(); +$getResponse = $parser->parseGetResponse($data); + +// header('HTTP/1.1 201 Created'); +// header('Content-Type: text/html; charset=utf-8'); +// echo(implode('', array_map('chr', $data['keyId']))); +// die(); + +// Set relaying party +$rp = new \Firehed\WebAuthn\SingleOriginRelyingParty('https://ipam-dc.ugbb.net'); +// challange manager +$challengeManager = new \Firehed\WebAuthn\SessionChallengeManager(); + +// get user credentials +$passkey = $User->get_user_passkey_by_keyId ($data['keyId']); + +// none found +if (is_null($passkey)) { + header('HTTP/1.1 404 Not Found'); + return; +} +else { + try { + // set user id + $User->set_passkey_user_id ($passkey->user_id); + + // init credentails + $codec = new Codecs\Credential(); + + // set credentials + $credentials[0] = $codec->decode($passkey->credential);; + + // container + $credentialContainer = new CredentialContainer($credentials); + + // Verify credentials, if it fails exit + $updatedCredential = $getResponse->verify($challengeManager, $rp, $credentialContainer); + + // Auth success. Now update credential and save session + + // encode credentials to store to database + $codec = new Codecs\Credential(); + $encodedCredential = $codec->encode($updatedCredential); + + // confirm login + $User->auth_passkey ($updatedCredential->getStorageId(), $encodedCredential, $data['keyId']); + + // print result + header('HTTP/1.1 200 OK'); + header('Content-type: application/json'); + echo json_encode([ + 'success' => true, + 'credential_ids' => $updatedCredential->getStorageId() + ]); + } + catch (Exception $e) { + header('HTTP/1.1 500 '.$e->getMessage()); + } +} \ No newline at end of file diff --git a/app/subnets/subnet-details/subnet-map-vertical.php b/app/subnets/subnet-details/subnet-map-vertical.php new file mode 100644 index 0000000000000000000000000000000000000000..538ecbe196361425486f3475f144bb7d7a1691af --- /dev/null +++ b/app/subnets/subnet-details/subnet-map-vertical.php @@ -0,0 +1,153 @@ +<?php + +# array +$free_subnets = []; + + +// ipv6 +if($Tools->identify_address($subnet['subnet'])=="IPv6") { + $maxmask = $subnet['mask']+10>128 ? 128 : $subnet['mask']+10; + $pow = 128; +} +else { + $maxmask = $subnet['mask']+10>32 ? 32 : $subnet['mask']+10; + $pow = 32; +} + + +# reset if search +if(@$from_search===true) { + $maxmask = $_GET['ipaddrid']+1; + $subnetmask = $_GET['ipaddrid']-1; +} +else { + $subnetmask = $subnet['mask']; +} + +// print $subnet['mask']; +// print $maxmask; + +# create free objects +for($searchmask=$subnetmask+1; $searchmask<$maxmask; $searchmask++) { + $found = $Subnets->search_available_subnets ($subnet['id'], $searchmask, $count = Subnets::SEARCH_FIND_ALL, $direction = Subnets::SEARCH_FIND_FIRST); + if($found!==false) { + // check if subnet has addresses + if($Addresses->count_subnet_addresses ($subnet['id'])>0) { + // subnet aqddresses + $subnet_addresses = $Addresses->fetch_subnet_addresses ($subnet['id'], null, null, $fields = ['ip_addr']); + + // remove found subnets with hosts ! + foreach($found as $k=>$f) { + // parse + $parsed = explode("/", $f); + // boundaries + $boundaries = $Subnets->get_network_boundaries ($parsed[0], $searchmask); + // broadcast to int + $maxint = isset($boundaries['broadcast']) ? $Subnets->transform_address ($boundaries['broadcast'],"decimal") : 0; + + if(sizeof($subnet_addresses)>0) { + foreach ($subnet_addresses as $a) { + if ($a->ip_addr>=$Subnets->transform_address($parsed[0],"decimal") && $a->ip_addr<=$maxint ) { + unset($found[$k]); + } + } + } + } + + // save remaining + $free_subnets[$searchmask] = $found; + } + else { + $free_subnets[$searchmask] = $found; + } + } + else { + $free_subnets[$searchmask] = []; + } +} + +# if some found print +if (sizeof($free_subnets)>0) { + + // get maximum number of subnets that will be calculated + $max_all_subnets = pow(2,array_keys($free_subnets)[count($free_subnets)-1]-$subnet['mask']); + $levels = sizeof($free_subnets); + + // content + print "<div id='showFreeSubnets'>"; + + // table + print "<table>"; + + // headers + print "<tr>"; + foreach ($free_subnets as $free_mask=>$items) { + print " <td>/".$free_mask."</td>"; + } + print "</tr>"; + + + $all_keys = array_keys($free_subnets); + + + for($m=0; $m<=$max_all_subnets;$m++) { + + // save start + $subnet_start = $subnet['subnet']; + + print "<tr>"; + foreach ($all_keys as $array_key) { + // max subnets + $max_subnets = pow(2,$array_key-$subnet['mask']); + + + if(in_array($Subnets->transform_address($subnet_start, "dotted")."/".$array_key, $free_subnets[$array_key])) { + print "<td>".$free_subnets[$array_key][$m]."/".$array_key."</td>"; + } + else { + print "<td>/</td>"; + } + + // next subnet + $subnet_start = gmp_strval(gmp_add($subnet_start, gmp_pow(2, ($pow-$array_key)))); + + + // rowspan + // $rowspan = $max_all_subnets/$max_subnets; + + } + print "</tr>"; + } + + + + // items + foreach ($free_subnets as $free_mask=>$items) { + break; + + // max + $max_subnets = pow(2,$free_mask-$subnet['mask']); + + // save start + $subnet_start = $subnet['subnet']; + + // print + print "<div class='ip_vis_subnet'>"; + for($m=1; $m<=$max_subnets;$m++) { + if(in_array($Subnets->transform_address($subnet_start, "dotted")."/".$free_mask, $items)) { + print "<span class='subnet_map subnet_map_$pow subnet_map_found'><a href='' data-sectionid='".$section['id']."' data-mastersubnetid='".$subnet['id']."' class='createfromfree' data-cidr='".$Subnets->transform_address($subnet_start, "dotted")."/".$free_mask."' rel='tooltip' title='"._("Create subnet")."'>".$Subnets->transform_address($subnet_start, "dotted")."/".$free_mask."</a></span>"; + } + else { + print "<span class='subnet_map subnet_map_$pow subnet_map_notfound'>".$Subnets->transform_address($subnet_start, "dotted")."/".$free_mask."</span>"; + } + + // next subnet + // $subnet_start = $subnet_start + pow(2,($pow-$free_mask)); + $subnet_start = gmp_strval(gmp_add($subnet_start, gmp_pow(2, ($pow-$free_mask)))); + + } + } + + print "</table>"; + print "</div>"; +} \ No newline at end of file diff --git a/app/tools/user-menu/2fa.php b/app/tools/user-menu/2fa.php index d7a7eadf70e70275b462866aa89e15534b978502..da5cd012de4f3cc64194c052227f9a68cd44a7d6 100644 --- a/app/tools/user-menu/2fa.php +++ b/app/tools/user-menu/2fa.php @@ -22,22 +22,37 @@ if (is_null($User->user->{'2fa_secret'}) && $User->user->{'2fa'}=="1") { // get QR code $username = strtolower($User->user->username)."@".$User->settings->{'2fa_name'}; + +// passkey only +if ($User->settings->{'passkeys'}=="1") { + // get user passkeys + $user_passkeys = $User->get_user_passkeys($User->user->id); + // set passkey_only flag + $passkey_only = $User->settings->{'passkeys'}=="1" && sizeof($user_passkeys)>0 && $User->user->passkey_only=="1" ? true : false; +} ?> + + <h4><?php print _('Two-factor authentication'); ?></h4> <hr> <span class="info2"><?php print _("Here you can change settings for two-factor authentication and get your 2fa secret."); ?></span> <br><br> +<?php if(!$passkey_only) { ?> +<div class="panel panel-default" style="max-width:300px;min-width:350px;"> +<ul class="list-group"> +<div class="panel-heading"><?php print _('2fa account status'); ?></div> +<li class="list-group-item"> <form name="2fa_user" id="2fa_user"> -<table id="userModSelf" class="table table-condensed"> +<table id="userModSelf" class="table table-condensed" style='margin-bottom:0px;width:100%'> <tr> <td class="title"><?php print _('2fa status'); ?></td> <?php if ($User->settings->{'2fa_userchange'}=="1") { ?> <td> <input type="checkbox" value="1" class="input-switch" name="2fa" <?php if($User->user->{'2fa'} == 1) print 'checked'; ?>> </td> - <td> + <td class="text-right"> <input type="submit" class="btn btn-default btn-success btn-sm submit_popup" data-script="app/tools/user-menu/2fa_save.php" data-result_div="userModSelf2faResult" data-form='2fa_user' value="<?php print _("Save"); ?>"> </td> <?php } else { ?> @@ -51,22 +66,28 @@ $username = strtolower($User->user->username)."@".$User->settings->{'2fa_name'}; </table> <input type="hidden" name="csrf_cookie" value="<?php print $csrf; ?>"> </form> +</li> +</ul> +</div> +<?php } ?> + <!-- result --> <div id="userModSelf2faResult" style="margin-bottom:90px;display:none"></div> - -<hr> -<br><br> <?php -if($User->user->{'2fa_secret'}!=null) { +if ($passkey_only) { + $Result->show ("warning alert-absolute", _("You can only login to your account using passkeys").".", false); +} +elseif($User->user->{'2fa_secret'}!=null && $User->user->{'2fa'}==1) { $html = []; - $html[] = '<div class="loginForm row" style="width:400px;">'; + $html[] = "<hr><br>"; + $html[] = '<div class="loginForm row" style="width:500px;">'; $html[] = ' '._('Details for your preferred authenticator application are below. Please write down your details, otherwise you will not be able to login to phpipam')."."; $html[] = ' <div style="border: 2px dashed red;margin:20px;padding: 10px" class="text-center row">'; - $html[] = ' <div class="col-xs-12" style="padding:5px 10px 3px 20px;"><strong>'._('Account').': <span style="color:red; font-size: 16px">'.$username.'</span></strong></div>'; - $html[] = ' <div class="col-xs-12" style="padding:0px 10px 3px 20px;"><strong>'._('Secret').' : <span style="color:red; font-size: 16px">'.$User->user->{'2fa_secret'}.'</span></strong></div>'; + $html[] = ' <div class="col-xs-12" style="padding:5px 10px 3px 20px;"><strong>'._('Account').':<br> <span style="color:red; font-size: 16px">'.$username.'</span></strong><hr></div>'; + $html[] = ' <div class="col-xs-12" style="padding:0px 10px 3px 20px;"><strong>'._('Secret').' :<br> <span style="color:red; font-size: 16px">'.$User->user->{'2fa_secret'}.'</span></strong></div>'; $html[] = ' </div>'; $html[] = ' <div class="text-center">'; $html[] = ' <hr>'._('You can also scan following QR code with your preferred authenticator application').':<br><br>'; diff --git a/app/tools/user-menu/account.php b/app/tools/user-menu/account.php index 8acc832577c16778cee31e761f23fa6fe4f06770..88b5a03184d7c686ef25bb8251df28826e8d6b3d 100644 --- a/app/tools/user-menu/account.php +++ b/app/tools/user-menu/account.php @@ -21,6 +21,14 @@ $User->check_user_session(); # fetch all languages $langs = $User->fetch_langs(); + +// passkeys +if ($User->settings->{'passkeys'}=="1") { + // get user passkeys + $user_passkeys = $User->get_user_passkeys($User->user->id); + // set passkey_only flag + $passkey_only = $User->settings->{'passkeys'}=="1" && sizeof($user_passkeys)>0 && $User->user->passkey_only=="1" ? true : false; +} ?> <!-- test --> @@ -75,6 +83,21 @@ if($User->user->authMethod == 1) { </tr> <?php } ?> + +<?php if ($User->settings->{'passkeys'}=="1") { ?> +<!-- passkey login only --> +<tr> + <td><?php print _('Passkey login only'); ?></td> + <td> + <input type="checkbox" value="1" class="input-switch" name="passkey_only" <?php if($User->user->passkey_only == "1") print 'checked'; ?>> + </td> + <td class="info2"><?php print _('Select to only allow account login with passkey'); ?> + <?php if(sizeof($user_passkeys)==0 && $User->user->passkey_only=="1") { print "<br><span class='text-warning'>". _("You can login to your account with normal authentication method only untill you create passkeys.")."</span>"; } ?> + </td> +</tr> +<?php } ?> + + <!-- select theme --> <tr> <td><?php print _('Theme'); ?></td> diff --git a/app/tools/user-menu/index.php b/app/tools/user-menu/index.php index 7c092a79ecaa5d92b6e6c644979bbaab83473da0..f245bb8a5c818fa3c13f1b92f4afcde3f30ffd9d 100755 --- a/app/tools/user-menu/index.php +++ b/app/tools/user-menu/index.php @@ -35,6 +35,11 @@ print "<hr><br>"; $subpages['2fa'] = "Two-factor authentication"; } + // Passkeys + if ($User->settings->{'passkeys'}=="1") { + $subpages['passkeys'] = "Passwordless authentication"; + } + // default tab if(!isset($_GET['subnetId'])) { $_GET['subnetId'] = "account"; diff --git a/app/tools/user-menu/passkey_challenge.php b/app/tools/user-menu/passkey_challenge.php new file mode 100644 index 0000000000000000000000000000000000000000..06113c5139b15fbf6faad2546b7c41a3c6b98364 --- /dev/null +++ b/app/tools/user-menu/passkey_challenge.php @@ -0,0 +1,28 @@ +<?php + +# +# Create challenge for webauthn +# + +// include composer +require __DIR__ . '/../../../functions/vendor/autoload.php'; + +// phpipam stuff +require_once( dirname(__FILE__) . '/../../../functions/functions.php' ); + +# initialize required objects - to start session +$Database = new Database_PDO; +$User = new User ($Database); + +// webauthn modules +use Firehed\WebAuthn\{ + SessionChallengeManager +}; + +// Generate challenge +$challengeManager = new \Firehed\WebAuthn\SessionChallengeManager(); +$challenge = $challengeManager->createChallenge(); + +// Send json challenge to user +header('Content-type: application/json'); +echo json_encode($challenge->getBase64()); \ No newline at end of file diff --git a/app/tools/user-menu/passkey_edit.php b/app/tools/user-menu/passkey_edit.php new file mode 100644 index 0000000000000000000000000000000000000000..c2da207df606394cf4106eb4305b793d6d4ece6c --- /dev/null +++ b/app/tools/user-menu/passkey_edit.php @@ -0,0 +1,90 @@ +<?php + +/** + * + * Name created passkey + */ + +# include required scripts +require_once( dirname(__FILE__) . '/../../../functions/functions.php' ); + +# initialize user object +$Database = new Database_PDO; +$User = new User ($Database); +$Result = new Result (); + +# verify that user is logged in +$User->check_user_session(); + +# validate action +$User->validate_action ($_POST['action'], true); + +# create csrf token +$csrf = $User->Crypto->csrf_cookie ("create", "passkeyedit"); + +# fetch passkey +$passkey = $User->get_user_passkey_by_keyId ($_POST['keyid']); + +# validate +if(is_null($passkey)) +$Result->show("danger", _("Passkey not found"), true, true); +?> + + +<!-- header --> +<div class="pHeader"><?php print _("Edit")." "._("passkey"); ?></div> + +<!-- content --> +<div class="pContent"> + + <?php + if($_POST['action']=="add") { + $Result->show("success", _("New passkey succesfully registered!")); + print "<hr>"; + } + ?> + + <form id="passkeyEdit" name="passkeyEdit"> + + <?php if ($_POST['action']!="delete") { ?> + <table class="groupEdit table table-noborder table-condensed"> + <!-- name --> + <tr> + <td><?php print _('Name your passkey'); ?>:</td> + <td> + + <input type="text" name="comment" class="form-control input-sm" value="<?php print escape_input(@$passkey->comment); ?>" <?php if($_POST['action'] == "delete") print "readonly"; ?>> + <input type="hidden" name="keyid" value="<?php print escape_input($_POST['keyid']); ?>"> + <input type="hidden" name="action" value="<?php print escape_input($_POST['action']); ?>"> + <input type="hidden" name="csrf_cookie" value="<?php print $csrf; ?>"> + </td> + </tr> + </table> + <?php } else { ?> + <input type="hidden" name="keyid" value="<?php print escape_input($_POST['keyid']); ?>"> + <input type="hidden" name="action" value="<?php print escape_input($_POST['action']); ?>"> + <input type="hidden" name="csrf_cookie" value="<?php print $csrf; ?>"> + <?php } ?> + + <?php + if($_POST['action']=="delete") { + $Result->show("danger", _("You are about to delete your passkey ").escape_input($passkey->comment)."!", false); + } + ?> +</form> + +</div> + + +<!-- footer --> +<div class="pFooter"> + <div class="btn-group"> + <button class="btn btn-sm btn-default hidePopups"><?php print _('Cancel'); ?></button> + <button class='btn btn-sm btn-default submit_popup <?php if($_POST['action']=="delete") { print "btn-danger"; } else { print "btn-success"; } ?>' data-script="app/tools/user-menu/passkey_edit_result.php" data-result_div="passkeyEditResult" data-form='passkeyEdit'> + <i class="fa <?php if($_POST['action']=="add") { print "fa-plus"; } else if ($_POST['action']=="delete") { print "fa-trash-o"; } else { print "fa-check"; } ?>"></i> <?php print escape_input(ucwords(_($_POST['action']))); ?> + </button> + + </div> + <!-- Result --> + <div id="passkeyEditResult"></div> +</div> \ No newline at end of file diff --git a/app/tools/user-menu/passkey_edit_result.php b/app/tools/user-menu/passkey_edit_result.php new file mode 100644 index 0000000000000000000000000000000000000000..2ee0b3f3699d207c717309495d9bea2bb380ca4c --- /dev/null +++ b/app/tools/user-menu/passkey_edit_result.php @@ -0,0 +1,45 @@ +<?php + +/** + * + * Rename passkey + * + */ + +# include required scripts +require_once( dirname(__FILE__) . '/../../../functions/functions.php' ); + +# initialize required objects +$Database = new Database_PDO; +$Result = new Result; +$User = new User ($Database); + +# verify that user is logged in +$User->check_user_session(); + +# strip input tags +$_POST = $User->strip_input_tags($_POST); + +# validate csrf cookie +$User->Crypto->csrf_cookie ("validate", "passkeyedit", $_POST['csrf_cookie']) === false ? $Result->show("danger", _("Invalid CSRF cookie"), true) : ""; + +# fetch passkey +$passkey = $User->get_user_passkey_by_keyId ($_POST['keyid']); + +# validate +if(is_null($passkey)) { + $Result->show("danger", _("Passkey not found"), true); +} +elseif ($passkey->user_id!=$User->user->id) { + $Result->show("danger", _("Passkey not found"), true); +} +else { + if($_POST['action']=="edit" || $_POST['action']=="add") { + if($User->rename_passkey ($passkey->id, $_POST['comment'])) { $Result->show("success", _("Passkey renamed"), false); } + else { $Result->show("success", _("Failed to rename passkey"), false); } + } + else { + if($User->delete_passkey ($passkey->id)) { $Result->show("success", _("Passkey removed"), false); } + else { $Result->show("success", _("Failed to remove passkey"), false); } + } +} \ No newline at end of file diff --git a/app/tools/user-menu/passkey_save.php b/app/tools/user-menu/passkey_save.php new file mode 100644 index 0000000000000000000000000000000000000000..3c15ed5462e1418c00eb9cd2711079e88c44798c --- /dev/null +++ b/app/tools/user-menu/passkey_save.php @@ -0,0 +1,71 @@ +<?php + +/** + * + * Save users passkey + * + */ + +# include composer +require __DIR__ . '/../../../functions/vendor/autoload.php'; + +// phpipam stuff +require_once( dirname(__FILE__) . '/../../../functions/functions.php' ); + +# initialize required objects +$Database = new Database_PDO; +$User = new User ($Database); + +// set header typw +header('Content-Type: text/html; charset=utf-8'); + +// webauthn modules +use Firehed\WebAuthn\{ + ChallengeManagerInterface, + Codecs, + CredentialContainer, + RelyingParty, + SessionChallengeManager, + SingleOriginRelyingParty, + ResponseParser +}; + +# process request +$json = file_get_contents('php://input'); +$data = json_decode($json, true); + +// parser +$parser = new ResponseParser(); +$createResponse = $parser->parseCreateResponse($data); + +// escape keyId +$data['keyId'] = $User->strip_input_tags ($data['keyId']); + +// Relaying party +$rp = new \Firehed\WebAuthn\SingleOriginRelyingParty('https://ipam-dc.ugbb.net'); +// challange manager +$challengeManager = new \Firehed\WebAuthn\SessionChallengeManager(); + +// Verify credentials, if it fails exit +try { + $credential = $createResponse->verify($challengeManager, $rp); +} catch (Throwable) { + header('HTTP/1.1 403 Unauthorized'); + return; +} + +// encode credentials to store to database +$codec = new Codecs\Credential(); +$encodedCredential = $codec->encode($credential); + + +// save passkey +$User->save_passkey ($encodedCredential, $credential->getStorageId(), $data['keyId']); + +// print result +header('HTTP/1.1 200 OK'); +header('Content-type: application/json'); +echo json_encode([ + 'success' => true, + 'credentialId' => $credential->getStorageId() +]); \ No newline at end of file diff --git a/app/tools/user-menu/passkeys.php b/app/tools/user-menu/passkeys.php new file mode 100644 index 0000000000000000000000000000000000000000..82709412b024c7883180748f0acd13f850bba3de --- /dev/null +++ b/app/tools/user-menu/passkeys.php @@ -0,0 +1,252 @@ +<?php + +/** + * Usermenu - passkeys + */ + +# verify that user is logged in +$User->check_user_session(); +?> + + +<h4><?php print _('Passwordless authentication'); ?></h4> +<hr> +<span class="info2"><?php print _("Here you can manage passkey authentication for your account"); ?>. <?php print _("Passkeys are a password replacement that validates your identity using touch, facial recognition, a device password, or a PIN"); ?>.</span> +<br><br> + +<?php + +# tls check +if (!$Tools->isHttps()) { + $Result->show("danger", _("TLS is required for passcode authentication"), false); +} +# are passkeys enabled ? +elseif (!$User->settings->{'passkeys'}=="1") { + $Result->show("danger", _("Passkey authentication is disabled"), false); +} +else { + // get user passkeys + $user_passkeys = $User->get_user_passkeys(false); + + + # passkey 0nly ? + if(sizeof($user_passkeys)>0 && $User->user->passkey_only=="1") { + $Result->show("warning alert-absolute", _("You can login to your account with with passkeys only").".<hr>"._("This can be changed under Account details tab")."."); + } + elseif($User->user->passkey_only=="1") { + $Result->show("warning alert-absolute", _("You can login to your account with normal authentication method only untill you create passkeys".".<hr>"._("This can be changed under Account details tab."))); + } + else { + $Result->show("warning alert-absolute", _("You can login to your account with normal authentication method or with passkeys".".<hr>"._("This can be changed under Account details tab."))); + } + print "<div class='clearfix'></div>"; + + // none ? + if (sizeof($user_passkeys)>0) { + print '<div class="panel panel-default" style="max-width:600px">'; + print '<div class="panel-heading">'._("Your passkeys").'</div>'; + print ' <ul class="list-group">'; + + foreach ($user_passkeys as $passkey) { + + // format last used and created + $created = date("M d, Y", strtotime($passkey->created)); + $last_used = is_null($passkey->used) ? _("Never") : date("M d, Y", strtotime($passkey->used)); + $passkey->comment = is_null($passkey->comment) ? "-- Unknown --" : $passkey->comment; + $this_browser = $passkey->keyId == @$_SESSION['keyId'] ? "<span class='badge' style='margin-bottom:2px;margin-left:10px;'>"._("You authenticated with this passkey")."</span>" : ""; + + print '<li class="list-group-item">'; + print "<div>"; + print ' <div style="width:40px;float:left" class="text-muted">'; + print ' <span class="float-left text-center text-muted"> + <svg height="40" aria-hidden="true" viewBox="0 -8 32 32" version="1.1" width="40" data-view-component="true" class="octicon octicon-passkey-fill" style="color:red !important;"> + <path d="M9.496 2a5.25 5.25 0 0 0-2.519 9.857A9.006 9.006 0 0 0 .5 20.228a.751.751 0 0 0 .728.772h5.257c3.338.001 6.677.002 10.015 0a.5.5 0 0 0 .5-.5v-4.669a.95.95 0 0 0-.171-.551 9.02 9.02 0 0 0-4.814-3.423A5.25 5.25 0 0 0 9.496 2Z"></path> + <path d="M23.625 10.313c0 1.31-.672 2.464-1.691 3.134a.398.398 0 0 0-.184.33v.886a.372.372 0 0 1-.11.265l-.534.534a.188.188 0 0 0 0 .265l.534.534c.071.07.11.166.11.265v.347a.374.374 0 0 1-.11.265l-.534.534a.188.188 0 0 0 0 .265l.534.534a.37.37 0 0 1 .11.265v.431a.379.379 0 0 1-.097.253l-1.2 1.319a.781.781 0 0 1-1.156 0l-1.2-1.319a.379.379 0 0 1-.097-.253v-5.39a.398.398 0 0 0-.184-.33 3.75 3.75 0 1 1 5.809-3.134ZM21 9.75a1.125 1.125 0 1 0-2.25 0 1.125 1.125 0 0 0 2.25 0Z"></path> + </svg>'; + print ' </span>'; + print " </div>"; + + print "<div class='pull-left' style='padding-top:8px;'>"; + print "<strong>".$User->strip_input_tags($passkey->comment)."</strong> ".$this_browser; + print "</div>"; + + print ' <div class="btn-group pull-right" style="padding-top:8px;">'; + print ' <button class="btn btn-xs btn-default open_popup" data-script="app/tools/user-menu/passkey_edit.php" data-action="edit" data-keyId="'.$passkey->keyId.'" rel="tooltip" title="" data-original-title="'._("Rename").'"><i class="fa fa-pencil"></i></button>'; + print ' <button class="btn btn-xs btn-default open_popup" data-script="app/tools/user-menu/passkey_edit.php" data-action="delete" data-keyId="'.$passkey->keyId.'" rel="tooltip" title="" data-original-title="'._("Delete").'"><i class="fa fa-times"></i></button>'; + print ' </div>'; + + // print "<div class='clearfix'></div>"; + print "<br><br>"; + print "<span class='text-muted' style='padding-left:0px;'>"._("Added on")." ".$created." :: "._("Last used")." $last_used</span>"; + print '</div>'; + + + print '</li>'; + } + print ' </ul>'; + print '</div>'; + } + // result + print '<div id="loginCheckPasskeys" style="max-width:600px"></div>'; + + // add + print '<button class="btn btn-sm btn-success addPasskey"><i class="fa fa-plus"></i> '._("Add a passkey").'</button>'; +} +?> + + +<script type="text/javascript"> + +function loginRedirect2() { + location.reload() +} + +// register function +const startRegister = async (e) => { + + // check if browser supports webauthn + if (!window.PublicKeyCredential) { + return + } + + try { + // get and parse challenge + const challengeReq = await fetch('app/tools/user-menu/passkey_challenge.php') + const challengeB64 = await challengeReq.json() + const challenge = atob(challengeB64) // base64-decode + + // create + const createOptions = { + publicKey: { + rp: { + name: 'https://ipam-dc.ugbb.net', + }, + user: { + name: "<?php print $User->user->username; ?>", + displayName: "<?php print $User->user->real_name; ?>", + id: Uint8Array.from("<?php print $User->user->id; ?>", c => c.charCodeAt(0)), + }, + // This base64-decodes the response and translates it into the Webauthn-required format. + challenge: Uint8Array.from(challenge, c => c.charCodeAt(0)), + pubKeyCredParams: [ + { + alg: -7, // ES256 + type: "public-key", + }, + // { + // alg: -257, // Value registered by this specification for "RS256" + // type: "public-key", + // } + ] + }, + attestation: 'direct', + } + + // Call the WebAuthn browser API and get the response. This may throw, which you + // should handle. Example: user cancels or never interacts with the device. + const credential = await navigator.credentials.create(createOptions) + // console.log(credential) + + // Format the credential to send to the server. This must match the format + // handed by the ResponseParser class. The formatting code below can be used + // without modification. + const dataForResponseParser = { + rawId: Array.from(new Uint8Array(credential.rawId)), + keyId: credential.id, + type: credential.type, + attestationObject: Array.from(new Uint8Array(credential.response.attestationObject)), + clientDataJSON: Array.from(new Uint8Array(credential.response.clientDataJSON)), + transports: credential.response.getTransports(), + } + + // Send this to your endpoint - adjust to your needs. + const request = new Request('app/tools/user-menu/passkey_save.php', { + body: JSON.stringify(dataForResponseParser), + headers: { + 'Content-type': 'application/json', + }, + method: 'POST', + }) + const result = await fetch(request) + + // process result + if(result.status==200) { + // $('#loginCheckPasskeys').html("<div class='alert alert-success'>New passkey registered!</div>"); + + // open popup to name passkey + $('div.loading').show(); + // post + $.post("/app/tools/user-menu/passkey_edit.php", {"keyid":credential.id, "action":"add"}, function(data) { + // set content + $('#popupOverlay .popup_w500').html(data).show(); + // show overlay + $("#popupOverlay").fadeIn('fast'); + $('#popupOverlay2 > div').empty(); + $('div.loading').hide(); + //disable page scrolling on bottom + $('body').addClass('stop-scrolling'); + // reset size + var myheight = $(window).height() - 250; + $(".popup .pContent").css('max-height', myheight); + + }).fail(function(jqxhr, textStatus, errorThrown) { + + $('div.jqueryError').fadeIn('fast'); + $('.jqueryErrorText').html(jqxhr.statusText+"<br>Status: "+textStatus+"<br>Error: "+errorThrown).show(); + $('div.loading').hide(); + }); + + /* this functions saves popup result */ + /* --------------------------------- */ + // function submit_popup_data (result_div, target_script, post_data, reload) { + // // show spinner + // showSpinner(); + // // set reload + // reload = typeof reload !== 'undefined' ? reload : true; + // // post + // $.post(target_script, post_data, function(data) { + // $('div'+result_div).html(data).slideDown('fast'); + // //reload after 2 seconds if succeeded! + // if(reload) { + // if(data.search("alert-danger")==-1 && data.search("error")==-1 && data.search("alert-warning")==-1 ) { setTimeout(function (){window.location.reload();}, 1500); } + // else { hideSpinner(); } + // } + // else { + // hideSpinner(); + // } + // }).fail(function(jqxhr, textStatus, errorThrown) { showError(jqxhr.statusText + "<br>Status: " + textStatus + "<br>Error: "+errorThrown); }); + // // prevent reload + // return false; + // } + + + } + else { + $('#loginCheckPasskeys').html("<div class='alert alert-danger'>Failed to register new passkey.</div>"); + console.log(result) + $('div.loading').hide(); + } + } + catch(err) { + $('#loginCheckPasskeys').html("<div class='alert alert-danger'>Failed to register new passkey.</div>"); + console.log(err); + } +} + + +// Start registration of new passkey +$(document).ready(function() { + // check if browser supports webauthn and disable add passkey button + if (!window.PublicKeyCredential) { + $('.addPasskey').addClass('disabled').removeClass('addPasskey') + } + // add passkey + $('.addPasskey').click(function () { + startRegister () + return false; + }) + +}) + + +</script> \ No newline at end of file diff --git a/app/tools/user-menu/user-edit.php b/app/tools/user-menu/user-edit.php index 915ea69fba730cf80569208af825f29650306f4c..edbeb1fc4602483327d888441a59abe53f11d355 100755 --- a/app/tools/user-menu/user-edit.php +++ b/app/tools/user-menu/user-edit.php @@ -43,6 +43,26 @@ if (!empty($_POST['theme'])) { if (!in_array($_POST['theme'], ['default', 'white', 'dark'])) { $Result->show("danger alert-absolute", _('Invalid theme'), true); } } +# passkeys +if ($User->settings->{'passkeys'}=="1") { + // fetch passkeys + $user_passkeys = $User->get_user_passkeys($User->user->id); + // check + if(isset($_POST['passkey_only'])) { + if(sizeof($user_passkeys)==0) { + $Result->show("warning alert-absolute", _('There are no passkeys set for user. Resetting passkey login only to false.'), false); + print "<div class='clearfix'></div>"; + $_POST['passkey_only'] = 0; + } + else { + $_POST['passkey_only'] = 1; + } + } + else { + $_POST['passkey_only'] = 0; + } +} + # set override $_POST['compressOverride'] = @$_POST['compressOverride']=="Uncompress" ? "Uncompress" : "default"; diff --git a/css/bootstrap/bootstrap-custom-dark.css b/css/bootstrap/bootstrap-custom-dark.css index 463b68979ae6d2418098cf01377119535ebfd656..b1c77434690fffd2d111823717cc6d2482fe4050 100644 --- a/css/bootstrap/bootstrap-custom-dark.css +++ b/css/bootstrap/bootstrap-custom-dark.css @@ -1,2 +1,1017 @@ -html{min-height:100% !important}html body{background:url("../images/bg-light.png") no-repeat center center fixed;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;background-repeat:repeat;height:100% !important;min-height:100% !important;color:#e5e5e5;background-color:transparent}html #header{background:rgba(0,0,0,0.4)}html .hero-unit a{text-shadow:none}html .hero-unit a:hover{color:white;text-decoration:none}html blockquote{border-left-color:#58606b}html .content_overlay{padding-bottom:50px}html h1,html h2,html h3,html h4,html h5{color:white !important;text-shadow:none}html a,html a:hover{color:#58ACFA}html .wrapper{background:transparent url("../images/noise.png");height:100% !important}html .subtitle{background:rgba(0,0,0,0.2);border-bottom:1px solid rgba(0,0,0,0.4)}html pre{background:rgba(0,0,0,0.1);border:1px solid #58606b;color:#ccc}html .text-muted{color:#999}html hr{border-top:none;border-bottom:1px solid rgba(255,255,255,0.1)}html hr.title{border-top:none;border-bottom:none;margin-bottom:10px}html .alert.alert-info{background:rgba(0,0,0,0.2);border-color:#58ACFA;color:#58ACFA}html .alert.alert-warning{background:rgba(0,0,0,0.2);color:#faebcc;border-color:#8a6d3b}html .alert.alert-success{background:rgba(0,0,0,0.2);color:#d6e9c6;border-color:#d6e9c6}html .alert.alert-danger{background:rgba(0,0,0,0.2);color:#f2dede;border-color:#a94442}html .alert.alert-muted{background:transparent;color:#999}html span.text-success,html span.text-danger{border:1px solid #58606b;background:rgba(0,0,0,0.2);padding:2px 5px;border-radius:3px}html span.text-success{color:#d6e9c6;border:1px solid #d6e9c6}html span.text-danger{color:#ebccd1;border:1px solid #a94442}html table td.info2,html .info2,html .muted{text-shadow:none;color:#999}html .form-control{border:1px solid #58606b}html .form-inline{border-bottom:1px solid #58606b}html input{color:#58606b !important}html .badge{background:rgba(0,0,0,0.2) !important;border:1px solid #58606b !important}html .badge.alert-success{color:#dff0d8 !important;border-color:#3c763d !important}html .badge.alert-danger{background:rgba(0,0,0,0.2);color:#ebccd1 !important;border-color:#a94442 !important}html .badge.alert-warning{background:rgba(0,0,0,0.2);color:#faebcc !important;border-color:#8a6d3b !important}html .badge.alert-info{background:rgba(0,0,0,0.2);color:#e3eaf2 !important;border-color:#436587 !important}html span.status{border-color:rgba(0,0,0,0.6)}html span.status.status-neutral{background:rgba(0,0,0,0.1)}html span.status.status-error{background:#a94442}html span.status.status-success{background:#3c763d}html i.fa-Offline{color:#a94442 !important}html .navbar .navbar-nav.sections li.active a{background:rgba(0,0,0,0.4) !important}html .navbar .navbar-nav.sections li.dropdown ul.dropdown-menu li:hover active{background:rgba(0,0,0,0.4) !important}html .navbar .navbar-nav li{min-width:10px}html .navbar .navbar-nav li a{color:white}html .navbar a span.badge{margin-left:7px;padding:2px 5px;color:#58606b}html .navbar.navbar-default{border-top:1px solid rgba(255,255,255,0.3) !important;border-bottom:1px solid rgba(255,255,255,0.3) !important}html .navbar .navbar-nav li{min-width:10px}html .navbar#menu-navbar{background:rgba(0,0,0,0.3)}html .navbar#menu-navbar a{font-weight:normal}html .dropdown-menu .divider{background-color:#333 !important}html ul.dropdown-menu{background:url("../images/bg-light.png") no-repeat center center fixed;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;background-repeat:repeat}html ul.dropdown-menu li.disabled{background:rgba(0,0,0,0.1);border-color:#58606b}html .navbar#menu-navbar .navbar-nav li.administration a{background:#a94442 !important}html .navbar#menu-navbar .navbar-nav li.administration li a{background:#40454a !important}html .navbar#menu-navbar .navbar-nav ul.dropdown-menu{border:none;border-radius:none;box-shadow:none;padding-top:0px !important;margin-top:5px}html .navbar#menu-navbar .navbar-nav ul.dropdown-menu li{background:transparent url("../images/noise.png") !important}html .navbar#menu-navbar .navbar-nav ul.dropdown-menu li a{border-left:1px solid #58606b;border-left:none}html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.admin li{border-left:1px solid #58606b}html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.admin li:first-child{border-top:1px solid #58606b !important}html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.admin li:last-child{border-bottom:1px solid #58606b !important}html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.admin li.active{border-left:2px solid #d43f3a !important}html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.admin li.active a{background:#272b30 !important}html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.admin li.nav-header{background:rgba(0,0,0,0.2) !important;border-bottom:1px solid #58606b}html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.admin li.nav-header:hover{background:rgba(0,0,0,0.2) !important}html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.tools_dropdown li{border-radius:0px !important}html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.tools_dropdown li a{border-radius:0px !important}html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.tools_dropdown li.active{border-left:2px solid #58ACFA !important}html .nav-tabs{border-bottom:1px solid #999}html .nav-tabs>li.active>a,html .nav-tabs>li.active>a:focus,html .nav-tabs>li.active>a:hover{background:rgba(0,0,0,0.3);border:1px solid #999 !important;border-bottom:none !important;color:white !important}html .nav-tabs>li>a:hover{background:rgba(0,0,0,0.1);border:1px solid transparent !important;border-bottom:0px solid #999 !important;color:white !important}html .nav-tabs li a{border-bottom:none !important}html .btn:hover i.prefix{color:white}html ul.dropdown-menu{border:1px solid rgba(0,0,0,0.5)}html ul.dropdown-menu li a{color:white !important}html ul.dropdown-menu li a:hover{background:rgba(0,0,0,0.2)}html .btn{color:#58ACFA;border:1px solid #58606b}html .btn:hover{background:#58ACFA;color:white}html .btn[disabled]:hover{color:#58ACFA;border-color:#58ACFA}html .btn.btn-success{border:1px solid transparent !important;background:rgba(0,255,0,0.2) !important}html .btn.btn-success:hover{background:#3c763d;color:white;border-color:#adadad !important}html .btn{background:rgba(0,0,0,0.2);color:white}html .btn:hover{background:rgba(0,0,0,0.4)}html .input-group-addon{background:rgba(0,0,0,0.2);color:white;border:1px solid #58606b}html .breadcrumb .active{color:#999}html div.btn-group{border:1px solid #58606b;border:none;border-radius:4px}html div.btn-group.noborder{border:none}html div.btn-group.noborder .btn-group{border:none}html div.btn-group .btn.btn-danger{color:#fff;background-color:#d9534f !important;border-color:#d43f3a}html .btn-default.active,html .btn-default:active,html .open>.dropdown-toggle.btn-default{background:rgba(0,0,0,0.2) !important;color:white !important}html .btn:focus{background-position:0 -14px}html ul#subnets li.folder i,html ul.submenu li i.fa-folder-close-o,html ul.submenu li i.fa-folder-open-o,html ul.submenu li i.fa-folder{background:transparent;color:white !important}html .fa-gray{color:white !important}html .fa-folder-open{color:white !important}html .action{text-shadow:none;background:transparent;border-top:1px solid #58606b;border-bottom:1px solid #58606b}html .action .btn-success{background:#3c763d;border:1px solid #58606b !important}html .tooltip{font-size:12px}html .popover{background:url("../images/bg-light.png") no-repeat center center fixed;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;background-repeat:repeat;color:#e5e5e5;background-color:transparent;border:2px solid rgba(0,0,0,0.2)}html .popover .popover-title{background:rgba(0,0,0,0.3);border-bottom:1px solid #999}html .popover .popover-content table{background:transparent !important}html .popover.right>.arrow:after{border-right-color:rgba(0,0,0,0.1) !important}html .table.ipaddresses tbody tr:hover td table.popover_table tbody td{background:transparent !important;border:none !important}html table#userModSelf.table td{border:none !important}html table.table-certificates tr.warning td{color:#FFC47B !important}html table.table-certificates tr.warning td a{color:#FFC47B !important}html table.table-certificates tr.danger td{color:#EA6C85 !important}html table.table-certificates tr.danger td a{color:#EA6C85 !important}html .legend .legendLabel{color:#999}html ul[class*="submenu-"] li{padding-left:20px !important;background:url("../images/li-dark.png") no-repeat 4px -3px}html ul[class*="submenu-"] li:last-child{background:url("../images/ul-li-bg-dark.png") no-repeat 3px 0px}html li.active ul[class*="submenu-"] li:last-child{background:url("../images/ul-li-bg-active-dark.png") no-repeat 3px 0px !important}html ul#subnets li.folder li.leaf.active{background:#F1FAFE url("../images/li-dark.png") no-repeat 4px -3px !important}html ul[class*="submenu-"] li.folder.active{background:#F1FAFE url("../images/ul-li-bg-dark.png") no-repeat 3px 0px !important}html ul#subnets li.folder li.leaf.active:last-of-type{background:#F1FAFE url("../images/ul-li-bg-active-dark.png") no-repeat 3px 0px !important}html table#manageSubnets tr td:nth-child(1) a{color:white}html table#manageSubnets .structure{background:url("../images/sn-bg-dark.png") 0px 7px}html table#manageSubnets .structure-last{background:url("../images/sn-bg-last-dark.png") no-repeat 1px 0px}html ul.submenu-dns li{background:url("../images/li-dns-dark.png") no-repeat 4px -3px !important;font-size:10px}html ul.submenu-dns li:last-child{background:url("../images/li-dns-last-dark.png") no-repeat 4px -3px !important}html table#manageSubnets>tbody>tr:last-child .structure{background:url("../images/sn-bg-last-dark.png") no-repeat 1px 0px !important}html ul.submenu-linked li{background:url("../images/li-dns-dark.png") no-repeat 4px -1px !important}html ul.submenu-linked li:last-child{background:url("../images/li-dns-last-dark.png") no-repeat 4px -1px !important}html table tr.similar td:nth-child(1){background:url("../images/li-dns-dark.png") no-repeat 17px -5px !important}html table tr.similar-last td:nth-child(1){background:url("../images/li-dns-last-dark.png") no-repeat 17px -5px !important}html table tr.similar-last td:nth-child(1){background:url("../images/li-dns-last-dark.png") no-repeat 17px -5px !important}html .ipaddress_subnet .subnet_badge{background:rgba(0,0,0,0.3) !important}html .table.ipaddresses tbody tr:hover td,html .table.slaves tbody tr:hover td,html .table-striped tbody tr:hover td{background:rgba(0,0,0,0.1) !important}html .table-striped tbody tr:nth-child(even) td.th{background:transparent !important}html table.table.ipaddresses .unused{text-shadow:none !important;color:white;background:rgba(0,255,0,0.1) !important}html table.table.ipaddresses tr.dhcp td{text-shadow:none !important;color:white;background:rgba(0,0,0,0.1) !important}html table.table.ipaddresses tr:hover td.unused{background:rgba(0,255,0,0.1) !important}html table.table.ipaddresses td{border-bottom:none !important;border-top:1px solid #58606b !important}html table.table.address_details td{border:none !important}html table#logs tr td.severity span{color:#272b30}html table#logs tr.success td a{border:none}html table#logs tr.danger td{background:#a94442 !important}html table.table-threshold td{border-bottom:none !important}html .ip_vis span{background:rgba(0,0,0,0.2) !important}html .ip_vis span.ip-unused{border-color:#999;color:#ccc !important}html .ip_vis span.ip-0{color:white !important}html .ip_vis span.ip-1{border-color:#a94442;background-color:rgba(255,0,0,0.05) !important;color:white !important}html .ip_vis span.ip-2{border-color:#d6e9c6;background:rgba(0,255,0,0.05) !important;color:white !important}html .ip_vis span.ip-4{border-color:#58ACFA;color:#e3eaf2 !important}html #popupOverlay{background:rgba(0,0,0,0.6) !important}html #popup{background:url("../images/bg-light.png") no-repeat center center fixed;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;background-repeat:repeat;color:#e5e5e5;background-color:transparent;border:2px solid rgba(0,0,0,0.2)}html #popup div.pHeader{background:rgba(0,0,0,0.3);border-bottom:1px solid #999}html #popup div.pContent{background:transparent url("../images/noise.png")}html #popup div.pFooter{background:rgba(0,0,0,0.3);border-top:1px solid #999 !important;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}html #popup div.pFooter .btn{border:1px solid #58606b !important}html #popup div.col-xs-12.col-md-6{border-right-color:#58606b !important}html #popup .sortable li{background:rgba(0,0,0,0.2);border:1px solid #58606b !important}html .bootstrap-switch{border-color:#999}html .bootstrap-switch .bootstrap-switch-label{background:transparent}html .bootstrap-switch .bootstrap-switch-handle-off,html .bootstrap-switch .bootstrap-switch-handle-on{color:white !important;background:rgba(0,0,0,0.2) !important}html table.table.table-top th{background:white;margin:0px}html table.table.table{background:transparent}html table.table.table-noborder th,html table.table.table-noborder td{border:none}html table.table.statistics td{border:none !important;padding-top:2px;padding-bottom:2px}html table.table td{font-size:13px}html table.table td.th{padding-top:25px !important;border-bottom:1px solid #999 !important}html table.table td.border-bottom{border-bottom:1px solid #999 !important}html table.table tr.success td{text-shadow:none !important;color:white;background:rgba(0,255,0,0.1) !important;border-bottom:none !important;border-bottom:none !important}html table.table tr.success td btn,html table.table tr.success td a{border:1px solid #999}html table.table tr.success:hover td{background:rgba(0,255,0,0.1) !important}html table.vlans tr td{border:none !important}html table.vlans tr.change td{border-top:1px solid #58606b !important}html .bootstrap-table .table{border-bottom:1px solid #58606b}html table.table th,html table.table tr,html table.table td{background:transparent !important}html table.table td.ip a,html table.table td.ipaddress a{color:white !important}html table.table th{background:rgba(0,0,0,0.2) !important;border-bottom:1px solid #272b30 !important;border-top:none !important}html table.table th i.fa{background:rgba(0,0,0,0.2);border:1px solid rgba(255,255,255,0.4)}html table.table tr:hover td{background:rgba(0,0,0,0.1) !important}html table.table tr.weeknumber th{border-top:none}html table.table tr.today{background:rgba(88,172,250,0.1) !important}html table.table tr.pw-status-V_teku{background:#3c763d !important}html table.table tr.pw-status-V_teku a{color:white}html table.table tr.pw-status-V_teku span.badge-warning{border:1px solid white;color:white !important}html table.table td{border-top:1px solid #58606b !important}html table.table td.izpad{color:white}html table.table.table-noborder tr th,html table.table.table-noborder tr td{background:transparent !important;border:none !important}html input,html textarea,html select{background:rgba(0,0,0,0.2) !important;color:white !important}html input.btn.btn-success,html textarea.btn.btn-success,html select.btn.btn-success{border:1px solid #58606b !important}html select{background:rgba(255,255,255,0.2) !important}html input[type=submit]{background:rgba(0,0,0,0.2) !important}html select{line-height:24px}html select optgroup{color:#58606b}html select option{color:#999}html table#subnetsMenu td#subnetsLeft{border-right:1px solid #58606b}html table#subnetsMenu td#subnetsLeft #leftMenu{margin-top:0px}html table#subnetsMenu td#subnetsLeft h4{padding:10px;margin:0px;background:rgba(0,0,0,0.3);border-bottom:1px solid #58606b}html table#subnetsMenu td#subnetsLeft hr{display:none}html table#subnetsMenu td#subnetsLeft a{color:white}html table#subnetsMenu td#subnetsLeft li.leaf i{color:#999 !important}html table#subnetsMenu td#subnetsLeft li.active{background-color:rgba(0,0,0,0.4) !important;background-color:#4b7387 !important;text-shadow:none;border-top:1px solid #58606b;border-bottom:1px solid #58606b}html table#subnetsMenu td#subnetsLeft li.active i{background:transparent}html .adminMenu,html .toolsMenu{background:transparent !important}html .adminMenu .panel-heading,html .toolsMenu .panel-heading{background:transparent !important;border-bottom:1px solid rgba(0,0,0,0.5)}html .adminMenu ul.list-group,html .toolsMenu ul.list-group{background:transparent !important}html .adminMenu ul.list-group li,html .toolsMenu ul.list-group li{background:rgba(0,0,0,0.2) !important;border-left-color:#58606b !important}html .adminMenu ul.list-group li.list-group-item,html .toolsMenu ul.list-group li.list-group-item{border:none}html .adminMenu ul.list-group li.active,html .toolsMenu ul.list-group li.active{background:rgba(0,0,0,0.6) !important;border:1px solid #58606b}html .adminMenu ul.list-group li:hover,html .toolsMenu ul.list-group li:hover{background:rgba(0,0,0,0.3) !important}html #dashboard .inner{background:rgba(0,0,0,0.2) !important;border:1px solid #58606b}html #dashboard .inner h4{background:rgba(0,0,0,0.35) !important;text-shadow:none}html #dashboard .inner .hContent{border-top:1px solid #58606b !important}html #dashboard .inner .hContent .icon{border-right:1px solid #58606b !important}html #dashboard .widget-dash .inner{box-shadow:none !important}html #dashboard #w-access_logs a,html #dashboard #w-error_logs a{color:white}html #dashboard span.severity0{border:1px solid #58606b;background:rgba(0,0,0,0.2);padding:2px 5px;border-radius:4px}html #dashboard span.severity1{border:1px solid #58606b;background:rgba(0,0,0,0.2);padding:2px 5px;border-radius:4px}html #dashboard span.severity2{border:1px solid #58606b;background:rgba(0,0,0,0.2);padding:2px 5px;border-radius:4px}html .menu-tools #dashboard .inner .hContent,html .menu-admin #dashboard .inner .hContent{border-top:none !important}html .adminMenu ul.list-group li.active{border-left-color:#a94442 !important}html .menu-tools ul.list-group li.active{border-left-color:#58ACFA !important}html .footer{border-top:1px solid #58606b}html .pagination ul li a{background:transparent;border:1px solid #999}html .pagination ul li a:hover{background:rgba(0,0,0,0.2);border:1px solid #999}html .pagination ul li.active a,html .pagination ul li.active a:hover{background:rgba(0,0,0,0.5)}html .res_val{background:rgba(0,0,0,0.2) !important}html .ui-slider-handle,html .slider-handle{background:#58606b !important}html .ui-slider,html .slider-track,html .slider-selection{background:#999 !important}html .progress{background:rgba(0,0,0,0.1) !important}html .progress .progress-limit-negative{background:transparent !important;border-color:rgba(0,0,0,0.1) !important}html .progress .progress-bar-info{background:rgba(0,255,0,0.15);border-color:rgba(0,0,0,0.1) !important;color:white}html table#switchMainTable tr.switch-title,html table#vrf tr.vrf-title,html table#subnets tr.subnets-title,html table#settings tr.settings-title,html table#settings{background:transparent !important}html table#switchMainTable tr.switch-title th,html table#vrf tr.vrf-title th,html table#subnets tr.subnets-title th,html table#settings tr.settings-title th,html table#settings th{background:transparent !important;border-bottom:none !important}html table#switchMainTable tr.switch-title th h4,html table#vrf tr.vrf-title th h4,html table#subnets tr.subnets-title th h4,html table#settings tr.settings-title th h4,html table#settings th h4{background:transparent !important}html table#switchMainTable tr.switch-title td,html table#switchMainTable tr.switch-title th,html table#vrf tr.vrf-title td,html table#vrf tr.vrf-title th,html table#subnets tr.subnets-title td,html table#subnets tr.subnets-title th,html table#settings tr.settings-title td,html table#settings tr.settings-title th,html table#settings td,html table#settings th{border:none !important}html table#settings tr.settings-title{border-bottom:1px solid #58606b}html #gmap{border-color:rgba(0,0,0,0.2)}html ul#sortable{max-width:500px}html ul#sortable li{background:rgba(0,0,0,0.2);border-color:#999 !important}html .instructions{background:transparent;border:1px solid #999}html ul.icon-ul>li{border:1px solid rgba(0,0,0,0.4) !important}html .ipreqMenu{background:rgba(0,0,0,0.2);border:1px solid #58606b;right:5px}html .fixed-table-loading{background:rgba(0,0,0,0.3)}html div#login{background:rgba(0,0,0,0.2);border:1px solid #999 !important}html div#login legend{color:white}html div#login form{border-bottom:none}html div#login .iprequest{background:rgba(0,0,0,0.1);border-color:#58606b;text-shadow:none}html .install h4{border-bottom:none !important}html #searchSelect{background:#40454a !important}.subnet_map_found{border-color:#d6e9c6;background:rgba(0,255,0,0.15) !important;color:white !important}.subnet_map_found:hover{background:rgba(0,255,0,0.2) !important}.subnet_map_notfound{background-color:rgba(255,0,0,0.15) !important;color:white !important}.subnet_map_found a{color:white !important}.ip_vis_subnet span{box-shadow:0px 0px 1px #777;border:1px solid rgba(255,255,255,0.2);padding:3px;padding-top:7px;text-align:center;font-size:12px;margin-right:0px;margin-bottom:0px;width:120px;height:30px;float:left;border-left:none}.clearfix1{border-left:1px solid rgba(255,255,255,0.2)}.ip_vis_subnet span a{font-size:12px} +html { + min-height: 100% !important; + /* active leaf */ + /* active submenu background */ +} +html body { + background: url("../images/bg-light.png") no-repeat center center fixed; + -webkit-background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + background-size: cover; + background-repeat: repeat; + height: 100% !important; + min-height: 100% !important; + color: #e5e5e5; + background-color: rgba(0, 0, 0, 0); +} +html #header { + background: rgba(0, 0, 0, 0.4); +} +html .hero-unit a { + text-shadow: none; +} +html .hero-unit a:hover { + color: white; + text-decoration: none; +} +html blockquote { + border-left-color: #58606b; +} +html .content_overlay { + padding-bottom: 50px; +} +html h1, html h2, html h3, html h4, html h5 { + color: white !important; + text-shadow: none; +} +html a, html a:hover { + color: #58ACFA; +} +html .wrapper { + background: transparent url("../images/noise.png"); + height: 100% !important; +} +html .subtitle { + background: rgba(0, 0, 0, 0.2); + border-bottom: 1px solid rgba(0, 0, 0, 0.4); +} +html pre { + background: rgba(0, 0, 0, 0.1); + border: 1px solid #58606b; + color: #ccc; +} +html .text-muted { + color: #999; +} +html hr { + border-top: none; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} +html hr.title { + border-top: none; + border-bottom: none; + margin-bottom: 10px; +} +html .alert.alert-info { + background: rgba(0, 0, 0, 0.2); + border-color: #58ACFA; + color: #58ACFA; +} +html .alert.alert-warning { + background: rgba(0, 0, 0, 0.2); + color: #faebcc; + border-color: #8a6d3b; +} +html .alert.alert-success { + background: rgba(0, 0, 0, 0.2); + color: #d6e9c6; + border-color: #d6e9c6; +} +html .alert.alert-danger { + background: rgba(0, 0, 0, 0.2); + color: #f2dede; + border-color: #a94442; +} +html .alert.alert-muted { + background: transparent; + color: #999; +} +html span.text-success, html span.text-danger { + border: 1px solid #58606b; + background: rgba(0, 0, 0, 0.2); + padding: 2px 5px; + border-radius: 3px; +} +html span.text-success { + color: #d6e9c6; + border: 1px solid #d6e9c6; +} +html span.text-danger { + color: #ebccd1; + border: 1px solid #a94442; +} +html table td.info2, +html .info2, +html .muted { + text-shadow: none; + color: #999; +} +html .form-control { + border: 1px solid #58606b; +} +html .form-inline { + border-bottom: 1px solid #58606b; +} +html input { + color: #58606b !important; +} +html .badge { + background: rgba(0, 0, 0, 0.2) !important; + border: 1px solid #58606b !important; +} +html .badge.alert-success { + color: #dff0d8 !important; + border-color: #3c763d !important; +} +html .badge.alert-danger { + background: rgba(0, 0, 0, 0.2); + color: #ebccd1 !important; + border-color: #a94442 !important; +} +html .badge.alert-warning { + background: rgba(0, 0, 0, 0.2); + color: #faebcc !important; + border-color: #8a6d3b !important; +} +html .badge.alert-info { + background: rgba(0, 0, 0, 0.2); + color: #e3eaf2 !important; + border-color: #436587 !important; +} +html span.status { + border-color: rgba(0, 0, 0, 0.6); +} +html span.status.status-neutral { + background: rgba(0, 0, 0, 0.1); +} +html span.status.status-error { + background: #a94442; +} +html span.status.status-success { + background: #3c763d; +} +html i.fa-Offline { + color: #a94442 !important; +} +html .navbar .navbar-nav.sections li.active a { + background: rgba(0, 0, 0, 0.4) !important; +} +html .navbar .navbar-nav.sections li.dropdown ul.dropdown-menu li:hover active { + background: rgba(0, 0, 0, 0.4) !important; +} +html .navbar .navbar-nav li { + min-width: 10px; +} +html .navbar .navbar-nav li a { + color: white; +} +html .navbar a span.badge { + margin-left: 7px; + padding: 2px 5px; + color: #58606b; +} +html .navbar.navbar-default { + border-top: 1px solid rgba(255, 255, 255, 0.3) !important; + border-bottom: 1px solid rgba(255, 255, 255, 0.3) !important; +} +html .navbar .navbar-nav li { + min-width: 10px; +} +html .navbar#menu-navbar { + background: rgba(0, 0, 0, 0.3); +} +html .navbar#menu-navbar a { + font-weight: normal; +} +html .dropdown-menu .divider { + background-color: #333 !important; +} +html ul.dropdown-menu { + background: url("../images/bg-light.png") no-repeat center center fixed; + -webkit-background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + background-size: cover; + background-repeat: repeat; +} +html ul.dropdown-menu li.disabled { + background: rgba(0, 0, 0, 0.1); + border-color: #58606b; +} +html .navbar#menu-navbar .navbar-nav li.administration a { + background: #a94442 !important; +} +html .navbar#menu-navbar .navbar-nav li.administration li a { + background: #40454a !important; +} +html .navbar#menu-navbar .navbar-nav ul.dropdown-menu { + border: none; + border-radius: none; + box-shadow: none; + padding-top: 0px !important; + margin-top: 5px; +} +html .navbar#menu-navbar .navbar-nav ul.dropdown-menu li { + background: transparent url("../images/noise.png") !important; +} +html .navbar#menu-navbar .navbar-nav ul.dropdown-menu li a { + border-left: 1px solid #58606b; + border-left: none; +} +html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.admin li { + border-left: 1px solid #58606b; +} +html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.admin li:first-child { + border-top: 1px solid #58606b !important; +} +html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.admin li:last-child { + border-bottom: 1px solid #58606b !important; +} +html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.admin li.active { + border-left: 2px solid #d43f3a !important; +} +html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.admin li.active a { + background: #272b30 !important; +} +html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.admin li.nav-header { + background: rgba(0, 0, 0, 0.2) !important; + border-bottom: 1px solid #58606b; +} +html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.admin li.nav-header:hover { + background: rgba(0, 0, 0, 0.2) !important; +} +html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.tools_dropdown li { + border-radius: 0px !important; +} +html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.tools_dropdown li a { + border-radius: 0px !important; +} +html .navbar#menu-navbar .navbar-nav ul.dropdown-menu.tools_dropdown li.active { + border-left: 2px solid #58ACFA !important; +} +html .nav-tabs { + border-bottom: 1px solid #999; +} +html .nav-tabs > li.active > a, html .nav-tabs > li.active > a:focus, html .nav-tabs > li.active > a:hover { + background: rgba(0, 0, 0, 0.3); + border: 1px solid #999 !important; + border-bottom: none !important; + color: white !important; +} +html .nav-tabs > li > a:hover { + background: rgba(0, 0, 0, 0.1); + border: 1px solid transparent !important; + border-bottom: 0px solid #999 !important; + color: white !important; +} +html .nav-tabs li a { + border-bottom: none !important; +} +html .btn:hover i.prefix { + color: white; +} +html ul.dropdown-menu { + border: 1px solid rgba(0, 0, 0, 0.5); +} +html ul.dropdown-menu li a { + color: white !important; +} +html ul.dropdown-menu li a:hover { + background: rgba(0, 0, 0, 0.2); +} +html .btn { + color: #58ACFA; + border: 1px solid #58606b; +} +html .btn:hover { + background: #58ACFA; + color: white; +} +html .btn[disabled]:hover { + color: #58ACFA; + border-color: #58ACFA; +} +html .btn.btn-success { + border: 1px solid transparent !important; + background: rgba(0, 255, 0, 0.2) !important; +} +html .btn.btn-success:hover { + background: #3c763d; + color: white; + border-color: #adadad !important; +} +html .btn { + background: rgba(0, 0, 0, 0.2); + color: white; +} +html .btn:hover { + background: rgba(0, 0, 0, 0.4); +} +html .input-group-addon { + background: rgba(0, 0, 0, 0.2); + color: white; + border: 1px solid #58606b; +} +html .breadcrumb .active { + color: #999; +} +html div.btn-group { + border: 1px solid #58606b; + border: none; + border-radius: 4px; +} +html div.btn-group.noborder { + border: none; +} +html div.btn-group.noborder .btn-group { + border: none; +} +html div.btn-group .btn.btn-danger { + color: #fff; + background-color: #d9534f !important; + border-color: #d43f3a; +} +html .btn-default.active, +html .btn-default:active, +html .open > .dropdown-toggle.btn-default { + background: rgba(0, 0, 0, 0.2) !important; + color: white !important; +} +html .btn:focus { + background-position: 0 -14px; +} +html ul#subnets li.folder i, +html ul.submenu li i.fa-folder-close-o, +html ul.submenu li i.fa-folder-open-o, +html ul.submenu li i.fa-folder { + background: transparent; + color: white !important; +} +html .fa-gray { + color: white !important; +} +html .fa-folder-open { + color: white !important; +} +html .action { + text-shadow: none; + background: transparent; + border-top: 1px solid #58606b; + border-bottom: 1px solid #58606b; +} +html .action .btn-success { + background: #3c763d; + border: 1px solid #58606b !important; +} +html .tooltip { + font-size: 12px; +} +html .popover { + background: url("../images/bg-light.png") no-repeat center center fixed; + -webkit-background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + background-size: cover; + background-repeat: repeat; + color: #e5e5e5; + background-color: rgba(0, 0, 0, 0); + border: 2px solid rgba(0, 0, 0, 0.2); +} +html .popover .popover-title { + background: rgba(0, 0, 0, 0.3); + border-bottom: 1px solid #999; +} +html .popover .popover-content table { + background: transparent !important; +} +html .popover.right > .arrow:after { + border-right-color: rgba(0, 0, 0, 0.1) !important; +} +html .table.ipaddresses tbody tr:hover td table.popover_table tbody td { + background: transparent !important; + border: none !important; +} +html table#userModSelf.table td { + border: none !important; +} +html table.table-certificates tr.warning td { + color: #FFC47B !important; +} +html table.table-certificates tr.warning td a { + color: #FFC47B !important; +} +html table.table-certificates tr.danger td { + color: #EA6C85 !important; +} +html table.table-certificates tr.danger td a { + color: #EA6C85 !important; +} +html .legend .legendLabel { + color: #999; +} +html ul[class*=submenu-] li { + /* horizontal ul lines */ + padding-left: 20px !important; + background: url("../images/li-dark.png") no-repeat 4px -3px; +} +html ul[class*=submenu-] li:last-child { + /* last child in inactive line */ + background: url("../images/ul-li-bg-dark.png") no-repeat 3px 0px; +} +html li.active ul[class*=submenu-] li:last-child { + /* last child in active line */ + background: url("../images/ul-li-bg-active-dark.png") no-repeat 3px 0px !important; +} +html ul#subnets li.folder li.leaf.active { + background: #F1FAFE url("../images/li-dark.png") no-repeat 4px -3px !important; +} +html ul[class*=submenu-] li.folder.active { + background: #F1FAFE url("../images/ul-li-bg-dark.png") no-repeat 3px 0px !important; +} +html ul#subnets li.folder li.leaf.active:last-of-type { + background: #F1FAFE url("../images/ul-li-bg-active-dark.png") no-repeat 3px 0px !important; +} +html table#manageSubnets tr td:nth-child(1) a { + color: white; +} +html table#manageSubnets .structure { + background: url("../images/sn-bg-dark.png") 0px 7px; +} +html table#manageSubnets .structure-last { + background: url("../images/sn-bg-last-dark.png") no-repeat 1px 0px; +} +html ul.submenu-dns li { + background: url("../images/li-dns-dark.png") no-repeat 4px -3px !important; + font-size: 10px; +} +html ul.submenu-dns li:last-child { + background: url("../images/li-dns-last-dark.png") no-repeat 4px -3px !important; +} +html table#manageSubnets > tbody > tr:last-child .structure { + background: url("../images/sn-bg-last-dark.png") no-repeat 1px 0px !important; +} +html ul.submenu-linked li { + background: url("../images/li-dns-dark.png") no-repeat 4px -1px !important; +} +html ul.submenu-linked li:last-child { + background: url("../images/li-dns-last-dark.png") no-repeat 4px -1px !important; +} +html table tr.similar td:nth-child(1) { + background: url("../images/li-dns-dark.png") no-repeat 17px -5px !important; +} +html table tr.similar-last td:nth-child(1) { + background: url("../images/li-dns-last-dark.png") no-repeat 17px -5px !important; +} +html table tr.similar-last td:nth-child(1) { + background: url("../images/li-dns-last-dark.png") no-repeat 17px -5px !important; +} +html .ipaddress_subnet .subnet_badge { + background: rgba(0, 0, 0, 0.3) !important; +} +html .table.ipaddresses tbody tr:hover td, +html .table.slaves tbody tr:hover td, +html .table-striped tbody tr:hover td { + background: rgba(0, 0, 0, 0.1) !important; +} +html .table-striped tbody tr:nth-child(even) td.th { + background: transparent !important; +} +html table.table.ipaddresses .unused { + text-shadow: none !important; + color: white; + background: rgba(0, 255, 0, 0.1) !important; +} +html table.table.ipaddresses tr.dhcp td { + text-shadow: none !important; + color: white; + background: rgba(0, 0, 0, 0.1) !important; +} +html table.table.ipaddresses tr:hover td.unused { + background: rgba(0, 255, 0, 0.1) !important; +} +html table.table.ipaddresses td { + border-bottom: none !important; + border-top: 1px solid #58606b !important; +} +html table.table.address_details td { + border: none !important; +} +html table#logs tr td.severity span { + color: #272b30; +} +html table#logs tr.success td a { + border: none; +} +html table#logs tr.danger td { + background: #a94442 !important; +} +html table.table-threshold td { + border-bottom: none !important; +} +html .ip_vis span { + background: rgba(0, 0, 0, 0.2) !important; +} +html .ip_vis span.ip-unused { + border-color: #999; + color: #ccc !important; +} +html .ip_vis span.ip-0 { + color: white !important; +} +html .ip_vis span.ip-1 { + border-color: #a94442; + background-color: rgba(255, 0, 0, 0.05) !important; + color: white !important; +} +html .ip_vis span.ip-2 { + border-color: #d6e9c6; + background: rgba(0, 255, 0, 0.05) !important; + color: white !important; +} +html .ip_vis span.ip-4 { + border-color: #58ACFA; + color: #e3eaf2 !important; +} +html #popupOverlay { + background: rgba(0, 0, 0, 0.6) !important; +} +html #popup { + background: url("../images/bg-light.png") no-repeat center center fixed; + -webkit-background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + background-size: cover; + background-repeat: repeat; + color: #e5e5e5; + background-color: rgba(0, 0, 0, 0); + border: 2px solid rgba(0, 0, 0, 0.2); +} +html #popup div.pHeader { + background: rgba(0, 0, 0, 0.3); + border-bottom: 1px solid #999; +} +html #popup div.pContent { + background: transparent url("../images/noise.png"); +} +html #popup div.pFooter { + background: rgba(0, 0, 0, 0.3); + border-top: 1px solid #999 !important; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} +html #popup div.pFooter .btn { + border: 1px solid #58606b !important; +} +html #popup div.col-xs-12.col-md-6 { + border-right-color: #58606b !important; +} +html #popup .sortable li { + background: rgba(0, 0, 0, 0.2); + border: 1px solid #58606b !important; +} +html .bootstrap-switch { + border-color: #999; +} +html .bootstrap-switch .bootstrap-switch-label { + background: transparent; +} +html .bootstrap-switch .bootstrap-switch-handle-off, +html .bootstrap-switch .bootstrap-switch-handle-on { + color: white !important; + background: rgba(0, 0, 0, 0.2) !important; +} +html table.table.table-top th { + background: white; + margin: 0px; +} +html table.table.table { + background: transparent; +} +html table.table.table-noborder th, +html table.table.table-noborder td { + border: none; +} +html table.table.statistics td { + border: none !important; + padding-top: 2px; + padding-bottom: 2px; +} +html table.table td { + font-size: 13px; +} +html table.table td.th { + padding-top: 25px !important; + border-bottom: 1px solid #999 !important; +} +html table.table td.border-bottom { + border-bottom: 1px solid #999 !important; +} +html table.table tr.success td { + text-shadow: none !important; + color: white; + background: rgba(0, 255, 0, 0.1) !important; + border-bottom: none !important; + border-bottom: none !important; +} +html table.table tr.success td btn, html table.table tr.success td a { + border: 1px solid #999; +} +html table.table tr.success:hover td { + background: rgba(0, 255, 0, 0.1) !important; +} +html table.vlans tr td { + border: none !important; +} +html table.vlans tr.change td { + border-top: 1px solid #58606b !important; +} +html .bootstrap-table .table { + border-bottom: 1px solid #58606b; +} +html table.table th, html table.table tr, html table.table td { + background: transparent !important; +} +html table.table td.ip a, +html table.table td.ipaddress a { + color: white !important; +} +html table.table th { + background: rgba(0, 0, 0, 0.2) !important; + border-bottom: 1px solid #272b30 !important; + border-top: none !important; +} +html table.table th i.fa { + background: rgba(0, 0, 0, 0.2); + border: 1px solid rgba(255, 255, 255, 0.4); +} +html table.table tr:hover td { + background: rgba(0, 0, 0, 0.1) !important; +} +html table.table tr.weeknumber th { + border-top: none; +} +html table.table tr.today { + background: rgba(88, 172, 250, 0.1) !important; +} +html table.table tr.pw-status-V_teku { + background: #3c763d !important; +} +html table.table tr.pw-status-V_teku a { + color: white; +} +html table.table tr.pw-status-V_teku span.badge-warning { + border: 1px solid white; + color: white !important; +} +html table.table td { + border-top: 1px solid #58606b !important; +} +html table.table td.izpad { + color: white; +} +html table.table.table-noborder tr th, html table.table.table-noborder tr td { + background: transparent !important; + border: none !important; +} +html input, +html textarea, +html select { + background: rgba(0, 0, 0, 0.2) !important; + color: white !important; +} +html input.btn.btn-success, +html textarea.btn.btn-success, +html select.btn.btn-success { + border: 1px solid #58606b !important; +} +html select { + background: rgba(255, 255, 255, 0.2) !important; +} +html input[type=submit] { + background: rgba(0, 0, 0, 0.2) !important; +} +html select { + line-height: 24px; +} +html select optgroup { + color: #58606b; +} +html select option { + color: #999; +} +html table#subnetsMenu td#subnetsLeft { + border-right: 1px solid #58606b; +} +html table#subnetsMenu td#subnetsLeft #leftMenu { + margin-top: 0px; +} +html table#subnetsMenu td#subnetsLeft h4 { + padding: 10px; + margin: 0px; + background: rgba(0, 0, 0, 0.3); + border-bottom: 1px solid #58606b; +} +html table#subnetsMenu td#subnetsLeft hr { + display: none; +} +html table#subnetsMenu td#subnetsLeft a { + color: white; +} +html table#subnetsMenu td#subnetsLeft li.leaf i { + color: #999 !important; +} +html table#subnetsMenu td#subnetsLeft li.active { + background-color: rgba(0, 0, 0, 0.4) !important; + background-color: #4b7387 !important; + text-shadow: none; + border-top: 1px solid #58606b; + border-bottom: 1px solid #58606b; +} +html table#subnetsMenu td#subnetsLeft li.active i { + background: transparent; +} +html .adminMenu, +html .toolsMenu { + background: transparent !important; +} +html .adminMenu .panel-heading, +html .toolsMenu .panel-heading { + background: transparent !important; + border-bottom: 1px solid rgba(0, 0, 0, 0.5); +} +html .adminMenu ul.list-group, +html .toolsMenu ul.list-group { + background: transparent !important; +} +html .adminMenu ul.list-group li, +html .toolsMenu ul.list-group li { + background: rgba(0, 0, 0, 0.2) !important; + border-left-color: #58606b !important; +} +html .adminMenu ul.list-group li.list-group-item, +html .toolsMenu ul.list-group li.list-group-item { + border: none; +} +html .adminMenu ul.list-group li.active, +html .toolsMenu ul.list-group li.active { + background: rgba(0, 0, 0, 0.6) !important; + border: 1px solid #58606b; +} +html .adminMenu ul.list-group li:hover, +html .toolsMenu ul.list-group li:hover { + background: rgba(0, 0, 0, 0.3) !important; +} +html #dashboard .inner { + background: rgba(0, 0, 0, 0.2) !important; + border: 1px solid #58606b; +} +html #dashboard .inner h4 { + background: rgba(0, 0, 0, 0.35) !important; + text-shadow: none; +} +html #dashboard .inner .hContent { + border-top: 1px solid #58606b !important; +} +html #dashboard .inner .hContent .icon { + border-right: 1px solid #58606b !important; +} +html #dashboard .widget-dash .inner { + box-shadow: none !important; +} +html #dashboard #w-access_logs a, +html #dashboard #w-error_logs a { + color: white; +} +html #dashboard span.severity0 { + border: 1px solid #58606b; + background: rgba(0, 0, 0, 0.2); + padding: 2px 5px; + border-radius: 4px; +} +html #dashboard span.severity1 { + border: 1px solid #58606b; + background: rgba(0, 0, 0, 0.2); + padding: 2px 5px; + border-radius: 4px; +} +html #dashboard span.severity2 { + border: 1px solid #58606b; + background: rgba(0, 0, 0, 0.2); + padding: 2px 5px; + border-radius: 4px; +} +html .menu-tools #dashboard .inner .hContent, +html .menu-admin #dashboard .inner .hContent { + border-top: none !important; +} +html .adminMenu ul.list-group li.active { + border-left-color: #a94442 !important; +} +html .menu-tools ul.list-group li.active { + border-left-color: #58ACFA !important; +} +html .footer { + border-top: 1px solid #58606b; +} +html .pagination ul li a { + background: transparent; + border: 1px solid #999; +} +html .pagination ul li a:hover { + background: rgba(0, 0, 0, 0.2); + border: 1px solid #999; +} +html .pagination ul li.active a, +html .pagination ul li.active a:hover { + background: rgba(0, 0, 0, 0.5); +} +html .res_val { + background: rgba(0, 0, 0, 0.2) !important; +} +html .ui-slider-handle, +html .slider-handle { + background: #58606b !important; +} +html .ui-slider, +html .slider-track, +html .slider-selection { + background: #999 !important; +} +html .progress { + background: rgba(0, 0, 0, 0.1) !important; +} +html .progress .progress-limit-negative { + background: transparent !important; + border-color: rgba(0, 0, 0, 0.1) !important; +} +html .progress .progress-bar-info { + background: rgba(0, 255, 0, 0.15); + border-color: rgba(0, 0, 0, 0.1) !important; + color: white; +} +html table#switchMainTable tr.switch-title, +html table#vrf tr.vrf-title, +html table#subnets tr.subnets-title, +html table#settings tr.settings-title, +html table#settings { + background: transparent !important; +} +html table#switchMainTable tr.switch-title th, +html table#vrf tr.vrf-title th, +html table#subnets tr.subnets-title th, +html table#settings tr.settings-title th, +html table#settings th { + background: transparent !important; + border-bottom: none !important; +} +html table#switchMainTable tr.switch-title th h4, +html table#vrf tr.vrf-title th h4, +html table#subnets tr.subnets-title th h4, +html table#settings tr.settings-title th h4, +html table#settings th h4 { + background: transparent !important; +} +html table#switchMainTable tr.switch-title td, html table#switchMainTable tr.switch-title th, +html table#vrf tr.vrf-title td, +html table#vrf tr.vrf-title th, +html table#subnets tr.subnets-title td, +html table#subnets tr.subnets-title th, +html table#settings tr.settings-title td, +html table#settings tr.settings-title th, +html table#settings td, +html table#settings th { + border: none !important; +} +html table#settings tr.settings-title { + border-bottom: 1px solid #58606b; +} +html #gmap { + border-color: rgba(0, 0, 0, 0.2); +} +html ul#sortable { + max-width: 500px; +} +html ul#sortable li { + background: rgba(0, 0, 0, 0.2); + border-color: #999 !important; +} +html .instructions { + background: transparent; + border: 1px solid #999; +} +html ul.icon-ul > li { + border: 1px solid rgba(0, 0, 0, 0.4) !important; +} +html .ipreqMenu { + background: rgba(0, 0, 0, 0.2); + border: 1px solid #58606b; + right: 5px; +} +html .fixed-table-loading { + background: rgba(0, 0, 0, 0.3); +} +html div#login { + background: rgba(0, 0, 0, 0.2); + border: 1px solid #999 !important; +} +html div#login legend { + color: white; + border-bottom: 1px solid #58606b !important; +} +html div#login form { + border-bottom: none; +} +html div#login .iprequest { + background: rgba(0, 0, 0, 0.1); + border-color: #58606b; + text-shadow: none; +} +html .install h4 { + border-bottom: none !important; +} +html #searchSelect { + background: #40454a !important; +} + +.panel.panel-default { + background: transparent; + border: 1px solid rgba(255, 255, 255, 0.1) !important; +} +.panel.panel-default .panel-heading { + background: rgba(0, 0, 0, 0.3); + border-bottom: 1px solid rgba(255, 255, 255, 0.5) !important; + color: white; +} +.panel.panel-default .list-group-item { + background: transparent; + border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important; +} +.panel.panel-default .list-group-item:last-child { + border-bottom: none; +} +.panel.panel-default:last-child { + border-bottom: none !important; +} + +.octicon-passkey-fill { + fill: #ccc; +} + +.list-group-item { + border: none; +} + +.btn-default.disabled.focus, +.btn-default.disabled:focus, +.btn-default.disabled:hover, +.btn-default[disabled].focus, +.btn-default[disabled]:focus, +.btn-default[disabled]:hover { + background: rgba(0, 0, 0, 0.1) !important; +} + +.subnet_map_found { + border-color: #d6e9c6; + background: rgba(0, 255, 0, 0.15) !important; + color: white !important; +} + +.subnet_map_found:hover { + background: rgba(0, 255, 0, 0.2) !important; +} + +.subnet_map_notfound { + background-color: rgba(255, 0, 0, 0.15) !important; + color: white !important; +} + +.subnet_map_found a { + color: white !important; +} + +.ip_vis_subnet span { + box-shadow: 0px 0px 1px #777; + border: 1px solid rgba(255, 255, 255, 0.2); + padding: 3px; + padding-top: 7px; + text-align: center; + font-size: 12px; + margin-right: 0px; + margin-bottom: 0px; + width: 120px; + height: 30px; + float: left; + border-left: none; +} + +.clearfix1 { + border-left: 1px solid rgba(255, 255, 255, 0.2); +} + +.ip_vis_subnet span a { + font-size: 12px; +} + /*# sourceMappingURL=bootstrap-custom-dark.css.map */ diff --git a/css/bootstrap/bootstrap-custom-dark.scss b/css/bootstrap/bootstrap-custom-dark.scss index 5f284395e114a1c91c5d7ede1b8dd8f6137a4c19..6e04a901fa6159aedc222cd1255895f50ba16e52 100644 --- a/css/bootstrap/bootstrap-custom-dark.scss +++ b/css/bootstrap/bootstrap-custom-dark.scss @@ -1237,6 +1237,8 @@ html { legend { color: white; + border-bottom: 1px solid $color_gray_5 !important; + } form { @@ -1263,6 +1265,45 @@ html { } +.panel.panel-default { + background: transparent; + + border: 1px solid rgba(255,255,255,0.1) !important; + + .panel-heading { + background: rgba(0, 0, 0, 0.3); + border-bottom: 1px solid rgba(255,255,255,0.5) !important; + color: white; + } + + .list-group-item { + background: transparent; + border-bottom: 1px solid rgba(255,255,255,0.1) !important; + } + .list-group-item:last-child { + border-bottom: none; + } + + &:last-child { + border-bottom: none !important; + } +} +.octicon-passkey-fill { + fill: #ccc; +} +.list-group-item { + border: none; +} +.btn-default.disabled.focus, +.btn-default.disabled:focus, +.btn-default.disabled:hover, +.btn-default[disabled].focus, +.btn-default[disabled]:focus, +.btn-default[disabled]:hover { + background: rgba(0,0,0,0.1) !important; +} + + .subnet_map_found { border-color: #d6e9c6; background: rgba(0,255,0,0.15) !important; diff --git a/css/bootstrap/bootstrap-custom.css b/css/bootstrap/bootstrap-custom.css index 0b7eed90574db914f3c872577af86f594527e863..a10b728f2b4df8f3d9da14537e9d1c13a613e088 100644 --- a/css/bootstrap/bootstrap-custom.css +++ b/css/bootstrap/bootstrap-custom.css @@ -2070,10 +2070,19 @@ div#login .login th { div#login .login td { text-align: right; } -div#loginCheck .alert { - margin: auto; +div#loginCheck .alert, +div#loginCheckPasskeys .alert { +/* margin: auto; */ margin-top: 10px; - width: 500px; + padding: 5px; +/* width: 500px; */ +} +div#loginCheck .alert-danger:before, +div#loginCheckPasskeys .alert-danger:before { + font-family: "FontAwesome"; + content: "\f071"; + padding-right: 8px; + color: #a94442; } .iprequest { background: #F1FAFE; diff --git a/db/SCHEMA.sql b/db/SCHEMA.sql index f4cb81c84363674d5171e992b56a95b41243e62b..5b02782e3f59b04cd01cf8882dc67249cd3ec4c6 100755 --- a/db/SCHEMA.sql +++ b/db/SCHEMA.sql @@ -224,6 +224,7 @@ CREATE TABLE `settings` ( `2fa_name` VARCHAR(32) NULL DEFAULT 'phpipam', `2fa_length` INT(2) NULL DEFAULT '16', `2fa_userchange` BOOL NOT NULL DEFAULT '1', + `passkeys` TINYINT(1) NULL DEFAULT '1', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /* insert default values */ @@ -370,6 +371,7 @@ CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL DEFAULT '', `authMethod` INT(2) NULL DEFAULT 1, + `passkey_only` TINYINT(1) NOT NULL DEFAULT '0', `password` CHAR(128) DEFAULT NULL, `groups` varchar(1024) DEFAULT NULL, `role` text, @@ -1029,6 +1031,26 @@ CREATE TABLE `vaultItems` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +# Dump of table passkeys +# ------------------------------------------------------------ +DROP TABLE IF EXISTS `passkeys`; + +-- passkey table +CREATE TABLE `passkeys` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `credentialId` text NOT NULL, + `keyId` text NOT NULL, + `credential` text NOT NULL, + `comment` text, + `created` timestamp NULL DEFAULT NULL, + `used` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `user_id` (`user_id`), + CONSTRAINT `user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + # Dump of table nominatim # ------------------------------------------------------------ DROP TABLE IF EXISTS `nominatim`; @@ -1059,4 +1081,4 @@ CREATE TABLE `nominatim_cache` ( # ------------------------------------------------------------ UPDATE `settings` SET `version` = "1.6"; -UPDATE `settings` SET `dbversion` = 39; +UPDATE `settings` SET `dbversion` = 40; diff --git a/functions/PHPMailer b/functions/PHPMailer index ee4090bd62ad3ded3eac19d6fd0213abbe3596f1..cbe9d8d9a9adb7dff77852a3cfc9b63ede3e7a89 160000 --- a/functions/PHPMailer +++ b/functions/PHPMailer @@ -1 +1 @@ -Subproject commit ee4090bd62ad3ded3eac19d6fd0213abbe3596f1 +Subproject commit cbe9d8d9a9adb7dff77852a3cfc9b63ede3e7a89 diff --git a/functions/classes/class.User.php b/functions/classes/class.User.php index 263af2bde30c40232c8f65b8830b059da7082201..7a92941bd0af45a7506f75b8ac94a272bd26ed25 100644 --- a/functions/classes/class.User.php +++ b/functions/classes/class.User.php @@ -819,10 +819,10 @@ class User extends Common_functions { $this->Log->write ( _("User login"), _('Error: Invalid authentication method'), 2 ); $this->Result->show("danger", _("Error: Invalid authentication method"), true); } - # disabled - elseif ($this->user->disabled=="Yes") { - $this->Result->show("danger", _("Your account has been disabled").".", true); - } + # disabled - we made separate check on this, therwise we reveal info before user is authenticated + // elseif ($this->user->disabled=="Yes") { + // $this->Result->show("danger", _("Your account has been disabled").".", true); + // } else { # set method name variable $authmethodtype = $this->authmethodtype; @@ -843,12 +843,12 @@ class User extends Common_functions { /** * tries to fetch user datails from database by username if not already existing locally * - * @access private + * @access public * @param string $username * @param bool $force * @return void */ - private function fetch_user_details ($username, $force = false) { + public function fetch_user_details ($username, $force = false) { # only if not already active if(!is_object($this->user) || $force) { try { @@ -950,6 +950,9 @@ class User extends Common_functions { private function auth_local ($username, $password) { # auth ok if(hash_equals($this->user->password, crypt($password, $this->user->password))) { + # check login restrictions for authenticated user + $this->check_login_restrictions ($username); + # save to session $this->write_session_parameters (); @@ -986,6 +989,9 @@ class User extends Common_functions { * @return void */ public function auth_http ($username, $password) { + # check login restrictions for authenticated user + $this->check_login_restrictions ($username); + # save to session $this->write_session_parameters (); @@ -1080,6 +1086,9 @@ class User extends Common_functions { # authenticate try { if ($adldap->authenticate($username, $password)) { + # check login restrictions for authenticated user + $this->check_login_restrictions ($username); + # save to session $this->write_session_parameters(); @@ -1193,6 +1202,8 @@ class User extends Common_functions { # authenticate user if($auth) { + # check login restrictions for authenticated user + $this->check_login_restrictions ($username); # save to session $this->write_session_parameters (); @@ -1222,6 +1233,9 @@ class User extends Common_functions { * @return void */ private function auth_SAML2 ($username, $password = null) { + # check login restrictions for authenticated user + $this->check_login_restrictions ($username); + # save to session $this->write_session_parameters (); @@ -1234,6 +1248,246 @@ class User extends Common_functions { $this->block_remove_entry (); } + /** + * Check for any login restrictions after user has authenticated + * @method check_login_restrictions + * @param string $username + * @return void + */ + private function check_login_restrictions ($username = "") { + // is account disabled ? + if ($this->user->disabled=="Yes") { + $this->log_failed_access ($username); + $this->Log->write( _("login"), _("User account is disabled"), 2, $username ); + $this->Result->show("danger", _("User account is disabled"), true); + } + // is passkey login enforced ? + elseif ($this->settings->{'passkeys'}=="1") { + if ($this->user->passkey_only=="1") { + // check passkeys + $user_passkeys = $this->get_user_passkeys($this->user->id); + + // make sure it has passkeys configured + if (sizeof($user_passkeys)>0) { + $this->log_failed_access ($username); + $this->Log->write( _("Passkey login"), _("Passkey required for login"), 2, $username ); + $this->Result->show("danger", _("Only passkey authentication is possible for this account"), true); + } + } + } + } + + /** + * Process succesfull passkey auth + * @method auth_passkey_success + * @param string $encodedCredential + * @return bool + */ + public function auth_passkey ($credentialId = "", $encodedCredential = "", $keyId = "") { + # save passkey + $this->update_passkey ($credentialId, $encodedCredential); + + # get user details from authenticated user_id + $this->fetch_passkey_user_details (); + + # failure + if(!isset($this->user->username)) { + throw new Exception ("Cannot fetch credentials from userid"); + } + header('HTTP/1.1 500 Cannot fetch credentials from userid'); + + # set session parameters + $_SESSION['ipamusername'] = $this->user->username; + $_SESSION['ipamlanguage'] = $this->fetch_lang_details (); + $_SESSION['keyId'] = $keyId; + $_SESSION['lastactive'] = time(); + + # remove passkey temp session user id + $this->clear_passkey_user_id (); + + # save to session + $this->write_session_parameters (); + # log + $this->Log->write( _("User login"), _("User")." ".$this->user->real_name." "._("logged in"), 0, $username ); + + # write last logintime + $this->update_login_time (); + + # remove possible blocked IP + $this->block_remove_entry (); + + # ok + return true; + } + + + + + + + + + + + + + + /* @passkey -------------------- */ + + + /** + * Fetch user details based on passkey ID + * @method fetch_passkey_user_details + * @return obj + */ + private function fetch_passkey_user_details () { + try { + $user = $this->Database->getObject("users", $this->get_passkey_user_id()); + + if(!is_null($user)) { + $this->user = $user; + } + else { + header('HTTP/1.1 404 Not found'); + $this->block_ip (); + $this->Log->write ( _("User login"), _('Failed passkey login'), 2, $this->get_passkey_user_id() ); + } + } + catch (Exception $e) { + header('HTTP/1.1 500 '.$e->getMessage()); + return false; + } + } + + /** + * Get passkeys for user + * @method get_user_passkeys + * @param bool $user_id + * @return array + */ + public function get_user_passkeys ($user_id = false) { + // set userId + $user_id = $user_id===false ? $this->user->id : $user_id; + try { + return $this->Database->findObjects("passkeys", "user_id", $user_id); + } + catch (Exception $e) { + !$this->debugging ? : $this->Result->show("danger", $e->getMessage(), false); + } + } + + /** + * Get passkey for user based on key_id + * @method get_user_passkeys + * @param bool $user_id + * @return array + */ + public function get_user_passkey_by_keyId ($keyId = false) { + try { + return $this->Database->findObject("passkeys", "keyId", $keyId); + } + catch (Exception $e) { + !$this->debugging ? : $this->Result->show("danger", $e->getMessage(), false); + } + } + + /** + * Save new passkey + * @method save_passkey + * @param string $credential + * @return bool + */ + public function save_passkey ($credential = "", $credentialId = NULL, $keyId = NULL) { + try { + $this->Database->insertObject("passkeys", ["user_id"=>$this->user->id, "credentialId"=>$credentialId, "credential"=>$credential, "keyId"=>$keyId, "created"=>date("Y-m-d H:i:s§")]); + // ok + return true; + } + catch (Exception $e) { + header('HTTP/1.1 500 '.$e->getMessage()); + return false; + } + } + + /** + * Rename passkey + * @method rename_passkey + * @param int $id + * @param string $comment + * @return bool + */ + public function rename_passkey ($id = 0, $comment = "") { + try { + $this->Database->updateObject("passkeys", ["id"=>$id, "comment"=>$comment]); + return true; + } + catch (Exception $e) { + $this->debugging ? : $this->Result->show("danger", _("Database error: ").$e->getMessage(), false); + return false; + } + } + + /** + * Delete passkey + * @method delete_passkey + * @param int $id + * @return bool + */ + public function delete_passkey ($id = 0) { + try { + $this->Database->deleteObject("passkeys", $id); + return true; + } + catch (Exception $e) { + $this->debugging ? : $this->Result->show("danger", _("Database error: ").$e->getMessage(), false); + return false; + } + } + + /** + * Update passkey on succesfull login + * @method save_passkey + * @param string $credential + * @return bool + */ + public function update_passkey ($credentialId = "", $updated_credential = "") { + try { + $this->Database->updateObject("passkeys", ["credentialId"=>$credentialId, "credential"=>$updated_credential, "used"=>date("Y-m-d H:i:s")], "credentialId"); + // ok + return true; + } + catch (Exception $e) { + header('HTTP/1.1 500 '.$e->getMessage()); + return false; + } + } + + /** + * Save authneitcation user id to session + * @method set_passkey_user_id + * @param int $userid + */ + public function set_passkey_user_id ($userid = 0) { + $_SESSION['passkey_user_id'] = $userid; + } + + /** + * Return user id + * @method get_passkey_user_id + * @return int + */ + public function get_passkey_user_id () { + return $_SESSION['passkey_user_id']; + } + + /** + * Remove temporary clear_passkey_user_id + * @method clear_passkey_user_id + * @return [type] + */ + public function clear_passkey_user_id () { + unset($_SESSION['passkey_user_id']); + } @@ -1355,6 +1609,7 @@ class User extends Common_functions { "menuCompact" => $this->verify_checkbox(@$post['menuCompact']), "theme" => $post['theme'], "2fa" => $this->verify_checkbox(@$post['2fa']), + "passkey_only" => $this->verify_checkbox(@$post['passkey_only']), ); if(!is_blank($post['password1'])) { $items['password'] = $this->crypt_user_pass ($post['password1']); @@ -1431,8 +1686,6 @@ class User extends Common_functions { - - /** * @blocking IP functions * ------------------------------ @@ -1829,6 +2082,7 @@ class User extends Common_functions { // return return $level=="0" ? "<span class='badge badge1 badge5 alert-danger'>"._($this->parse_permissions ($level))."</span>" : "<span class='badge badge1 badge5 alert-success'>"._($this->parse_permissions ($level))."</span>"; } + } /** * Fake User object for install/scripts diff --git a/functions/composer.json b/functions/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..74c9d4e0a66373dbe8275fed517a1be4377f35af --- /dev/null +++ b/functions/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "firehed/webauthn": "dev-main" + } +} diff --git a/functions/composer.lock b/functions/composer.lock new file mode 100644 index 0000000000000000000000000000000000000000..ebb1323a00e8ad91408ed5f10b4fbfaaaec5ef76 --- /dev/null +++ b/functions/composer.lock @@ -0,0 +1,122 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "7bf730768732814406175c12775f3390", + "packages": [ + { + "name": "firehed/cbor", + "version": "0.1.0", + "source": { + "type": "git", + "url": "https://github.com/Firehed/cbor-php.git", + "reference": "eef67b1b5fdf90a3688fc8d9d13afdaf342c4b80" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Firehed/cbor-php/zipball/eef67b1b5fdf90a3688fc8d9d13afdaf342c4b80", + "reference": "eef67b1b5fdf90a3688fc8d9d13afdaf342c4b80", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "^8.1" + }, + "suggest": { + "ext-bcmath": "Enables parsing of very large values" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firehed\\CBOR\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eric Stern", + "email": "eric@ericstern.com" + } + ], + "description": "CBOR decoder", + "homepage": "https://github.com/Firehed/CBOR", + "keywords": [ + "cbor" + ], + "support": { + "issues": "https://github.com/Firehed/cbor-php/issues", + "source": "https://github.com/Firehed/cbor-php/tree/master" + }, + "time": "2019-05-14T06:31:13+00:00" + }, + { + "name": "firehed/webauthn", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/Firehed/webauthn-php.git", + "reference": "267d04a6d2926d9ab6d7630fb86a92410eb6b36c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Firehed/webauthn-php/zipball/267d04a6d2926d9ab6d7630fb86a92410eb6b36c", + "reference": "267d04a6d2926d9ab6d7630fb86a92410eb6b36c", + "shasum": "" + }, + "require": { + "ext-hash": "*", + "ext-openssl": "*", + "firehed/cbor": "^0.1.0", + "php": "^8.1" + }, + "require-dev": { + "maglnet/composer-require-checker": "^4.1", + "mheap/phpunit-github-actions-printer": "^1.5", + "nikic/php-parser": "^4.14", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.3", + "squizlabs/php_codesniffer": "^3.5" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-4": { + "Firehed\\WebAuthn\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eric Stern", + "email": "eric@ericstern.com" + } + ], + "description": "Web Authentication", + "support": { + "issues": "https://github.com/Firehed/webauthn-php/issues", + "source": "https://github.com/Firehed/webauthn-php/tree/main" + }, + "time": "2023-11-16T23:07:44+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "firehed/webauthn": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/functions/composer.phar b/functions/composer.phar new file mode 100755 index 0000000000000000000000000000000000000000..e766506542d36f55d0200e9986b5c33a6d2919a1 Binary files /dev/null and b/functions/composer.phar differ diff --git a/functions/functions.php b/functions/functions.php index 3a673f9898b27b6735b5baac62a5d3849fbfaf9e..91aa1161395237cdaba5c6e5a0138da516bea148 100755 --- a/functions/functions.php +++ b/functions/functions.php @@ -39,7 +39,7 @@ if(php_sapi_name()!="cli") if(Config::ValueOf('debugging')==true) { ini_set('display_errors', 1); ini_set('display_startup_errors', 1); - error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT); + error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED); } else { disable_php_errors(); diff --git a/functions/locale/changes.txt b/functions/locale/changes.txt index 5fcd1cdc91e097e3ba36927cff165e9abe31727b..34a1411aa54ff4baa74c46113fe49d6d2436a737 100644 --- a/functions/locale/changes.txt +++ b/functions/locale/changes.txt @@ -5514,3 +5514,192 @@ msgstr "" msgid "Scan agents" msgstr "" + + +### 1.6.1 + +msgid "passkey" +msgstr "" + +msgid "Passkey" +msgstr "" + +msgid "2fa account status" +msgstr "" + +msgid "2fa disabled" +msgstr "" + +msgid "2fa enabled" +msgstr "" + +msgid "2fa status" +msgstr "" + +msgid "Account" +msgstr "" + +msgid "Check passkey you want to remove" +msgstr "" + +msgid "Details for your preferred authenticator application are below. Please write down your details, otherwise you will not be able to login to phpipam" +msgstr "" + +msgid "Disable 2fa for user" +msgstr "" + +msgid "Enable Passkeys" +msgstr "" + +msgid "Enable Vaults" +msgstr "" + +msgid "Enable Vaults for storing encrypted information" +msgstr "" + +msgid "Enable passkeys for passwordless login" +msgstr "" + +msgid "failed" +msgstr "" + +msgid "Failed passkey login" +msgstr "" + +msgid "has logged out" +msgstr "" + +msgid "Here you can change settings for two-factor authentication and get your 2fa secret." +msgstr "" + +msgid "Invalid theme" +msgstr "" + +msgid "logged in" +msgstr "" + +msgid "Login with a passkey" +msgstr "" + +msgid "Only passkey authentication is possible for this account" +msgstr "" + +msgid "Passkey login only" +msgstr "" + +msgid "Passkey only" +msgstr "" + +msgid "Passkey required for login" +msgstr "" + +msgid "Passkeys" +msgstr "" + +msgid "Select to only allow account login with passkey" +msgstr "" + +msgid "successful" +msgstr "" + +msgid "There are no passkeys set for user. Resetting passkey login only to false." +msgstr "" + +msgid "Two-factor authentication" +msgstr "" + +msgid "User account is disabled" +msgstr "" + +msgid "User permissions for phpipam modules" +msgstr "" + +msgid "You can also scan following QR code with your preferred authenticator application" +msgstr "" + +msgid "You can login to your account with normal authentication method only untill you create passkeys." +msgstr "" + +msgid "You can only login to your account using passkeys" +msgstr "" + +msgid "Passwordless authentication" +msgstr "" + +msgid "Account details" +msgstr "" + +msgid "You can login to your account with with passkeys only" +msgstr "" + +msgid "This can be changed under Account details tab" +msgstr "" + +msgid "Your passkeys" +msgstr "" + +msgid "Added on" +msgstr "" + +msgid "Last used" +msgstr "" + +msgid "You authenticated with this passkey" +msgstr "" + +msgid "Add a passkey" +msgstr "" + +msgid "Rename" +msgstr "" + +msgid "Here you can manage passkey authentication for your account" +msgstr "" + +msgid "Passkeys are a password replacement that validates your identity using touch, facial recognition, a device password, or a PIN" +msgstr "" + +msgid "Name your passkey" +msgstr "" + +msgid "Duplicates" +msgstr "" + +msgid "Duplicated subnets" +msgstr "" + +msgid "Duplicated hosts" +msgstr "" + +msgid "No duplicate subnets found" +msgstr "" + +msgid "Routing" +msgstr "" + +msgid "BGP routing" +msgstr "" + +msgid "Add peer" +msgstr "" + +msgid "Peer name" +msgstr "" + +msgid "Peer AS" +msgstr "" + +msgid "Local AS" +msgstr "" + +msgid "Peer address" +msgstr "" + +msgid "Local address" +msgstr "" + +msgid "Local Address" +msgstr "" + +msgid "BGP type" +msgstr "" diff --git a/functions/locale/en_GB.UTF-8/LC_MESSAGES/phpipam.po b/functions/locale/en_GB.UTF-8/LC_MESSAGES/phpipam.po index e6dba6dc83f47cf19d571bf516263b549c9a77e0..b099a804cf515241c23db1b442bf1fd6f9187bef 100644 --- a/functions/locale/en_GB.UTF-8/LC_MESSAGES/phpipam.po +++ b/functions/locale/en_GB.UTF-8/LC_MESSAGES/phpipam.po @@ -1,4 +1,4 @@ -# PHPIPAM translations file - template +# PHPIPAM translations file - English # Copyright (C) phpipam 2013 # This file is distributed under the same license as the phpipam package. # miha petkovsek <miha.petkovsek@gmail.com>, 2013. @@ -7,72 +7,13 @@ # "Project-Id-Version: 1.40\n" # "Report-Msgid-Bugs-To: Miha Petkovsek <miha.petkovsek@gmail.com>\n" # "POT-Creation-Date: 2013-04-02 14:33+0200\n" -# "PO-Revision-Date: 2015-10-07 14:33+0200\n" -# "Last-Translator: \n" -# "Language: English\n" +# "PO-Revision-Date: 2017-02-21 14:33+0200\n" +# "Last-Translator: Miha Petkovsek <miha.petkovsek@gmail.com>\n" +# "Language: Slovenian (sl_SI)\n" # "MIME-Version: 1.0\n" # "Content-Type: text/plain; charset=UTF-8\n" # "Content-Transfer-Encoding: 8bit\n" -#: functions/functions-install.php:85 functions/functions-install.php:133 -#: functions/functions-install.php:150 functions/functions-install.php:182 -#: functions/functions-install.php:199 functions/functions-install.php:236 -#: functions/functions-install.php:272 functions/functions-install.php:379 -#: functions/functions-install.php:453 functions/functions-install.php:495 -#: functions/functions-install.php:524 functions/functions-install.php:549 -#: functions/functions-install.php:576 functions/functions-admin.php:41 -#: functions/functions-admin.php:73 functions/functions-admin.php:147 -#: functions/functions-admin.php:185 functions/functions-admin.php:217 -#: functions/functions-admin.php:241 functions/functions-admin.php:387 -#: functions/functions-admin.php:507 functions/functions-admin.php:533 -#: functions/functions-admin.php:887 functions/functions-admin.php:1124 -#: functions/functions-admin.php:1204 functions/functions-admin.php:1238 -#: functions/functions-admin.php:1283 functions/functions-admin.php:1374 -#: functions/functions-admin.php:1493 functions/functions-admin.php:1633 -#: functions/functions-admin.php:1661 functions/functions-admin.php:1717 -#: functions/functions-admin.php:1750 functions/functions-admin.php:1848 -#: functions/functions-admin.php:1881 functions/functions-admin.php:1980 -#: functions/functions-admin.php:2012 functions/functions-admin.php:2106 -#: functions/functions-admin.php:2138 functions/functions-common.php:105 -#: functions/functions-common.php:154 functions/functions-common.php:177 -#: functions/functions-common.php:200 functions/functions-common.php:223 -#: functions/functions-common.php:246 functions/functions-common.php:396 -#: functions/functions-common.php:420 functions/functions-common.php:453 -#: functions/functions-common.php:709 functions/functions-network.php:90 -#: functions/functions-network.php:116 functions/functions-network.php:142 -#: functions/functions-network.php:197 functions/functions-network.php:231 -#: functions/functions-network.php:255 functions/functions-network.php:289 -#: functions/functions-network.php:313 functions/functions-network.php:337 -#: functions/functions-network.php:361 functions/functions-network.php:396 -#: functions/functions-network.php:420 functions/functions-network.php:454 -#: functions/functions-network.php:478 functions/functions-network.php:502 -#: functions/functions-network.php:526 functions/functions-network.php:550 -#: functions/functions-network.php:586 functions/functions-network.php:610 -#: functions/functions-network.php:641 functions/functions-network.php:670 -#: functions/functions-network.php:694 functions/functions-network.php:802 -#: functions/functions-network.php:873 functions/functions-network.php:956 -#: functions/functions-network.php:1252 functions/functions-network.php:1289 -#: functions/functions-network.php:1313 functions/functions-network.php:1366 -#: functions/functions-network.php:1417 functions/functions-network.php:1460 -#: functions/functions-network.php:1487 functions/functions-network.php:1512 -#: functions/functions-network.php:1536 functions/functions-network.php:1562 -#: functions/functions-network.php:1590 functions/functions-network.php:1620 -#: functions/functions-network.php:1729 functions/functions-network.php:1754 -#: functions/functions-network.php:2072 functions/functions-network.php:2181 -#: functions/functions-network.php:2251 functions/functions-tools.php:220 -#: functions/functions-tools.php:241 functions/functions-tools.php:284 -#: functions/functions-tools.php:311 functions/functions-tools.php:333 -#: functions/functions-tools.php:355 functions/functions-tools.php:398 -#: functions/functions-tools.php:429 functions/functions-tools.php:453 -#: functions/functions-tools.php:478 functions/functions-tools.php:620 -#: functions/functions-tools.php:644 functions/functions-tools.php:666 -#: functions/functions-tools.php:688 functions/functions-tools.php:711 -#: functions/functions-tools.php:740 functions/functions-tools.php:766 -#: functions/functions-tools.php:817 functions/functions-tools.php:856 -#: functions/functions-tools.php:880 functions/functions-tools.php:905 -#: functions/functions-tools.php:941 site/ipaddr/modifyIpAddressCheck.php:102 -#: site/ipaddr/modifyIpAddressCheck.php:103 -#: site/ipaddr/modifyIpAddressCheck.php:179 msgid "Error" msgstr "" @@ -416,7 +357,7 @@ msgstr "" #: site/ipaddr/ipAddressPrintTableSlaves.php:34 site/admin/manageSubnet.php:76 #: site/admin/manageSubnetEdit.php:76 #: site/admin/manageSubnetEditResult.php:169 site/admin/manageRequests.php:24 -#: site/admin/ripeImportTelnet.php:74 site/admin/manageSubnetsplit.php:64 +#: site/admin/RIPE / ARINImportTelnet.php:74 site/admin/manageSubnetsplit.php:64 #: site/admin/manageSubnetresize.php:35 site/admin/manageSubnettruncate.php:37 msgid "Subnet" msgstr "" @@ -437,7 +378,7 @@ msgstr "" #: site/admin/manageVRFEdit.php:51 site/admin/manageVRFEdit.php:57 #: site/admin/manageVLANEdit.php:58 site/admin/manageVLANEdit.php:60 #: site/admin/manageRequests.php:26 site/admin/replaceFields.php:27 -#: site/admin/ripeImportTelnet.php:90 site/admin/manageSection.php:28 +#: site/admin/RIPE / ARINImportTelnet.php:90 site/admin/manageSection.php:28 #: site/admin/manageSectionEdit.php:51 site/admin/manageVLANs.php:36 #: site/admin/manageDevices.php:37 site/admin/groupEditPrint.php:57 #: site/login/requestIPform.php:82 @@ -597,7 +538,7 @@ msgstr "" #: site/ipaddr/mailNotifyIP.php:58 site/ipaddr/subnetDetailsSlaves.php:85 #: site/ipaddr/subnetDetails.php:96 site/admin/manageSubnet.php:78 #: site/admin/manageSubnetEdit.php:125 site/admin/manageVLANEdit.php:32 -#: site/admin/ripeImportTelnet.php:95 site/admin/manageVLANEditResult.php:47 +#: site/admin/RIPE / ARINImportTelnet.php:95 site/admin/manageVLANEditResult.php:47 #: site/admin/manageVLANEditResult.php:48 msgid "VLAN" msgstr "" @@ -695,6 +636,8 @@ msgstr "" #: site/ipaddr/subnetDetails.php:126 msgid "enabled" msgstr "" +msgid "Enabled" +msgstr "" #: site/tools/vrf.php:114 msgid "No subnets belonging to this VRF" @@ -878,9 +821,10 @@ msgid "Hostname search tips" msgstr "" #: site/tools/searchTips.php:46 -msgid "You can get all IP addresses some host uses and all ports it is connected to " +msgid "You can get all IP addresses some host uses and all ports it is connected to " "by entering hostname in search field" msgstr "" +"iščeš po DNS imenu" #: site/tools/searchTips.php:53 msgid "Device search tips" @@ -890,6 +834,7 @@ msgstr "" msgid "You can get all used / available ports and connected IP's / hostnames in " "some device by entering device name in search field" msgstr "" +"v iskalnik vpiši ime naprave" #: site/tools/searchTips.php:62 msgid "MAC search tips" @@ -899,6 +844,7 @@ msgstr "" msgid "You can search by MAC address list entering MAC in 00:1cd:d4:78:ec:46 or " "001dd478ec46 format, or search multiple with 00:1c:c4:" msgstr "" +"001dd478ec46 formatu, ali pa poišči vec mac adres z iskanjem npr. 00:1c:c4:" #: site/tools/searchTips.php:71 msgid "Custom field search tips" @@ -937,7 +883,7 @@ msgstr "" msgid "Instructions" msgstr "" -#: site/tools/toolsMenu.php:31 site/admin/ripeImport.php:19 +#: site/tools/toolsMenu.php:31 site/admin/RIPE / ARINImport.php:19 #: site/userMenu.php:22 site/sections.php:104 msgid "Search" msgstr "" @@ -1004,6 +950,7 @@ msgstr "" msgid "Search results (Subnet list)" msgstr "" + #: site/tools/searchResults.php:312 msgid "Edit subnet details" msgstr "" @@ -1138,8 +1085,9 @@ msgid "Orphaned IP addresses for subnet" msgstr "" #: site/ipaddr/ipAddressPrintTableOrphaned.php:69 -msgid "This happens if subnet had IP addresses<br>when new nested subnet was added" +msgid "This happens if subnet had IP addresses<br>when new nested subnet was added" msgstr "" +"Do tega pride, če se z izbrisom omrežja<br>niso pobrisali tudi IP naslovi" #: site/ipaddr/ipAddressPrintTableOrphaned.php:171 msgid "Move to different subnet" @@ -1285,11 +1233,11 @@ msgid "Move IP address" msgstr "" #: site/ipaddr/modifyIpAddress.php:31 -msgid "Cannot edit IP address details" +msgid "You do not have write access for this network'" msgstr "" #: site/ipaddr/modifyIpAddress.php:31 -msgid "You do not have write access for this network'" +msgid "Cannot edit IP address details" msgstr "" #: site/ipaddr/modifyIpAddress.php:79 site/ipaddr/modifyIpAddress.php:80 @@ -1308,6 +1256,7 @@ msgstr "" msgid "You can add,edit or delete multiple IP addresses<br>by specifying IP range " "(e.g. 10.10.0.0-10.10.0.25)" msgstr "" +"(npr.: 10.10.0.0-10.10.0.25)" #: site/ipaddr/modifyIpAddress.php:147 msgid "Click to check for hostname" @@ -1441,8 +1390,9 @@ msgstr "" msgid "If you like the software you can donate by clicking this button to support " "further development" msgstr "" +"tem podprete nadaljnji razvoj projekta" -#: site/dashboard/widgets/statistics.php:27 +#: site/dashboard/statistics.php:27 msgid "Number of Sections" msgstr "" @@ -1506,7 +1456,7 @@ msgstr "" msgid "No IPv4 host configured" msgstr "" -#: site/dashboard/top10_hosts.php:153 +#: site/dashboard/top10_percentage.php:189 msgid "No IPv6 host configured" msgstr "" @@ -1570,16 +1520,17 @@ msgstr "" msgid "Missing fields" msgstr "" -#: site/admin/ripeImport.php:12 -msgid "Import subnets from RIPE" +#: site/admin/RIPE / ARINImport.php:12 +msgid "Import subnets from RIPE / ARIN" msgstr "" -#: site/admin/ripeImport.php:15 -msgid "This script imports subnets from RIPE database for specific AS. Enter " +#: site/admin/RIPE / ARINImport.php:15 +msgid "This script imports subnets from RIPE / ARIN database for specific AS. Enter " "desired AS to search for subnets" msgstr "" +"Vnesite žljeni AS za iskanje pripadajočih omrežij" -#: site/admin/ripeImport.php:19 +#: site/admin/RIPE / ARINImport.php:19 msgid "AS number" msgstr "" @@ -1649,17 +1600,21 @@ msgstr "" msgid "Here you can set parameters for connecting to AD for authenticating users. " "phpIPAM uses" msgstr "" +"phpIPAM uporablja" #: site/admin/manageAD_AD.php:17 site/admin/manageAD_LDAP.php:17 msgid "to authenticate users. If you need additional settings please take a look at " "functions/adLDAP or check online documentation!" msgstr "" +"v functions/adLDAP, ali pa preverite dokumentacijo" #: site/admin/manageAD_AD.php:20 msgid "First create new user under user management with <u>same username as on AD</u>" " and set usertype to domain user. Also set proper permissions - group membership " "for new user." msgstr "" +" in za tip uporabnika izberite domenski uporabnik. Nastavite tudi pravilne pravice dostopa - " +"pripadnost skupinam" #: site/admin/manageAD_AD.php:29 site/admin/manageAD_LDAP.php:29 msgid "ldap extension not enabled in php" @@ -1673,6 +1628,7 @@ msgstr "" msgid "Enter domain controllers, separated by ; (default: dc1.domain.local;dc2." "domain.local)" msgstr "" +"domain.local)" #: site/admin/manageAD_AD.php:49 site/admin/manageAD_LDAP.php:48 msgid "Base DN" @@ -1684,6 +1640,9 @@ msgid "Enter base DN for LDAP (default: CN=Users,CN=Company,DC=domain,DC=local)" "\t\tIf this is set to null then adLDAP will attempt to obtain this " "automatically from the rootDSE" msgstr "" +"<br>\n" +"\t\tČe je to polje prazno bo adLDAP poizkušal avtomatsko pridobiti to informacijo " +"iz korenskega DSE" #: site/admin/manageAD_AD.php:61 msgid "Account suffix" @@ -1719,6 +1678,7 @@ msgstr "" msgid "If you wish to use TLS you should ensure that useSSL is set to false and " "vice-versa (default: false)" msgstr "" +"(privzeto: ne)" #: site/admin/manageAD_AD.php:102 msgid "AD port" @@ -1803,6 +1763,7 @@ msgid "You can select which fields are actually being used for IP management, so "you dont show any overhead if not used. IP, hostname and description are " "mandatory" msgstr "" +"manj pomembna ostanejo skrita. IP naslov, dns ime in opis so obvezna polja." #: site/admin/filterIPFields.php:44 msgid "Check which fields to use for IP addresses" @@ -1913,6 +1874,7 @@ msgstr "" msgid "Normal users will have permissions set based on group access to sections and " "subnets" msgstr "" +"in pravice skupin" #: site/admin/manageSubnet.php:11 site/admin/adminMenu.php:53 msgid "Subnet management" @@ -1983,7 +1945,7 @@ msgid "VRF management" msgstr "" #: site/admin/adminMenu.php:68 -msgid "RIPE import" +msgid "RIPE / ARIN import" msgstr "" #: site/admin/adminMenu.php:77 @@ -2046,6 +2008,7 @@ msgstr "" msgid "Domain authenticates on AD, but still needs to be setup here for permissions " "etc." msgstr "" +"pravic dostopa ipd." #: site/admin/usersEditPrint.php:116 msgid "User's password" @@ -2082,6 +2045,7 @@ msgstr "" #: site/admin/usersEditPrint.php:150 msgid "Users have access defined based on groups" msgstr "" +"skupin, katerim pripadajo" #: site/admin/usersEditPrint.php:177 msgid "No groups configured" @@ -2140,7 +2104,7 @@ msgid "subnet in CIDR" msgstr "" #: site/admin/manageSubnetEdit.php:86 -msgid "Get information from RIPE database" +msgid "Get information from RIPE / ARIN database" msgstr "" #: site/admin/manageSubnetEdit.php:87 @@ -2183,6 +2147,7 @@ msgstr "" msgid "Enter master subnet if you want to nest it under existing subnet, or select " "root to create root subnet" msgstr "" +"korensko omrežje za izdelavo novega" #: site/admin/manageSubnetEdit.php:188 msgid "Select VRF" @@ -2239,6 +2204,7 @@ msgstr "" msgid "Removing subnets will delete ALL underlaying subnets and belonging IP " "addresses" msgstr "" +"naslove" #: site/admin/manageSubnetEdit.php:324 msgid "Delete subnet" @@ -2264,7 +2230,7 @@ msgstr "" msgid "Status" msgstr "" -#: site/admin/CSVimportShowFile.php:100 site/admin/ripeImportTelnet.php:108 +#: site/admin/CSVimportShowFile.php:100 site/admin/RIPE / ARINImportTelnet.php:108 msgid "Import to database" msgstr "" @@ -2416,7 +2382,7 @@ msgstr "" msgid "Errors occured when importing to database!" msgstr "" -#: site/admin/CSVimportSubmit.php:95 site/admin/ripeImportResult.php:72 +#: site/admin/CSVimportSubmit.php:95 site/admin/RIPE / ARINImportResult.php:72 msgid "Import successfull" msgstr "" @@ -2631,6 +2597,7 @@ msgstr "" msgid "Set authentication type for users. Requires php LDAP support. Set connection " "settings in admin menu" msgstr "" +"za php. Nastavite nastavitve povezave v administracijskem meniju" #: site/admin/settings.php:105 msgid "Tooltips" @@ -2665,6 +2632,7 @@ msgid "Check reverse dns lookups for IP addresses that do not have hostname in " "database. (Activating this feature can significantly increase ip address " "pages loading time!)" msgstr "" +"podatkovni bazi. (Aktivacija te funkcije lahko povzroči precej daljše nalaganje strani!)" #: site/admin/settings.php:149 msgid "Duplicate VLANs" @@ -2698,6 +2666,7 @@ msgstr "" msgid "Select netmask limit for visual display of IP addresses (mask equal or " "bigger than - more then /22 not recommended)" msgstr "" +" - večja od /22 ni priporočena)" #: site/admin/settings.php:212 msgid "IP address print limit" @@ -2771,19 +2740,19 @@ msgstr "" msgid "Instructions updated successfully" msgstr "" -#: site/admin/ripeImportTelnet.php:48 +#: site/admin/RIPE / ARINImportTelnet.php:48 msgid "No subnets found" msgstr "" -#: site/admin/ripeImportTelnet.php:56 +#: site/admin/RIPE / ARINImportTelnet.php:56 msgid "I found the following routes belonging to AS" msgstr "" -#: site/admin/ripeImportTelnet.php:69 +#: site/admin/RIPE / ARINImportTelnet.php:69 msgid "Remove this subnet" msgstr "" -#: site/admin/ripeImportTelnet.php:79 +#: site/admin/RIPE / ARINImportTelnet.php:79 msgid "select section" msgstr "" @@ -2830,6 +2799,7 @@ msgstr "" #: site/admin/manageSection.php:92 msgid "If group is not set in permissions then it will not have access to subnet" msgstr "" +"dostopa do omrežja" #: site/admin/manageSection.php:93 msgid "Groups with RO permissions will not be able to create new subnets" @@ -2839,6 +2809,7 @@ msgstr "" msgid "Subnet permissions must be set separately. By default if group has access to " "section<br>it will have same permission on subnets" msgstr "" +"do razdelka<br>, potem bo imela enake pravice v pripadajočih omrežjih" #: site/admin/manageSection.php:95 msgid "You can choose to delegate section permissions to all underlying subnets" @@ -2848,6 +2819,7 @@ msgstr "" msgid "If group does not have access to section it will not be able to access " "subnet, even if<br>subnet permissions are set" msgstr "" +"tudi če so pravice za to omrežje določene" #: site/admin/customSubnetFieldsOrder.php:15 #: site/admin/customIPFieldsOrder.php:15 @@ -2939,6 +2911,7 @@ msgstr "" msgid "No disables overlapping subnet checks. Subnets can be nested/created " "randomly. Anarchy." msgstr "" +"poljubno. Anarhija." #: site/admin/manageSectionEdit.php:90 msgid "Permissions" @@ -2970,11 +2943,11 @@ msgstr "" msgid "Logs cleared successfully" msgstr "" -#: site/admin/ripeImportResult.php:66 +#: site/admin/RIPE / ARINImportResult.php:66 msgid "Failed to import subnet" msgstr "" -#: site/admin/ripeImportResult.php:76 +#: site/admin/RIPE / ARINImportResult.php:76 msgid "Please fix the following errors before inserting" msgstr "" @@ -2998,11 +2971,13 @@ msgstr "" msgid "Here you can set parameters for connecting to OpenLDAP for authenticating " "users. phpIPAM uses" msgstr "" +"uporabnikov. phpIPAM uporablja " #: site/admin/manageAD_LDAP.php:20 msgid "First create new user under user management with <u>same username as on " "LDAP</u> and set usertype to domain user. Also set proper groups (premissions) for this user." msgstr "" +"LDAPu</u> in izberite tip uporabnika domenski uporabnik. Nastavite tudi ustrezne skupine za uporabnika" #: site/admin/manageAD_LDAP.php:38 msgid "LDAP servers" @@ -3108,6 +3083,7 @@ msgstr "" msgid "To successfully import data please use the following XLS/CSV structure:<br>" "( ip | State | Description | hostname | MAC | Owner | Device | Port | Note " msgstr "" +"( ip | State | Opis | DNS ime | MAC | Lastnik | Naprava | Port | Note " #: site/admin/CSVimport.php:48 msgid "Upload file" @@ -3185,6 +3161,7 @@ msgstr "" msgid "If existing IP will fall to subnet/broadcast of new subnets split will fail, " "except if strict mode is disabled" msgstr "" +"ne bo uspela, razen če je strog način izklopljen" #: site/admin/manageSubnetresize.php:41 msgid "Current mask" @@ -3207,6 +3184,7 @@ msgstr "" msgid "If strict mode is enabled check will be made to ensure it is still inside " "master subnet" msgstr "" +"še vedno znotraj nadrejenega omrežja" #: site/admin/manageDevices.php:17 msgid "Add device" @@ -3266,6 +3244,7 @@ msgstr "" #: site/admin/usersEditEmailNotif.php:28 msgid "Sending notification mail for new account failed" msgstr "" +"računu ni uspelo" #: site/admin/usersEditEmailNotif.php:29 msgid "Notification mail for new account sent" @@ -3592,7 +3571,7 @@ msgstr "" msgid "Languages" msgstr "" -#: site/admin/languages.php +#: site/admin/languages msgid "Manage translations" msgstr "" @@ -3704,31 +3683,31 @@ msgstr "" msgid "IP delete successful" msgstr "" -#: site/admin/subnetDetailsSlaves.php +#: site/admin/subnetDetailsSlaves.php:134 msgid "You do not have permissions to truncate subnet" msgstr "" -#: site/admin/manageDevicesEditResult.php +#: site/admin/manageDevicesEditResult msgid "Failed to add device" msgstr "" -#: site/admin/manageDevicesEditResult.php +#: site/admin/manageDevicesEditResult msgid "Failed to edit device" msgstr "" -#: site/admin/manageDevicesEditResult.php +#: site/admin/manageDevicesEditResult msgid "Failed to delete device" msgstr "" -#: site/admin/manageDevicesEditResult.php +#: site/admin/manageDevicesEditResult msgid "Device add successfull" msgstr "" -#: site/admin/manageDevicesEditResult.php +#: site/admin/manageDevicesEditResult msgid "Device edit successfull" msgstr "" -#: site/admin/manageDevicesEditResult.php +#: site/admin/manageDevicesEditResult msgid "Device delete successfull" msgstr "" @@ -3858,7 +3837,7 @@ msgstr "" msgid "Ping hosts inside subnet to check availability" msgstr "" -#: site/ipaddr/subnetDetials.php +#: site/ipaddr/subnetDetails.php msgid "Hosts check" msgstr "" @@ -4010,6 +3989,11 @@ msgstr "" msgid "Invalid ping path" msgstr "" + + + + + #: site/admin/settings.php msgid "Display settings" msgstr "" @@ -4248,7 +4232,7 @@ msgstr "" msgid "Required field" msgstr "" -#: site/admin/custopmIPFields.php +#: site/admin/customIPFields.php msgid "Required" msgstr "" @@ -4341,7 +4325,10 @@ msgstr "" msgid "Delete folder" msgstr "" -#: site/admin/userADsearchForm.php +#: site/admin/CSVimportSubmit.php +msgid "Errors marked with red will be ignored from importing" +msgstr "" + msgid "Search user in AD" msgstr "" @@ -4391,6 +4378,9 @@ msgstr "" msgid "No changelog entries are available for this section" msgstr "" +msgid "No changelog entries are available" +msgstr "" + #: site/ipaddr/ipDetails.php msgid "Availability" msgstr "" @@ -4587,11 +4577,11 @@ msgstr "" msgid "Sender mail" msgstr "" -#: site/admin/settings.php +#: site/admin.settings.php msgid "Set administrator name" msgstr "" -msgid "Set administrator email" +msgid "Set administrator e-mail" msgstr "" #: site/admin/verifyDatabase.php @@ -4628,17 +4618,14 @@ msgstr "" msgid "Sort by vendor" msgstr "" -#: site/admin/userEditPrint.php -msgid "Mail State changes" -msgstr "" - -msgid "Select yes to receive notification change mail for State change" +msgid "Select yes to receive notification change mail for" msgstr "" -msgid "Mail Changelog" +msgid "IP edited, Subnet edited, State change" msgstr "" -msgid "Select yes to receive notification change mail for changelog" +#: site/admin/userEditPrint.php +msgid "Mail State changes" msgstr "" #: site/admin/usersEditPrint.php @@ -4720,17 +4707,6 @@ msgstr "" msgid "Discover new hosts in this subnet" msgstr "" - -## 1.18.00 - - - - - - -### 1.18.006 ### - - msgid "Invalid ID" msgstr "" @@ -4941,9 +4917,6 @@ msgstr "" msgid "No subnets" msgstr "" -msgid "No changelog entries are available" -msgstr "" - msgid "Device details" msgstr "" @@ -4974,9 +4947,6 @@ msgstr "" msgid "Invalid widget" msgstr "" -msgid "No $type hosts configured" -msgstr "" - msgid "Loading statistics" msgstr "" @@ -5175,9 +5145,6 @@ msgstr "" msgid "Master NS" msgstr "" -msgid "NULL" -msgstr "" - msgid "Domain type" msgstr "" @@ -5610,7 +5577,7 @@ msgstr "" msgid "For AD/LDAP connection phpipam is using adLDAP, for documentation please check " msgstr "" -msgid "First create new user under user management with <u>same username as on AD</u> and set authention type to one of available methods." +msgid "First create new user under user management with <u>same username as on AD</u> and set authention typeto one of available methods." msgstr "" msgid "Failed to edit authentication method" @@ -5910,9 +5877,6 @@ msgstr "" msgid "Invalid file type" msgstr "" -msgid "Errors marked with red will be ignored from importing" -msgstr "" - msgid "Invalid subnet ID" msgstr "" @@ -6009,9 +5973,6 @@ msgstr "" msgid "Invalid Network!" msgstr "" -msgid "Subnet $new_subnet overlaps with" -msgstr "" - msgid "Mask must be an integer" msgstr "" @@ -6057,7 +6018,8 @@ msgstr "" msgid "No subnets belong to this device" msgstr "" -# 1.19 +### 1.19.000 ### + msgid "Log files are sent to syslog" msgstr "" @@ -6115,7 +6077,7 @@ msgstr "" msgid "Enter scan agent name" msgstr "" -msgid "Agent description" +msgid "Agent description'" msgstr "" msgid "Agent type" @@ -6154,230 +6116,6 @@ msgstr "" msgid "Scan agent references removed" msgstr "" -### 1.19.002 ### - -msgid "Enable Firewall Zones" -msgstr "" - -msgid "Enable or disable firewall zone management module" -msgstr "" - -msgid "Firewall zone management" -msgstr "" - -msgid "Invalid zone alias value." -msgstr "" - -msgid "Invalid interface." -msgstr "" - -msgid "Invalid zone ID." -msgstr "" - -msgid "Invalid mapping ID." -msgstr "" - -msgid "Cannot add mapping" -msgstr "" - -msgid "Mapping modified successfully" -msgstr "" - -msgid "Invalid ID. Do not manipulate the POST values!" -msgstr "" - -msgid "Invalid action. Do not manipulate the POST values!" -msgstr "" - -msgid "Add a mapping between a firewall device and a firewall zone" -msgstr "" - -msgid "Zone to map" -msgstr "" - -msgid "Select a firewall zone" -msgstr "" - -msgid "Firewall to map" -msgstr "" - -msgid "Select firewall" -msgstr "" - -msgid "Interface" -msgstr "" - -msgid "Create Firewall zone mapping" -msgstr "" - -msgid "Firewall interface" -msgstr "" - -msgid "Zone alias" -msgstr "" - -msgid "Local zone alias" -msgstr "" - -msgid "You are about to remove the firewall to zone mapping!" -msgstr "" - -msgid "Zone" -msgstr "" - -msgid "Alias" -msgstr "" - -msgid "Devicename" -msgstr "" - -msgid "No firewall zones configured" -msgstr "" - -msgid "Invalid zone name length parameter. A valid valid value is between 3 and 31" -msgstr "" - -msgid "Invalid IPv4 address type alias. Only alphanumeric characters, "-", "_" and "." are allowed." -msgstr "" - -msgid "Invalid separator. Only "-", "_" and "." are allowed." -msgstr "" - -msgid "Invalid zone indicator. Only alphanumeric characters, "-", "_" and "." are allowed." -msgstr "" - -msgid "Invalid zone generator method. Do not manipulate the POST values!" -msgstr "" - -msgid "Invalid zone generator types [decimal]. Do not manipulate the POST values!" -msgstr "" - -msgid "Invalid zone generator types [hex]. Do not manipulate the POST values!" -msgstr "" - -msgid "Invalid zone generator types [text]. Do not manipulate the POST values!" -msgstr "" - -msgid "Invalid padding value. Use the checkbox to set the padding value to on or off." -msgstr "" - -msgid "Invalid device type." -msgstr "" - -msgid "Maximum zone name length" -msgstr "" - -msgid "Choose a maximum length of the zone name.<br>The default length is 3, the maximum is 31 characters.<br>(keep in mind that your firewall may have a limit for the length of zone names or address objects )" -msgstr "" - -msgid "IPv4 address type alias" -msgstr "" - -msgid "IPv6 address type alias" -msgstr "" - -msgid "Address type aliases are used to indicate a IPv4 or IPv6 address object." -msgstr "" - -msgid "The separator is used to keep the name of address objects tidy." -msgstr "" - -msgid "Own zone indicator" -msgstr "" - -msgid "The indicator is used to indicate a zone wether is owned by the company or by a customer.<br>It is the leading character of the zone name but will be separated from the zone name in the database." -msgstr "" - -msgid "Customer zone indicator" -msgstr "" - -msgid "Zone generator method" -msgstr "" - -msgid "Generate zone names automaticaly with the setting "decimal" or "hex".<br>To use your own unique zone names you can choose the option "text"." -msgstr "" - -msgid "Zone name padding" -msgstr "" - -msgid "Insert leading zeros into the zone name if you want to have a constant length of your zone name.<br>This setting will be ignored if you use the \"text\" zone name generator." -msgstr "" - -msgid "Zone name strict mode" -msgstr "" - -msgid "Zone name strict mode is enabled by default.<br>If you like to use your own zone names with the "text" mode you may uncheck this to have not unique zone names." -msgstr "" - -msgid "Firewall device Type" -msgstr "" - -msgid "Select the appropriate device type to match firewall devices." -msgstr "" - -msgid "Invalid zone name value." -msgstr "" - -msgid "Invalid indicator ID." -msgstr "" - -msgid "Invalid section ID." -msgstr "" - -msgid "Invalid subnet ID." -msgstr "" - -msgid "Invalid L2 domain ID." -msgstr "" - -msgid "Invalid VLAN ID." -msgstr "" - -msgid "Invalid generator ID." -msgstr "" - -msgid "Invalid padding setting." -msgstr "" - -msgid "Cannot generate zone name" -msgstr "" - -msgid "Cannot validate zone name" -msgstr "" - -msgid "Cannot add zone" -msgstr "" - -msgid "Zone modified successfully" -msgstr "" - -msgid "Add a firewall zone" -msgstr "" - -msgid "Zone name (Only alphanumeric and special characters like .-_ and space.)" -msgstr "" - -msgid "The zone name will be automatically generated" -msgstr "" - -msgid "Zone name" -msgstr "" - -msgid "Own zone" -msgstr "" - -msgid "Customer zone" -msgstr "" - -msgid "Removing this firewall zone will also remove all referenced mappings!" -msgstr "" - -msgid "Create Firewall zone" -msgstr "" - -msgid "Firewall zone and device mappings" -msgstr "" - msgid "Base DN for your directory" msgstr "" @@ -6732,6 +6470,12 @@ msgstr "" msgid "NAT settings" msgstr "" +msgid "RIPE import" +msgstr "" + +msgid "Import subnets from RIPE" +msgstr "" + msgid "Select which default address fields to display" msgstr "" @@ -7170,6 +6914,9 @@ msgstr "" msgid "Automatic" msgstr "" +msgid "This script imports subnets from RIPE database for specific AS. Enter desired AS to search for subnets" +msgstr "" + msgid "Invalid AS" msgstr "" @@ -8303,3 +8050,502 @@ msgstr "" msgid "phpIPAM settings" msgstr "" + + +### Customers module ### + +msgid "All customers" +msgstr "" + +msgid "Add customer" +msgstr "" + +msgid "Customer" +msgstr "" + +msgid "Customers" +msgstr "" + +msgid "Select customer" +msgstr "" + +msgid "Edit customer" +msgstr "" + +msgid "Delete customer" +msgstr "" + +msgid "Contact" +msgstr "" + +msgid "Customer title" +msgstr "" + +msgid "Customer address" +msgstr "" + +msgid "Contact details" +msgstr "" + +msgid "Customer contact details" +msgstr "" + +msgid "Postcode" +msgstr "" + +msgid "City" +msgstr "" + +msgid "Contact person" +msgstr "" + +msgid "Phone" +msgstr "" + +msgid "Random notes" +msgstr "" + +msgid "Customers module" +msgstr "" + +msgid "Enable or disable customers module for customer management" +msgstr "" + + + +### 2FA ### + +msgid "2FA authentication" +msgstr "" + +msgid "2FA provider" +msgstr "" + +msgid "Current 2FA provider" +msgstr "" + +msgid "2FA users" +msgstr "" + +msgid "2FA status" +msgstr "" + +msgid "2FA status legend" +msgstr "" + +msgid "2FA is enabled" +msgstr "" + +msgid "2FA disabled" +msgstr "" + +msgid "2fa is enabled, but secret is not set. User will be given new secret upon first login" +msgstr "" + +msgid "Reset secret" +msgstr "" + +msgid "2FA name" +msgstr "" + +msgid "Name for 2fa application that will be displayed" +msgstr "" + +msgid "2FA length" +msgstr "" + +msgid "Length of 2FA secret (16 to 32)" +msgstr "" + +msgid "2FA user change" +msgstr "" + +msgid "Enabled, not activated" +msgstr "" + +msgid "Can users change 2fa settings for their account" +msgstr "" + +msgid "Apply to all users" +msgstr "" + +msgid "Force all users to use 2fa on next login or disable 2fa for all users" +msgstr "" + + + + +### Password policy ### + +msgid "phpIPAM password policy settings" +msgstr "" + +msgid "Here you can set password policy for user authentication" +msgstr "" + +msgid "Minimum length" +msgstr "" + +msgid "Minimum password length" +msgstr "" + +msgid "Maximum length" +msgstr "" + +msgid "Maximum password length" +msgstr "" + +msgid "Minimum numbers" +msgstr "" + +msgid "Minumum number of numbers" +msgstr "" + +msgid "Minimum letters" +msgstr "" + +msgid "Minumum number of letters" +msgstr "" + +msgid "Minimum lowercase letter" +msgstr "" + +msgid "Minumum number of lowercase letters" +msgstr "" + +msgid "Minimum uppercase letter" +msgstr "" + +msgid "Minumum number of uppercase letters" +msgstr "" + +msgid "Minimum symbols" +msgstr "" + +msgid "Minumum number of symbols" +msgstr "" + +msgid "Maximum symbols" +msgstr "" + +msgid "Maximum number of symbols" +msgstr "" + +msgid "Symbols" +msgstr "" + +msgid "List of allowed symbols. csv separated." +msgstr "" + +msgid "Enforce" +msgstr "" + +msgid "Require all users to change password upon next login." +msgstr "" + + + + + +### Circuits #### + +msgid "Circuit options" +msgstr "" + +msgid "Physical Circuits" +msgstr "" + +msgid "Logical Circuits" +msgstr "" + +msgid "Circuit providers" +msgstr "" + +msgid "Circuit map" +msgstr "" + +msgid "Options" +msgstr "" + +msgid "Point A" +msgstr "" + +msgid "Point B" +msgstr "" + +msgid "Provider" +msgstr "" + +msgid "Circuit type" +msgstr "" + +msgid "Capacity" +msgstr "" + +msgid "Purpose" +msgstr "" + +msgid "Circuit count" +msgstr "" + +msgid "Members" +msgstr "" + +msgid "Logical circuit physical members" +msgstr "" + +msgid "Available physical circuits" +msgstr "" + + + +### Misc 1.4 ### + +msgid "Theme" +msgstr "" + +msgid "Select UI theme" +msgstr "" + +msgid "dark" +msgstr "" + +msgid "white" +msgstr "" + +msgid "Compress text in top menu" +msgstr "" + +msgid "Bandwidth calculator" +msgstr "" + +msgid "TCP Window size" +msgstr "" + +msgid "Delay" +msgstr "" + +msgid "Filesize" +msgstr "" + +msgid "Transfer time" +msgstr "" + +msgid "IP Request" +msgstr "" + +msgid "Active IP address requests." +msgstr "" + +msgid "Enable Firewall Zones" +msgstr "" + +msgid "Enable or disable firewall zone management module" +msgstr "" + +msgid "Allow duplicate vlans" +msgstr "" + +msgid "Allow duplicated vlans inside L2 domain" +msgstr "" + +msgid "Decode MAC vendor" +msgstr "" + +msgid "Decode MAC address vendor for addresses" +msgstr "" + +msgid "Module permissions" +msgstr "" + +msgid "Scan agents" +msgstr "" + + +### 1.6.1 + +msgid "passkey" +msgstr "" + +msgid "Passkey" +msgstr "" + +msgid "2fa account status" +msgstr "" + +msgid "2fa disabled" +msgstr "" + +msgid "2fa enabled" +msgstr "" + +msgid "2fa status" +msgstr "" + +msgid "Account" +msgstr "" + +msgid "Check passkey you want to remove" +msgstr "" + +msgid "Details for your preferred authenticator application are below. Please write down your details, otherwise you will not be able to login to phpipam" +msgstr "" + +msgid "Disable 2fa for user" +msgstr "" + +msgid "Enable Passkeys" +msgstr "" + +msgid "Enable Vaults" +msgstr "" + +msgid "Enable Vaults for storing encrypted information" +msgstr "" + +msgid "Enable passkeys for passwordless login" +msgstr "" + +msgid "failed" +msgstr "" + +msgid "Failed passkey login" +msgstr "" + +msgid "has logged out" +msgstr "" + +msgid "Here you can change settings for two-factor authentication and get your 2fa secret." +msgstr "" + +msgid "Invalid theme" +msgstr "" + +msgid "logged in" +msgstr "" + +msgid "Login with a passkey" +msgstr "" + +msgid "Only passkey authentication is possible for this account" +msgstr "" + +msgid "Passkey login only" +msgstr "" + +msgid "Passkey only" +msgstr "" + +msgid "Passkey required for login" +msgstr "" + +msgid "Passkeys" +msgstr "" + +msgid "Select to only allow account login with passkey" +msgstr "" + +msgid "successful" +msgstr "" + +msgid "There are no passkeys set for user. Resetting passkey login only to false." +msgstr "" + +msgid "Two-factor authentication" +msgstr "" + +msgid "User account is disabled" +msgstr "" + +msgid "User permissions for phpipam modules" +msgstr "" + +msgid "You can also scan following QR code with your preferred authenticator application" +msgstr "" + +msgid "You can login to your account with normal authentication method only untill you create passkeys." +msgstr "" + +msgid "You can only login to your account using passkeys" +msgstr "" + +msgid "Passwordless authentication" +msgstr "" + +msgid "Account details" +msgstr "" + +msgid "You can login to your account with with passkeys only" +msgstr "" + +msgid "This can be changed under Account details tab" +msgstr "" + +msgid "Your passkeys" +msgstr "" + +msgid "Added on" +msgstr "" + +msgid "Last used" +msgstr "" + +msgid "You authenticated with this passkey" +msgstr "" + +msgid "Add a passkey" +msgstr "" + +msgid "Rename" +msgstr "" + +msgid "Here you can manage passkey authentication for your account" +msgstr "" + +msgid "Passkeys are a password replacement that validates your identity using touch, facial recognition, a device password, or a PIN" +msgstr "" + +msgid "Name your passkey" +msgstr "" + +msgid "Duplicates" +msgstr "" + +msgid "Duplicated subnets" +msgstr "" + +msgid "Duplicated hosts" +msgstr "" + +msgid "No duplicate subnets found" +msgstr "" + +msgid "Routing" +msgstr "" + +msgid "BGP routing" +msgstr "" + +msgid "Add peer" +msgstr "" + +msgid "Peer name" +msgstr "" + +msgid "Peer AS" +msgstr "" + +msgid "Local AS" +msgstr "" + +msgid "Peer address" +msgstr "" + +msgid "Local address" +msgstr "" + +msgid "Local Address" +msgstr "" + +msgid "BGP type" +msgstr "" diff --git a/functions/locale/sl_SI.UTF-8/LC_MESSAGES/phpipam.mo b/functions/locale/sl_SI.UTF-8/LC_MESSAGES/phpipam.mo index ab67e6fac36132a88d8058024c98bdc57a93d8d8..596e7f2a78d51cec9046a242b30d68a721557d5b 100644 Binary files a/functions/locale/sl_SI.UTF-8/LC_MESSAGES/phpipam.mo and b/functions/locale/sl_SI.UTF-8/LC_MESSAGES/phpipam.mo differ diff --git a/functions/locale/sl_SI.UTF-8/LC_MESSAGES/phpipam.po b/functions/locale/sl_SI.UTF-8/LC_MESSAGES/phpipam.po index 9e4b1e59dfe12462e6317cafb1de14e022dc23b1..9c751215844acbffb3974590ec1dada8c939397e 100644 --- a/functions/locale/sl_SI.UTF-8/LC_MESSAGES/phpipam.po +++ b/functions/locale/sl_SI.UTF-8/LC_MESSAGES/phpipam.po @@ -8361,3 +8361,191 @@ msgstr "Pravice modulov" msgid "Scan agents" msgstr "Agenti skeniranja" + +### 1.6.1 + +msgid "passkey" +msgstr "prijavni ključ" + +msgid "Passkey" +msgstr "prijavni ključ" + +msgid "2fa account status" +msgstr "Status 2fa" + +msgid "2fa disabled" +msgstr "2fa izklopljena" + +msgid "2fa enabled" +msgstr "2fa vklopljena" + +msgid "2fa status" +msgstr "Status 2fa" + +msgid "Account" +msgstr "Račun" + +msgid "Check passkey you want to remove" +msgstr "Izperite prijavni ključ ki ga želite odstraniti" + +msgid "Details for your preferred authenticator application are below. Please write down your details, otherwise you will not be able to login to phpipam" +msgstr "" + +msgid "Disable 2fa for user" +msgstr "Onemogoči 2fa za uporabnika" + +msgid "Enable Passkeys" +msgstr "Omogočite prijavne ključe" + +msgid "Enable Vaults" +msgstr "Omogoċite trezorje" + +msgid "Enable Vaults for storing encrypted information" +msgstr "Omogočite trezorje za varno hrambo informacij" + +msgid "Enable passkeys for passwordless login" +msgstr "Omogočite prijavo brez gesel z uporabo passkey prijavnimi ključi" + +msgid "failed" +msgstr "nauspešna" + +msgid "Failed passkey login" +msgstr "Napaka pri prijavi s prijavnim ključem" + +msgid "has logged out" +msgstr "se je odjavil" + +msgid "Here you can change settings for two-factor authentication and get your 2fa secret." +msgstr "Tukaj lahko spremenite nastavitve za dvofaktorsko avtentikacijo in pridobite vaš 2fa skrivni ključ." + +msgid "Invalid theme" +msgstr "Neveljavna tema" + +msgid "logged in" +msgstr "prijavljen" + +msgid "Login with a passkey" +msgstr "Prijava s prijavnim ključem" + +msgid "Only passkey authentication is possible for this account" +msgstr "Prijava v ta račun je mogoča samo s prijavnimi ključi passkey" + +msgid "Passkey login only" +msgstr "Prijava samo s prijavnimi ključi" + +msgid "Passkey only" +msgstr "Samo prijavni ključi" + +msgid "Passkey required for login" +msgstr "Prijavni ključ je za zahtevan za prijavo" + +msgid "Passkeys" +msgstr "Prijavni ključi" + +msgid "Select to only allow account login with passkey" +msgstr "Izberite to opcijo, da dovolite prijavo v račun samo s prijavnimi ključi" + +msgid "successful" +msgstr "uspešna" + +msgid "There are no passkeys set for user. Resetting passkey login only to false." +msgstr "Uporabnik nima nastavljenih prijavnih ključev." + +msgid "Two-factor authentication" +msgstr "Dvofaktorska avtentikacija" + +msgid "User account is disabled" +msgstr "Uporabniški račun je onemogočen" + +msgid "User permissions for phpipam modules" +msgstr "Uporabniške pravice za phpipam module" + +msgid "You can also scan following QR code with your preferred authenticator application" +msgstr "Spodnjo QR kodo lahko skenirate s svojo applikacijo za upravljanje avtentikacij" + +msgid "You can login to your account with normal authentication method only untill you create passkeys." +msgstr "V svoj račun se lahko prijavite z običajno metodo, dokler ne ustvarite prijavnih ključev." + +msgid "You can only login to your account using passkeys" +msgstr "V račun se lahko prijavite samo s prijavnimi ključi" + +msgid "Passwordless authentication" +msgstr "Prijava brez gesel" + +msgid "Account details" +msgstr "Podatki o računu" + +msgid "You can login to your account with with passkeys only" +msgstr "V račun se lahko prijavite samo s prijavnimi ključi" + +msgid "This can be changed under Account details tab" +msgstr "To lahko spremenite v zavihku Podatki o računu" + +msgid "Your passkeys" +msgstr "Vaši prijavni ključi" + +msgid "Added on" +msgstr "Dodan" + +msgid "Last used" +msgstr "Zadnjič uporabljen" + +msgid "You authenticated with this passkey" +msgstr "Prijavili ste se s tem ključem" + +msgid "Add a passkey" +msgstr "Dodaj prijavni ključ" + +msgid "Rename" +msgstr "Preimenuj" + +msgid "Here you can manage passkey authentication for your account" +msgstr "Tukaj lahko urejate avtentikacijo s prijavnimi ključi za vaš račun" + +msgid "Passkeys are a password replacement that validates your identity using touch, facial recognition, a device password, or a PIN" +msgstr "Prijavni ključi so zamenjava za gesla, ki preverijo vašo identiteto s pomočjo dotika, prepoznave obraza, PIN naprave ali z geslom naprave" + +msgid "Name your passkey" +msgstr "Poimenujte prijavni ključ" + +msgid "Duplicates" +msgstr "Duplikati" + +msgid "Duplicated subnets" +msgstr "Podvojena omreżja" + +msgid "Duplicated hosts" +msgstr "Podvojeni IP naslovi" + +msgid "No duplicate subnets found" +msgstr "Ni podvojenih omrežij" + +msgid "Routing" +msgstr "Usmerjevalni protokoli" + +msgid "BGP routing" +msgstr "BGP usmerjanje" + +msgid "Add peer" +msgstr "Dodaj povezavo" + +msgid "Peer name" +msgstr "Ime partnerja" + +msgid "Peer AS" +msgstr "AS partnerja" + +msgid "Local AS" +msgstr "Lokalni AS" + +msgid "Peer address" +msgstr "Naslov partnerja" + +msgid "Local address" +msgstr "Lokalni naslov" + +msgid "Local Address" +msgstr "Lokalni naslov" + +msgid "BGP type" +msgstr "tip BGP povezave" diff --git a/functions/scripts/sync_ad_groups.php b/functions/scripts/sync_ad_groups.php new file mode 100755 index 0000000000000000000000000000000000000000..8e04bcaf8591ea5e1b0171e88f8ca2287bf62423 --- /dev/null +++ b/functions/scripts/sync_ad_groups.php @@ -0,0 +1,21 @@ +<?php + + +/** + * + * This script will sync phpipam groups with AD groups + * + * + * + * + */ + +// functions +require_once( dirname(__FILE__) . '/../functions.php' ); + +// AD sync +$Database = new Database_PDO; + +$AD_sync = new AD_user_sync ($Database); +$AD_sync->set_debug (true); + diff --git a/functions/upgrade_queries/upgrade_queries_1.6.php b/functions/upgrade_queries/upgrade_queries_1.6.php index 84cd23587429b99b8263b362b6f02c60cdc27c01..289feed5a8687a0508b6d2fc4b4189975531d87a 100644 --- a/functions/upgrade_queries/upgrade_queries_1.6.php +++ b/functions/upgrade_queries/upgrade_queries_1.6.php @@ -5,4 +5,27 @@ # $upgrade_queries["1.6.39"] = []; $upgrade_queries["1.6.39"][] = "-- Version update"; -$upgrade_queries["1.6.39"][] = "UPDATE `settings` set `version` = '1.6';"; \ No newline at end of file +$upgrade_queries["1.6.39"][] = "UPDATE `settings` set `version` = '1.6';"; + +// passkeys +$upgrade_queries["1.6.40"] = []; +$upgrade_queries["1.6.40"][] = "-- Database version bump"; +$upgrade_queries["1.6.40"][] = "UPDATE `settings` set `dbversion` = '40';"; +// add passkey support to settings +$upgrade_queries["1.6.40"][] = "ALTER TABLE `settings` ADD `passkeys` TINYINT(1) NULL DEFAULT '0' AFTER `2fa_userchange`;"; +// allow passkey login only +$upgrade_queries["1.6.40"][] = "ALTER TABLE `users` ADD `passkey_only` TINYINT(1) NOT NULL DEFAULT '0' AFTER `authMethod`;"; +// passkey table +$upgrade_queries["1.6.40"][] = "CREATE TABLE `passkeys` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `credentialId` text NOT NULL, + `keyId` text NOT NULL, + `credential` text NOT NULL, + `comment` text, + `created` timestamp NULL DEFAULT NULL, + `used` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `user_id` (`user_id`), + CONSTRAINT `user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8;"; \ No newline at end of file diff --git a/functions/vendor/autoload.php b/functions/vendor/autoload.php new file mode 100644 index 0000000000000000000000000000000000000000..29e50e81ec03c5c718a61ca9c4bf9d9283fe935b --- /dev/null +++ b/functions/vendor/autoload.php @@ -0,0 +1,25 @@ +<?php + +// autoload.php @generated by Composer + +if (PHP_VERSION_ID < 50600) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, $err); + } elseif (!headers_sent()) { + echo $err; + } + } + trigger_error( + $err, + E_USER_ERROR + ); +} + +require_once __DIR__ . '/composer/autoload_real.php'; + +return ComposerAutoloaderInit7bf730768732814406175c12775f3390::getLoader(); diff --git a/functions/vendor/composer/ClassLoader.php b/functions/vendor/composer/ClassLoader.php new file mode 100644 index 0000000000000000000000000000000000000000..7824d8f7eafe8db890975f0fa2dfab31435900da --- /dev/null +++ b/functions/vendor/composer/ClassLoader.php @@ -0,0 +1,579 @@ +<?php + +/* + * This file is part of Composer. + * + * (c) Nils Adermann <naderman@naderman.de> + * Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Jordi Boggiano <j.boggiano@seld.be> + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array<string, array<string, int>> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array<string, list<string>> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list<string> + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array<string, array<string, list<string>>> + */ + private $prefixesPsr0 = array(); + /** + * @var list<string> + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array<string, string> + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array<string, bool> + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array<string, self> + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array<string, list<string>> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array<string, list<string>> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list<string> + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list<string> + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array<string, string> Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array<string, string> $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list<string>|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list<string>|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list<string>|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list<string>|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array<string, self> + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/functions/vendor/composer/InstalledVersions.php b/functions/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000000000000000000000000000000000000..51e734a774b3ed9ca110a921cb40a74f8c7905c2 --- /dev/null +++ b/functions/vendor/composer/InstalledVersions.php @@ -0,0 +1,359 @@ +<?php + +/* + * This file is part of Composer. + * + * (c) Nils Adermann <naderman@naderman.de> + * Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list<string> + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list<string> + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */ + $required = require $vendorDir.'/composer/installed.php'; + $installed[] = self::$installedByVendor[$vendorDir] = $required; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/functions/vendor/composer/LICENSE b/functions/vendor/composer/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..f27399a042d95c4708af3a8c74d35d338763cf8f --- /dev/null +++ b/functions/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/functions/vendor/composer/autoload_classmap.php b/functions/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000000000000000000000000000000000000..0fb0a2c194b8590999a5ed79e357d4a9c1e9d8b8 --- /dev/null +++ b/functions/vendor/composer/autoload_classmap.php @@ -0,0 +1,10 @@ +<?php + +// autoload_classmap.php @generated by Composer + +$vendorDir = dirname(__DIR__); +$baseDir = dirname($vendorDir); + +return array( + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', +); diff --git a/functions/vendor/composer/autoload_namespaces.php b/functions/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000000000000000000000000000000000000..15a2ff3ad6d8d6ea2b6b1f9552c62d745ffc9bf4 --- /dev/null +++ b/functions/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ +<?php + +// autoload_namespaces.php @generated by Composer + +$vendorDir = dirname(__DIR__); +$baseDir = dirname($vendorDir); + +return array( +); diff --git a/functions/vendor/composer/autoload_psr4.php b/functions/vendor/composer/autoload_psr4.php new file mode 100644 index 0000000000000000000000000000000000000000..edcada0d62ca96e24fe9c2d889d7118e49bd61f3 --- /dev/null +++ b/functions/vendor/composer/autoload_psr4.php @@ -0,0 +1,11 @@ +<?php + +// autoload_psr4.php @generated by Composer + +$vendorDir = dirname(__DIR__); +$baseDir = dirname($vendorDir); + +return array( + 'Firehed\\WebAuthn\\' => array($vendorDir . '/firehed/webauthn/src'), + 'Firehed\\CBOR\\' => array($vendorDir . '/firehed/cbor/src'), +); diff --git a/functions/vendor/composer/autoload_real.php b/functions/vendor/composer/autoload_real.php new file mode 100644 index 0000000000000000000000000000000000000000..53cd7ae35679adfc2a5ffb871c3c88f3776d270b --- /dev/null +++ b/functions/vendor/composer/autoload_real.php @@ -0,0 +1,38 @@ +<?php + +// autoload_real.php @generated by Composer + +class ComposerAutoloaderInit7bf730768732814406175c12775f3390 +{ + private static $loader; + + public static function loadClassLoader($class) + { + if ('Composer\Autoload\ClassLoader' === $class) { + require __DIR__ . '/ClassLoader.php'; + } + } + + /** + * @return \Composer\Autoload\ClassLoader + */ + public static function getLoader() + { + if (null !== self::$loader) { + return self::$loader; + } + + require __DIR__ . '/platform_check.php'; + + spl_autoload_register(array('ComposerAutoloaderInit7bf730768732814406175c12775f3390', 'loadClassLoader'), true, true); + self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); + spl_autoload_unregister(array('ComposerAutoloaderInit7bf730768732814406175c12775f3390', 'loadClassLoader')); + + require __DIR__ . '/autoload_static.php'; + call_user_func(\Composer\Autoload\ComposerStaticInit7bf730768732814406175c12775f3390::getInitializer($loader)); + + $loader->register(true); + + return $loader; + } +} diff --git a/functions/vendor/composer/autoload_static.php b/functions/vendor/composer/autoload_static.php new file mode 100644 index 0000000000000000000000000000000000000000..2d75bd05a3014d03e244899d4bc67a651b39d35d --- /dev/null +++ b/functions/vendor/composer/autoload_static.php @@ -0,0 +1,41 @@ +<?php + +// autoload_static.php @generated by Composer + +namespace Composer\Autoload; + +class ComposerStaticInit7bf730768732814406175c12775f3390 +{ + public static $prefixLengthsPsr4 = array ( + 'F' => + array ( + 'Firehed\\WebAuthn\\' => 17, + 'Firehed\\CBOR\\' => 13, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Firehed\\WebAuthn\\' => + array ( + 0 => __DIR__ . '/..' . '/firehed/webauthn/src', + ), + 'Firehed\\CBOR\\' => + array ( + 0 => __DIR__ . '/..' . '/firehed/cbor/src', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit7bf730768732814406175c12775f3390::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit7bf730768732814406175c12775f3390::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit7bf730768732814406175c12775f3390::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/functions/vendor/composer/installed.json b/functions/vendor/composer/installed.json new file mode 100644 index 0000000000000000000000000000000000000000..4c86530bf0887706356c4c32d94ee82ceaeed756 --- /dev/null +++ b/functions/vendor/composer/installed.json @@ -0,0 +1,113 @@ +{ + "packages": [ + { + "name": "firehed/cbor", + "version": "0.1.0", + "version_normalized": "0.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/Firehed/cbor-php.git", + "reference": "eef67b1b5fdf90a3688fc8d9d13afdaf342c4b80" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Firehed/cbor-php/zipball/eef67b1b5fdf90a3688fc8d9d13afdaf342c4b80", + "reference": "eef67b1b5fdf90a3688fc8d9d13afdaf342c4b80", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "^8.1" + }, + "suggest": { + "ext-bcmath": "Enables parsing of very large values" + }, + "time": "2019-05-14T06:31:13+00:00", + "type": "library", + "installation-source": "source", + "autoload": { + "psr-4": { + "Firehed\\CBOR\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eric Stern", + "email": "eric@ericstern.com" + } + ], + "description": "CBOR decoder", + "homepage": "https://github.com/Firehed/CBOR", + "keywords": [ + "cbor" + ], + "support": { + "issues": "https://github.com/Firehed/cbor-php/issues", + "source": "https://github.com/Firehed/cbor-php/tree/master" + }, + "install-path": "../firehed/cbor" + }, + { + "name": "firehed/webauthn", + "version": "dev-main", + "version_normalized": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/Firehed/webauthn-php.git", + "reference": "267d04a6d2926d9ab6d7630fb86a92410eb6b36c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Firehed/webauthn-php/zipball/267d04a6d2926d9ab6d7630fb86a92410eb6b36c", + "reference": "267d04a6d2926d9ab6d7630fb86a92410eb6b36c", + "shasum": "" + }, + "require": { + "ext-hash": "*", + "ext-openssl": "*", + "firehed/cbor": "^0.1.0", + "php": "^8.1" + }, + "require-dev": { + "maglnet/composer-require-checker": "^4.1", + "mheap/phpunit-github-actions-printer": "^1.5", + "nikic/php-parser": "^4.14", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.3", + "squizlabs/php_codesniffer": "^3.5" + }, + "time": "2023-11-16T23:07:44+00:00", + "default-branch": true, + "type": "library", + "installation-source": "source", + "autoload": { + "psr-4": { + "Firehed\\WebAuthn\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eric Stern", + "email": "eric@ericstern.com" + } + ], + "description": "Web Authentication", + "support": { + "issues": "https://github.com/Firehed/webauthn-php/issues", + "source": "https://github.com/Firehed/webauthn-php/tree/main" + }, + "install-path": "../firehed/webauthn" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/functions/vendor/composer/installed.php b/functions/vendor/composer/installed.php new file mode 100644 index 0000000000000000000000000000000000000000..384a408b191984ae34c0c83b78c0cc5cd0f525f7 --- /dev/null +++ b/functions/vendor/composer/installed.php @@ -0,0 +1,43 @@ +<?php return array( + 'root' => array( + 'name' => '__root__', + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => 'e75387138147905ffd3b12cafffdc10f5388d948', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => 'e75387138147905ffd3b12cafffdc10f5388d948', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'firehed/cbor' => array( + 'pretty_version' => '0.1.0', + 'version' => '0.1.0.0', + 'reference' => 'eef67b1b5fdf90a3688fc8d9d13afdaf342c4b80', + 'type' => 'library', + 'install_path' => __DIR__ . '/../firehed/cbor', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'firehed/webauthn' => array( + 'pretty_version' => 'dev-main', + 'version' => 'dev-main', + 'reference' => '267d04a6d2926d9ab6d7630fb86a92410eb6b36c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../firehed/webauthn', + 'aliases' => array( + 0 => '9999999-dev', + ), + 'dev_requirement' => false, + ), + ), +); diff --git a/functions/vendor/composer/platform_check.php b/functions/vendor/composer/platform_check.php new file mode 100644 index 0000000000000000000000000000000000000000..4c3a5d68f144c5aff4a1f9e2fcd2d4bc3be133b5 --- /dev/null +++ b/functions/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ +<?php + +// platform_check.php @generated by Composer + +$issues = array(); + +if (!(PHP_VERSION_ID >= 80100)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/functions/vendor/firehed/cbor b/functions/vendor/firehed/cbor new file mode 160000 index 0000000000000000000000000000000000000000..eef67b1b5fdf90a3688fc8d9d13afdaf342c4b80 --- /dev/null +++ b/functions/vendor/firehed/cbor @@ -0,0 +1 @@ +Subproject commit eef67b1b5fdf90a3688fc8d9d13afdaf342c4b80 diff --git a/functions/vendor/firehed/webauthn b/functions/vendor/firehed/webauthn new file mode 160000 index 0000000000000000000000000000000000000000..267d04a6d2926d9ab6d7630fb86a92410eb6b36c --- /dev/null +++ b/functions/vendor/firehed/webauthn @@ -0,0 +1 @@ +Subproject commit 267d04a6d2926d9ab6d7630fb86a92410eb6b36c diff --git a/functions/version.php b/functions/version.php index 2f8a7b6de348fef4bb9e46fd6278429f2bfdaf03..fca4fa829809b78fa90728e2e077b2c91c34338e 100644 --- a/functions/version.php +++ b/functions/version.php @@ -4,7 +4,7 @@ define("VERSION", "1.6"); //decimal release version e.g 1.32 /* set latest version */ define("VERSION_VISIBLE", "1.6.0"); //visible version in footer e.g 1.3.2 /* set latest revision */ -define("REVISION", "001"); //increment on static content changes (js/css) or point releases to avoid caching issues +define("REVISION", "002"); //increment on static content changes (js/css) or point releases to avoid caching issues /* set last possible upgrade */ define("LAST_POSSIBLE", "1.19"); //minimum required version to be able to upgrade /* set published - hide dbversion in footer */ diff --git a/js/login.js b/js/login.js index ba26cebe8139d9142b51c4e2b0a6ebddaa6a499e..2b18c75fc2a9a663a9b8aa7c7cda5a1d179c6dce 100755 --- a/js/login.js +++ b/js/login.js @@ -5,6 +5,24 @@ * */ +/* loading spinner functions +*******************************/ +function showSpinner(hide_res = true) { + $('div.loading').show(); + $('input[type=submit]').addClass("disabled"); + $('button.passkey_login').addClass("disabled"); + if (hide_res) { + $('#loginCheckPasskeys').fadeOut('fast'); + $('#loginCheck').fadeOut('fast'); + } +} +function hideSpinner() { + $('div.loading').fadeOut('fast'); + $('input[type=submit]').removeClass("disabled"); + $('button.passkey_login').removeClass("disabled"); +} + + $(document).ready(function() { @@ -14,14 +32,7 @@ $('div.jqueryError').hide(); $('div.loading').hide(); -/* loading spinner functions -*******************************/ -function showSpinner() { - $('div.loading').show(); -} -function hideSpinner() { - $('div.loading').fadeOut('fast'); -} + /* Login redirect function if success ****************************************/ @@ -40,13 +51,12 @@ $('form#login').submit(function() { var logindata = $(this).serialize(); - $('div#loginCheck').hide(); - //post to check form + // post to check form $.post('app/login/login_check.php', logindata, function(data) { $('div#loginCheck').html(data).fadeIn('fast'); //reload after 2 seconds if succeeded! if(data.search("alert alert-success") != -1) { - showSpinner(); + showSpinner(false); //search for redirect if($('form#login input#phpipamredirect').length > 0) { setTimeout(function (){window.location=$('form#login input#phpipamredirect').val();}, 1000); } else { setTimeout(loginRedirect, 1000); } @@ -134,6 +144,93 @@ $(".clearIPrequest").click(function() { }); + +// passkey login +$('.passkey_login').click(function() { + // execute passkey login + startLogin(); + // prevent submit + return false; +}) + }); + +function loginRedirect2() { + var base = $('.iebase').html(); + window.location=base; +} + + +const startLogin = async (e) => { + + // check if browser supports webauthn + if (!window.PublicKeyCredential) { + return + } + + // show login window + showSpinner(); + + try { + // get and parse challenge + const challengeReq = await fetch('app/tools/user-menu/passkey_challenge.php') + const challengeB64 = await challengeReq.json() + const challenge = atob(challengeB64) // base64-decode + + // Format for WebAuthn API + const getOptions = { + publicKey: { + challenge: Uint8Array.from(challenge, c => c.charCodeAt(0)), + allowCredentials: [], + mediation: 'conditional', + }, + } + + // Call the WebAuthn browser API and get the response. This may throw, which you + // should handle. Example: user cancels or never interacts with the device. + const credential = await navigator.credentials.get(getOptions) + // console.log(credential) + + // Format the credential to send to the server. This must match the format + // handed by the ResponseParser class. The formatting code below can be used + // without modification. + const dataForResponseParser = { + rawId: Array.from(new Uint8Array(credential.rawId)), + keyId: credential.id, + type: credential.type, + authenticatorData: Array.from(new Uint8Array(credential.response.authenticatorData)), + clientDataJSON: Array.from(new Uint8Array(credential.response.clientDataJSON)), + signature: Array.from(new Uint8Array(credential.response.signature)), + userHandle: Array.from(new Uint8Array(credential.response.userHandle)), + } + + // Send this to your endpoint - adjust to your needs. + const request = new Request('app/login/passkey_login_check.php', { + body: JSON.stringify(dataForResponseParser), + headers: { + 'Content-type': 'application/json', + }, + method: 'POST', + }) + const result = await fetch(request) + + // process result by http status returned from passkey_login_check + if(result.status==200) { + $('#loginCheckPasskeys').html("<div class='alert alert-success'>Passkey authentication successfull</div>").show(); + setTimeout(loginRedirect2, 1000) + } + else { + $('#loginCheckPasskeys').html("<div class='alert alert-danger'>Passkey authentication failed!</div>").show(); + console.log(result) + hideSpinner() + } + } + // handle throwable error + catch(err) { + $('#loginCheckPasskeys').html("<div class='alert alert-danger'>Passkey authentication failed!</div>").show(); + console.log(err) + hideSpinner(); + } +} \ No newline at end of file diff --git a/js/magic.js b/js/magic.js index 8d6def598158534a497d86a17f7bb433caf93cf2..2e9e70552a00e8157ce2b86c9bddb51c14613967 100755 --- a/js/magic.js +++ b/js/magic.js @@ -2418,6 +2418,10 @@ $(document).on("click", "#editVLANdomainsubmit", function() { submit_popup_data (".domainEditResult", "app/admin/vlans/edit-domain-result.php", $('form#editVLANdomain').serialize()); }); +/* ---- Show permissions ----- */ +$(document).on("click", ".toggle-module-permissions", function () { + $(this).next('div').toggleClass('hidden'); +}) /* ---- VRF ----- */ //submit form diff --git a/misc/CHANGELOG b/misc/CHANGELOG index 76c0aa4f08380ec06ca61397c372cd12bf7ec7bd..f625c7708b4ad936bd7b6917ffe340a54fcc1391 100755 --- a/misc/CHANGELOG +++ b/misc/CHANGELOG @@ -1,3 +1,9 @@ +== 1.7.0 + + New features: + ------------ + + support for passkeys / passwordless logins; + == 1.6.0 Enhancements, changes: