diff --git a/app/Models/SCIMUser.php b/app/Models/SCIMUser.php new file mode 100644 index 000000000..71bd9169a --- /dev/null +++ b/app/Models/SCIMUser.php @@ -0,0 +1,16 @@ +ignoreWrite()->setRead( + function (&$object) { + return $object->getFullNameAttribute(); + } + ); + + $config['validations'][$core.'emails'] = 'nullable|array'; // emails are not required in Snipe-IT... + $config['validations'][$core.'emails.*.value'] = 'required|email'; // ...but if you give us one, it better be an email address + + $mappings['emails'] = [[ + "value" => AttributeMapping::eloquent("email"), + "display" => null, + "type" => AttributeMapping::constant("work")->ignoreWrite(), + "primary" => AttributeMapping::constant(true)->ignoreWrite() + ]]; + + //active + $config['validations'][$core.'active'] = 'boolean'; + + $mappings['active'] = AttributeMapping::eloquent('activated'); + + //phone + $config['validations'][$core.'phoneNumbers'] = 'nullable|array'; + $config['validations'][$core.'phoneNumbers.*.value'] = 'required'; + + $mappings['phoneNumbers'] = [[ + "value" => AttributeMapping::eloquent("phone"), + "display" => null, + "type" => AttributeMapping::constant("work")->ignoreWrite(), + "primary" => AttributeMapping::constant(true)->ignoreWrite() + ]]; + + //address + $config['validations'][$core.'addresses'] = 'nullable|array'; + $config['validations'][$core.'addresses.*.streetAddress'] = 'required'; + $config['validations'][$core.'addresses.*.locality'] = 'string'; + $config['validations'][$core.'addresses.*.region'] = 'string'; + $config['validations'][$core.'addresses.*.postalCode'] = 'string'; + $config['validations'][$core.'addresses.*.country'] = 'string'; + + $mappings['addresses'] = [[ + 'type' => AttributeMapping::constant("work")->ignoreWrite(), + 'formatted' => AttributeMapping::constant("n/a")->ignoreWrite(), // TODO - is this right? This doesn't look right. + 'streetAddress' => AttributeMapping::eloquent("address"), + 'locality' => AttributeMapping::eloquent("city"), + 'region' => AttributeMapping::eloquent("state"), + 'postalCode' => AttributeMapping::eloquent("zip"), + 'country' => AttributeMapping::eloquent("country"), + 'primary' => AttributeMapping::constant(true)->ignoreWrite() //this isn't in the example? + ]]; + + //title + $config['validations'][$core.'title'] = 'string'; + $mappings['title'] = AttributeMapping::eloquent('jobtitle'); + + //Preferred Language + $config['validations'][$core.'preferredLanguage'] = 'string'; + $mappings['preferredLanguage'] = AttributeMapping::eloquent('locale'); + + /* + more snipe-it attributes I'd like to check out (to map to 'enterprise' maybe?): + - website + - notes? + - remote??? + - location_id ? + - company_id to "organization?" + */ + + $enterprise_namespace = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'; + $ent = $enterprise_namespace.':'; + + // we remove the 'example' namespace and add the Enterprise one + $config['mapping']['schemas'] = AttributeMapping::constant( [$core_namespace, $enterprise_namespace] )->ignoreWrite(); + + $config['validations'][$ent.'employeeNumber'] = 'string'; + $config['validations'][$ent.'department'] = 'string'; + $config['validations'][$ent.'manager'] = 'nullable'; + $config['validations'][$ent.'manager.value'] = 'string'; + + $config['mapping'][$enterprise_namespace] = [ + 'employeeNumber' => AttributeMapping::eloquent('employee_num'), + 'department' =>(new AttributeMapping())->setAdd( // FIXME parent? + function ($value, &$object) { + \Log::error("Department-Add: $value"); //FIXME + $department = Department::where("name", $value)->first(); + if ($department) { + $object->department_id = $department->id; + } + } + )->setReplace( + function ($value, &$object) { + \Log::error("Department-Replace: $value"); //FIXME + $department = Department::where("name", $value)->first(); + if ($department) { + $object->department_id = $department->id; + } + } + )->setRead( + function (&$object) { + \Log::error("Weird department reader firing..."); //FIXME + return $object->department ? $object->department->name : null; + } + ), + 'manager' => [ + // FIXME - manager writes are disabled. This kinda works but it leaks errors all over the place. Not cool. + // '$ref' => (new AttributeMapping())->ignoreWrite()->ignoreRead(), + // 'displayName' => (new AttributeMapping())->ignoreWrite()->ignoreRead(), + // NOTE: you could probably do a 'plain' Eloquent mapping here, but we don't for future-proofing + 'value' => (new AttributeMapping())->setAdd( + function ($value, &$object) { + \Log::error("Manager-Add: $value"); //FIXME + $manager = User::find($value); + if ($manager) { + $object->manager_id = $manager->id; + } + } + )->setReplace( + function ($value, &$object) { + \Log::error("Manager-Replace: $value"); //FIXME + $manager = User::find($value); + if ($manager) { + $object->manager_id = $manager->id; + } + } + )->setRead( + function (&$object) { + \Log::error("Weird manager reader firing..."); //FIXME + return $object->manager_id; + } + ), + ] + ]; + + return $config; + } + +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index c4110a589..29e581709 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -8,6 +8,7 @@ use App\Models\Component; use App\Models\Consumable; use App\Models\License; use App\Models\Setting; +use App\Models\SnipeSCIMConfig; use App\Observers\AccessoryObserver; use App\Observers\AssetObserver; use App\Observers\ComponentObserver; @@ -80,6 +81,8 @@ class AppServiceProvider extends ServiceProvider if ($this->app->environment(['local', 'develop'])) { $this->app->register(\Laravel\Dusk\DuskServiceProvider::class); } + + $this->app->singleton('ArieTimmerman\Laravel\SCIMServer\SCIMConfig', SnipeSCIMConfig::class); // this overrides the default SCIM configuration with our own } } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 16a22d866..447f1bc30 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -24,7 +24,7 @@ class RouteServiceProvider extends ServiceProvider $this->mapWebRoutes(); - // + require base_path('routes/scim.php'); }); } diff --git a/composer.json b/composer.json index d3261eadf..712164f74 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,7 @@ "ext-mbstring": "*", "ext-pdo": "*", "alek13/slack": "^2.0", + "arietimmerman/laravel-scim-server": "^0.5.5", "bacon/bacon-qr-code": "^2.0", "barryvdh/laravel-debugbar": "^3.6", "barryvdh/laravel-dompdf": "^1.0", @@ -61,7 +62,6 @@ "rollbar/rollbar-laravel": "^7.0", "spatie/laravel-backup": "^6.16", "tecnickcom/tc-lib-barcode": "^1.15", - "tightenco/ziggy": "v1.2.0", "unicodeveloper/laravel-password": "^1.0", "watson/validating": "^6.1" }, diff --git a/composer.lock b/composer.lock index 68671abb8..42b5f2e9a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "442a6af235589e35cfcaa7e5e39e75ec", + "content-hash": "448c7508ab99eb86eb62e5cac3e9ee59", "packages": [ { "name": "alek13/slack", @@ -72,6 +72,65 @@ ], "time": "2021-10-20T22:52:32+00:00" }, + { + "name": "arietimmerman/laravel-scim-server", + "version": "v0.5.5", + "source": { + "type": "git", + "url": "https://github.com/arietimmerman/laravel-scim-server.git", + "reference": "8fb5b1cc0d28ace820b5b38a543d801fd49ada90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/arietimmerman/laravel-scim-server/zipball/8fb5b1cc0d28ace820b5b38a543d801fd49ada90", + "reference": "8fb5b1cc0d28ace820b5b38a543d801fd49ada90", + "shasum": "" + }, + "require": { + "illuminate/console": "^6.0|^7.0|^8.0", + "illuminate/database": "^6.0|^7.0|^8.0", + "illuminate/support": "^6.0|^7.0|^8.0", + "php": "^7.0|^8.0", + "tmilos/scim-filter-parser": "^1.3", + "tmilos/scim-schema": "^0.1.0" + }, + "require-dev": { + "laravel/legacy-factories": "*", + "orchestra/testbench": "^5.0|^6.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "ArieTimmerman\\Laravel\\SCIMServer\\ServiceProvider" + ], + "aliases": { + "SCIMServerHelper": "ArieTimmerman\\Laravel\\SCIMServer\\Helper" + } + } + }, + "autoload": { + "psr-4": { + "ArieTimmerman\\Laravel\\SCIMServer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Arie Timmerman", + "email": "arietimmerman@gmail.com" + } + ], + "description": "Laravel Package for creating a SCIM server", + "support": { + "issues": "https://github.com/arietimmerman/laravel-scim-server/issues", + "source": "https://github.com/arietimmerman/laravel-scim-server/tree/v0.5.5" + }, + "time": "2022-02-03T20:56:31+00:00" + }, { "name": "asm89/stack-cors", "version": "v2.1.1", @@ -11030,72 +11089,6 @@ ], "time": "2021-12-31T09:40:23+00:00" }, - { - "name": "tightenco/ziggy", - "version": "v1.2.0", - "source": { - "type": "git", - "url": "https://github.com/tighten/ziggy.git", - "reference": "147804d5f3e98b897fc1ed15efc2807f1099cf83" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/tighten/ziggy/zipball/147804d5f3e98b897fc1ed15efc2807f1099cf83", - "reference": "147804d5f3e98b897fc1ed15efc2807f1099cf83", - "shasum": "" - }, - "require": { - "laravel/framework": ">=5.4@dev" - }, - "require-dev": { - "orchestra/testbench": "^6.0", - "phpunit/phpunit": "^9.2" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Tightenco\\Ziggy\\ZiggyServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Tightenco\\Ziggy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Daniel Coulbourne", - "email": "daniel@tighten.co" - }, - { - "name": "Jake Bathman", - "email": "jake@tighten.co" - }, - { - "name": "Jacob Baker-Kretzmar", - "email": "jacob@tighten.co" - } - ], - "description": "Generates a Blade directive exporting all of your named Laravel routes. Also provides a nice route() helper function in JavaScript.", - "homepage": "https://github.com/tighten/ziggy", - "keywords": [ - "Ziggy", - "javascript", - "laravel", - "routes" - ], - "support": { - "issues": "https://github.com/tighten/ziggy/issues", - "source": "https://github.com/tighten/ziggy/tree/v1.2.0" - }, - "time": "2021-05-24T22:46:59+00:00" - }, { "name": "tijsverkoyen/css-to-inline-styles", "version": "2.2.4", @@ -11149,6 +11142,199 @@ }, "time": "2021-12-08T09:12:39+00:00" }, + { + "name": "tmilos/lexer", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/tmilos/lexer.git", + "reference": "e7885595614759f1da2ff79b66e3fb26d7f875fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tmilos/lexer/zipball/e7885595614759f1da2ff79b66e3fb26d7f875fa", + "reference": "e7885595614759f1da2ff79b66e3fb26d7f875fa", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "~5.6", + "satooshi/php-coveralls": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Tmilos\\Lexer\\": "src/", + "Tests\\Tmilos\\Lexer\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Milos Tomic", + "email": "tmilos@gmail.com" + } + ], + "description": "Lexical analyzer with individual token definition with regular expressions", + "keywords": [ + "lexer", + "parser" + ], + "support": { + "issues": "https://github.com/tmilos/lexer/issues", + "source": "https://github.com/tmilos/lexer/tree/master" + }, + "time": "2016-12-21T11:22:39+00:00" + }, + { + "name": "tmilos/scim-filter-parser", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/tmilos/scim-filter-parser.git", + "reference": "cfd9ba1f33e1e15adcab2481bffd74cb9fb35704" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tmilos/scim-filter-parser/zipball/cfd9ba1f33e1e15adcab2481bffd74cb9fb35704", + "reference": "cfd9ba1f33e1e15adcab2481bffd74cb9fb35704", + "shasum": "" + }, + "require": { + "tmilos/lexer": "^1.0", + "tmilos/value": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7", + "satooshi/php-coveralls": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Tmilos\\ScimFilterParser\\": "src/", + "Tests\\Tmilos\\ScimFilterParser\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Milos Tomic", + "email": "tmilos@gmail.com" + } + ], + "description": "System for Cross-domain Identity Management SCIM AST filter parser PHP library", + "keywords": [ + "SCIM AST", + "SCIM filter parser", + "SCIM parser", + "ast", + "parser", + "scim", + "simplecloud" + ], + "support": { + "issues": "https://github.com/tmilos/scim-filter-parser/issues", + "source": "https://github.com/tmilos/scim-filter-parser/tree/master" + }, + "time": "2017-01-19T11:17:42+00:00" + }, + { + "name": "tmilos/scim-schema", + "version": "0.1", + "source": { + "type": "git", + "url": "https://github.com/tmilos/scim-schema.git", + "reference": "bb871e667b33080b4cd36d7a9b2ac2cdbf796062" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tmilos/scim-schema/zipball/bb871e667b33080b4cd36d7a9b2ac2cdbf796062", + "reference": "bb871e667b33080b4cd36d7a9b2ac2cdbf796062", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "^4.8|^5.6", + "satooshi/php-coveralls": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Tmilos\\ScimSchema\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Milos Tomic", + "email": "tmilos@gmail.com" + } + ], + "description": "SCIM schema library", + "support": { + "issues": "https://github.com/tmilos/scim-schema/issues", + "source": "https://github.com/tmilos/scim-schema/tree/master" + }, + "time": "2017-11-25T22:18:16+00:00" + }, + { + "name": "tmilos/value", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/tmilos/value.git", + "reference": "9e78ad9c026b14cacec1a27552ee0ada9d7d1c06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tmilos/value/zipball/9e78ad9c026b14cacec1a27552ee0ada9d7d1c06", + "reference": "9e78ad9c026b14cacec1a27552ee0ada9d7d1c06", + "shasum": "" + }, + "require": { + "php": ">=5.5.1" + }, + "require-dev": { + "moontoast/math": "~1.1", + "phpunit/phpunit": "~4.8", + "ramsey/uuid": "^3.3", + "satooshi/php-coveralls": "~0.6" + }, + "type": "library", + "autoload": { + "psr-0": { + "Tmilos\\Value\\": "src/", + "Tmilos\\Value\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Milos Tomic", + "email": "tmilos@gmail.com", + "homepage": "https://github.com/tmilos/", + "role": "Developer" + } + ], + "support": { + "issues": "https://github.com/tmilos/value/issues", + "source": "https://github.com/tmilos/value/tree/master" + }, + "time": "2016-06-06T10:22:16+00:00" + }, { "name": "unicodeveloper/laravel-password", "version": "1.0.4", @@ -13474,5 +13660,5 @@ "ext-pdo": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.1.0" } diff --git a/config/app.php b/config/app.php index d9fc45bb0..ba56b42e3 100755 --- a/config/app.php +++ b/config/app.php @@ -342,7 +342,6 @@ return [ Laravel\Passport\PassportServiceProvider::class, Laravel\Tinker\TinkerServiceProvider::class, Unicodeveloper\DumbPassword\DumbPasswordServiceProvider::class, - Tightenco\Ziggy\ZiggyServiceProvider::class, // Laravel routes in vue Eduardokum\LaravelMailAutoEmbed\ServiceProvider::class, /* diff --git a/config/scim.php b/config/scim.php new file mode 100644 index 000000000..3008e2f07 --- /dev/null +++ b/config/scim.php @@ -0,0 +1,5 @@ + false +]; diff --git a/public/js/snipeit.js b/public/js/snipeit.js deleted file mode 100644 index 81c79c884..000000000 --- a/public/js/snipeit.js +++ /dev/null @@ -1,738 +0,0 @@ -/******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // define getter function for harmony exports -/******/ __webpack_require__.d = function(exports, name, getter) { -/******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // getDefaultExport function for compatibility with non-harmony modules -/******/ __webpack_require__.n = function(module) { -/******/ var getter = module && module.__esModule ? -/******/ function getDefault() { return module['default']; } : -/******/ function getModuleExports() { return module; }; -/******/ __webpack_require__.d(getter, 'a', getter); -/******/ return getter; -/******/ }; -/******/ -/******/ // Object.prototype.hasOwnProperty.call -/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = "/"; -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 2); -/******/ }) -/************************************************************************/ -/******/ ({ - -/***/ "./resources/assets/js/snipeit.js": -/***/ (function(module, exports) { - - -/** - * Module containing core application logic. - * @param {jQuery} $ Insulated jQuery object - * @param {JSON} settings Insulated `window.snipeit.settings` object. - * @return {IIFE} Immediately invoked. Returns self. - */ - -lineOptions = { - - legend: { - position: "bottom" - }, - scales: { - yAxes: [{ - ticks: { - fontColor: "rgba(0,0,0,0.5)", - fontStyle: "bold", - beginAtZero: true, - maxTicksLimit: 5, - padding: 20 - }, - gridLines: { - drawTicks: false, - display: false - } - }], - xAxes: [{ - gridLines: { - zeroLineColor: "transparent" - }, - ticks: { - padding: 20, - fontColor: "rgba(0,0,0,0.5)", - fontStyle: "bold" - } - }] - } - -}; - -pieOptions = { - //Boolean - Whether we should show a stroke on each segment - segmentShowStroke: true, - //String - The colour of each segment stroke - segmentStrokeColor: "#fff", - //Number - The width of each segment stroke - segmentStrokeWidth: 1, - //Number - The percentage of the chart that we cut out of the middle - percentageInnerCutout: 50, // This is 0 for Pie charts - //Number - Amount of animation steps - animationSteps: 100, - //String - Animation easing effect - animationEasing: "easeOutBounce", - //Boolean - Whether we animate the rotation of the Doughnut - animateRotate: true, - //Boolean - Whether we animate scaling the Doughnut from the centre - animateScale: false, - //Boolean - whether to make the chart responsive to window resizing - responsive: true, - // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container - maintainAspectRatio: false, - - //String - A legend template - legendTemplate: "", - //String - A tooltip template - tooltipTemplate: "<%=value %> <%=label%> " -}; - -//----------------- -//- END PIE CHART - -//----------------- - - -(function ($, settings) { - var Components = {}; - Components.modals = {}; - - // confirm delete modal - Components.modals.confirmDelete = function () { - var $el = $('table'); - - var events = { - 'click': function click(evnt) { - var $context = $(this); - var $dataConfirmModal = $('#dataConfirmModal'); - var href = $context.attr('href'); - var message = $context.attr('data-content'); - var title = $context.attr('data-title'); - - $('#myModalLabel').text(title); - $dataConfirmModal.find('.modal-body').text(message); - $('#deleteForm').attr('action', href); - $dataConfirmModal.modal({ - show: true - }); - return false; - } - }; - - var render = function render() { - $el.on('click', '.delete-asset', events['click']); - }; - - return { - render: render - }; - - // confirm restore modal - Components.modals.confirmRestore = function () { - var $el = $('table'); - - var events = { - 'click': function click(evnt) { - var $context = $(this); - var $dataConfirmModal = $('#restoreConfirmModal'); - var href = $context.attr('href'); - var message = $context.attr('data-content'); - var title = $context.attr('data-title'); - - $('#myModalLabel').text(title); - $dataConfirmModal.find('.modal-body').text(message); - $('#confirmRestoreForm').attr('action', href); - $dataConfirmModal.modal({ - show: true - }); - return false; - } - }; - - var render = function render() { - $el.on('click', '.restore-modal', events['click']); - }; - - return { - render: render - }; - }; - - /** - * Application start point - * Component definition stays out of load event, execution only happens. - */ - $(function () { - new Components.modals.confirmDelete().render(); - }); -})(jQuery, window.snipeit.settings); - -$(document).ready(function () { - - /* - * Slideout help menu - */ - $('.slideout-menu-toggle').on('click', function (event) { - console.log('clicked'); - event.preventDefault(); - // create menu variables - var slideoutMenu = $('.slideout-menu'); - var slideoutMenuWidth = $('.slideout-menu').width(); - - // toggle open class - slideoutMenu.toggleClass("open"); - - // slide menu - if (slideoutMenu.hasClass("open")) { - slideoutMenu.show(); - slideoutMenu.animate({ - right: "0px" - }); - } else { - slideoutMenu.animate({ - right: -slideoutMenuWidth - }, "-350px"); - slideoutMenu.fadeOut(); - } - }); - - /* - * iCheck checkbox plugin - */ - - $('input[type="checkbox"].minimal, input[type="radio"].minimal').iCheck({ - checkboxClass: 'icheckbox_minimal-blue', - radioClass: 'iradio_minimal-blue' - }); - - /* - * Select2 - */ - - var iOS = /iPhone|iPad|iPod/.test(navigator.userAgent) && !window.MSStream; - if (!iOS) { - // Vue collision: Avoid overriding a vue select2 instance - // by checking to see if the item has already been select2'd. - $('select.select2:not(".select2-hidden-accessible")').each(function (i, obj) { - { - $(obj).select2(); - } - }); - } - - $('.datepicker').datepicker(); - - // var datepicker = $.fn.datepicker.noConflict(); // return $.fn.datepicker to previously assigned value - // $.fn.bootstrapDP = datepicker; - // $('.datepicker').datepicker(); - - - // Crazy select2 rich dropdowns with images! - $('.js-data-ajax').each(function (i, item) { - var link = $(item); - var endpoint = link.data("endpoint"); - var select = link.data("select"); - - link.select2({ - - /** - * Adds an empty placeholder, allowing every select2 instance to be cleared. - * This placeholder can be overridden with the "data-placeholder" attribute. - */ - placeholder: '', - allowClear: true, - - ajax: { - - // the baseUrl includes a trailing slash - url: Ziggy.baseUrl + 'api/v1/' + endpoint + '/selectlist', - dataType: 'json', - delay: 250, - headers: { - "X-Requested-With": 'XMLHttpRequest', - "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content') - }, - data: function data(params) { - var data = { - search: params.term, - page: params.page || 1, - assetStatusType: link.data("asset-status-type") - }; - return data; - }, - processResults: function processResults(data, params) { - - params.page = params.page || 1; - - var answer = { - results: data.items, - pagination: { - more: "true" //(params.page < data.page_count) - } - }; - - return answer; - }, - cache: true - }, - escapeMarkup: function escapeMarkup(markup) { - return markup; - }, // let our custom formatter work - templateResult: formatDatalist, - templateSelection: formatDataSelection - }); - }); - - function getSelect2Value(element) { - - // if the passed object is not a jquery object, assuming 'element' is a selector - if (!(element instanceof jQuery)) element = $(element); - - var select = element.data("select2"); - - // There's two different locations where the select2-generated input element can be. - searchElement = select.dropdown.$search || select.$container.find(".select2-search__field"); - - var value = searchElement.val(); - return value; - } - - $(".select2-hidden-accessible").on('select2:selecting', function (e) { - var data = e.params.args.data; - var isMouseUp = false; - var element = $(this); - var value = getSelect2Value(element); - - if (e.params.args.originalEvent) isMouseUp = e.params.args.originalEvent.type == "mouseup"; - - // if selected item does not match typed text, do not allow it to pass - force close for ajax. - if (!isMouseUp) { - if (value.toLowerCase() && data.text.toLowerCase().indexOf(value) < 0) { - e.preventDefault(); - - element.select2('close'); - - // if it does match, we set a flag in the event (which gets passed to subsequent events), telling it not to worry about the ajax - } else if (value.toLowerCase() && data.text.toLowerCase().indexOf(value) > -1) { - e.params.args.noForceAjax = true; - } - } - }); - - $(".select2-hidden-accessible").on('select2:closing', function (e) { - var element = $(this); - var value = getSelect2Value(element); - var noForceAjax = false; - var isMouseUp = false; - if (e.params.args.originalSelect2Event) noForceAjax = e.params.args.originalSelect2Event.noForceAjax; - if (e.params.args.originalEvent) isMouseUp = e.params.args.originalEvent.type == "mouseup"; - - if (value && !noForceAjax && !isMouseUp) { - var endpoint = element.data("endpoint"); - var assetStatusType = element.data("asset-status-type"); - $.ajax({ - url: Ziggy.baseUrl + 'api/v1/' + endpoint + '/selectlist?search=' + value + '&page=1' + (assetStatusType ? '&assetStatusType=' + assetStatusType : ''), - dataType: 'json', - headers: { - "X-Requested-With": 'XMLHttpRequest', - "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content') - } - }).done(function (response) { - ; - var currentlySelected = element.select2('data').map(function (x) { - return +x.id; - }).filter(function (x) { - return x !== 0; - }); - - // makes sure we're not selecting the same thing twice for multiples - var filteredResponse = response.items.filter(function (item) { - return currentlySelected.indexOf(+item.id) < 0; - }); - - var first = currentlySelected.length > 0 ? filteredResponse[0] : response.items[0]; - - if (first && first.id) { - first.selected = true; - - if ($("option[value='" + first.id + "']", element).length < 1) { - var option = new Option(first.text, first.id, true, true); - element.append(option); - } else { - var isMultiple = element.attr("multiple") == "multiple"; - element.val(isMultiple ? element.val().concat(first.id) : element.val(first.id)); - } - element.trigger('change'); - - element.trigger({ - type: 'select2:select', - params: { - data: first - } - }); - } - }); - } - }); - - function formatDatalist(datalist) { - var loading_markup = ' Loading...'; - if (datalist.loading) { - return loading_markup; - } - - var markup = "
"; - markup += "
"; - if (datalist.image) { - markup += "
"; - } else { - markup += "
"; - } - - markup += "
" + datalist.text + "
"; - markup += "
"; - return markup; - } - - function formatDataSelection(datalist) { - return datalist.text; - } - - // This handles the radio button selectors for the checkout-to-foo options - // on asset checkout and also on asset edit - $(function () { - $('input[name=checkout_to_type]').on("change", function () { - var assignto_type = $('input[name=checkout_to_type]:checked').val(); - var userid = $('#assigned_user option:selected').val(); - - if (assignto_type == 'asset') { - $('#current_assets_box').fadeOut(); - $('#assigned_asset').show(); - $('#assigned_user').hide(); - $('#assigned_location').hide(); - $('.notification-callout').fadeOut(); - } else if (assignto_type == 'location') { - $('#current_assets_box').fadeOut(); - $('#assigned_asset').hide(); - $('#assigned_user').hide(); - $('#assigned_location').show(); - $('.notification-callout').fadeOut(); - } else { - - $('#assigned_asset').hide(); - $('#assigned_user').show(); - $('#assigned_location').hide(); - if (userid) { - $('#current_assets_box').fadeIn(); - } - $('.notification-callout').fadeIn(); - } - }); - }); - - // ------------------------------------------------ - // Deep linking for Bootstrap tabs - // ------------------------------------------------ - var taburl = document.location.toString(); - - // Allow full page URL to activate a tab's ID - // ------------------------------------------------ - // This allows linking to a tab on page load via the address bar. - // So a URL such as, http://snipe-it.local/hardware/2/#my_tab will - // cause the tab on that page with an ID of “my_tab” to be active. - if (taburl.match('#')) { - $('.nav-tabs a[href="#' + taburl.split('#')[1] + '"]').tab('show'); - } - - // Allow internal page links to activate a tab's ID. - // ------------------------------------------------ - // This allows you to link to a tab from anywhere on the page - // including from within another tab. Also note that internal page - // links either inside or out of the tabs need to include data-toggle="tab" - // Ex: Click me - $('a[data-toggle="tab"]').click(function (e) { - var href = $(this).attr("href"); - history.pushState(null, null, href); - e.preventDefault(); - $('a[href="' + $(this).attr('href') + '"]').tab('show'); - }); - - // ------------------------------------------------ - // End Deep Linking for Bootstrap tabs - // ------------------------------------------------ - - - // Image preview - function readURL(input) { - if (input.files && input.files[0]) { - var reader = new FileReader(); - reader.onload = function (e) { - $('#imagePreview').attr('src', e.target.result); - }; - reader.readAsDataURL(input.files[0]); - } - } - - function formatBytes(bytes) { - if (bytes < 1024) return bytes + " Bytes";else if (bytes < 1048576) return (bytes / 1024).toFixed(2) + " KB";else if (bytes < 1073741824) return (bytes / 1048576).toFixed(2) + " MB";else return (bytes / 1073741824).toFixed(2) + " GB"; - }; - - // File size validation - $('#uploadFile').bind('change', function () { - $('#upload-file-status').removeClass('text-success').removeClass('text-danger'); - $('.goodfile').remove(); - $('.badfile').remove(); - $('.badfile').remove(); - $('.previewSize').hide(); - $('#upload-file-info').html(''); - - var max_size = $('#uploadFile').data('maxsize'); - var total_size = 0; - - for (var i = 0; i < this.files.length; i++) { - total_size += this.files[i].size; - $('#upload-file-info').append('' + this.files[i].name + ' (' + formatBytes(this.files[i].size) + ') '); - } - - if (total_size > max_size) { - $('#upload-file-status').addClass('text-danger').removeClass('help-block').prepend(' ').append(' Upload is ' + formatBytes(total_size) + '.'); - } else { - $('#upload-file-status').addClass('text-success').removeClass('help-block').prepend(' '); - readURL(this); - $('#imagePreview').fadeIn(); - } - }); -}); - -/***/ }), - -/***/ "./resources/assets/js/snipeit_modals.js": -/***/ (function(module, exports) { - -/* - * - * Snipe-IT Universal Modal support - * - * Enables modal dialogs to create sub-resources throughout Snipe-IT - * - */ - -/* -HOW TO USE - Create a Button looking like this: - New - If you don't have access to Blade commands (like {{ and }}, etc), you can hard-code a URL as the 'href' - data-toggle="modal" - required for Bootstrap Modals -data-target="#createModal" - fixed ID for the modal, do not change -data-select="assigned_to" - What is the *ID* of the select-dropdown that you're going to be adding to, if the modal-create was a success? Be on the lookout for duplicate ID's, it will confuse this library! -class="btn btn-sm btn-default" - makes it look button-ey, feel free to change :) - -If you want to pass additional variables to the modal (In the Category Create one, for example, you can pass category_id), you can encode them as URL variables in the href - -*/ - -$(function () { - - //handle modal-add-interstitial calls - var model, select; - - if ($('#createModal').length == 0) { - $('body').append(''); - } - - $('#createModal').on("show.bs.modal", function (event) { - var link = $(event.relatedTarget); - model = link.data("dependency"); - select = link.data("select"); - $('#createModal').load(link.attr('href'), function () { - //do we need to re-select2 this, after load? Probably. - $('#createModal').find('select.select2').select2(); - // Initialize the ajaxy select2 with images. - // This is a copy/paste of the code from snipeit.js, would be great to only have this in one place. - $('.js-data-ajax').each(function (i, item) { - var link = $(item); - var endpoint = link.data("endpoint"); - var select = link.data("select"); - - link.select2({ - ajax: { - - // the baseUrl includes a trailing slash - url: Ziggy.baseUrl + 'api/v1/' + endpoint + '/selectlist', - dataType: 'json', - delay: 250, - headers: { - "X-Requested-With": 'XMLHttpRequest', - "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content') - }, - data: function data(params) { - var data = { - search: params.term, - page: params.page || 1, - assetStatusType: link.data("asset-status-type") - }; - return data; - }, - processResults: function processResults(data, params) { - - params.page = params.page || 1; - - var answer = { - results: data.items, - pagination: { - more: "true" //(params.page < data.page_count) - } - }; - - return answer; - }, - cache: true - }, - escapeMarkup: function escapeMarkup(markup) { - return markup; - }, // let our custom formatter work - templateResult: formatDatalist, - templateSelection: formatDataSelection - }); - }); - }); - }); - - $('#createModal').on('click', '#modal-save', function () { - $.ajax({ - type: 'POST', - url: $('.modal-body form').attr('action'), - headers: { - "X-Requested-With": 'XMLHttpRequest', - "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content') - }, - - data: $('.modal-body form').serialize(), - success: function success(result) { - - if (result.status == "error") { - var error_message = ""; - for (var field in result.messages) { - error_message += "
  • Problem(s) with field " + field + ": " + result.messages[field]; - } - $('#modal_error_msg').html(error_message).show(); - return false; - } - var id = result.payload.id; - var name = result.payload.name || result.payload.first_name + " " + result.payload.last_name; - if (!id || !name) { - console.error("Could not find resulting name or ID from modal-create. Name: " + name + ", id: " + id); - return false; - } - $('#createModal').modal('hide'); - $('#createModal').html(""); - - // "select" is the original drop-down menu that someone - // clicked 'add' on to add a new 'thing' - // this code adds the newly created object to that select - var selector = document.getElementById(select); - - if (!selector) { - return false; - } - - selector.options[selector.length] = new Option(name, id); - selector.selectedIndex = selector.length - 1; - $(selector).trigger("change"); - if (window.fetchCustomFields) { - fetchCustomFields(); - } - }, - error: function error(result) { - msg = result.responseJSON.messages || result.responseJSON.error; - $('#modal_error_msg').html("Server Error: " + msg).show(); - } - - }); - }); -}); - -function formatDatalist(datalist) { - var loading_markup = ' Loading...'; - if (datalist.loading) { - return loading_markup; - } - - var markup = "
    "; - markup += "
    "; - if (datalist.image) { - markup += "
    "; - } else { - markup += "
    "; - } - - markup += "
    " + datalist.text + "
    "; - markup += "
    "; - return markup; -} - -function formatDataSelection(datalist) { - return datalist.text; -} - -/***/ }), - -/***/ 2: -/***/ (function(module, exports, __webpack_require__) { - -__webpack_require__("./resources/assets/js/snipeit.js"); -module.exports = __webpack_require__("./resources/assets/js/snipeit_modals.js"); - - -/***/ }) - -/******/ }); \ No newline at end of file diff --git a/resources/assets/js/components/importer/importer-file.vue b/resources/assets/js/components/importer/importer-file.vue index 2f692db01..356d75c5c 100644 --- a/resources/assets/js/components/importer/importer-file.vue +++ b/resources/assets/js/components/importer/importer-file.vue @@ -100,6 +100,7 @@ - - @routes diff --git a/routes/scim.php b/routes/scim.php new file mode 100644 index 000000000..d8d8a8fdb --- /dev/null +++ b/routes/scim.php @@ -0,0 +1,38 @@ +group(function () { + SCIMRouteProvider::routes( + [ + /* + * If we leave public_routes as 'true', the public routes will load *now* and + * be jammed into the same middleware that these private routes are loaded + * with. That's bad, because these routes are *supposed* to be public. + * + * We loaded them a few lines above, *first*, otherwise the various + * fallback routes in the library defined within these *secured* routes + * will "take over" the above routes - and then you will end up losing + * like 4 hours of your life trying to figure out why the public routes + * aren't quite working right. Ask me how I know (BMW, 3/19/2022) + */ + 'public_routes' => false + ] + ); + + SCIMRouteProvider::meRoutes(); +}); // ->can('superuser');