├── server ├── problems.py ├── favicon.ico ├── static │ ├── dist │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ └── glyphicons-halflings-regular.woff │ │ ├── css │ │ │ ├── site-template.css │ │ │ ├── bootstrap-theme.min.css │ │ │ ├── bootstrap-theme.css │ │ │ └── bootstrap-theme.css.map │ │ ├── js │ │ │ ├── setup.js │ │ │ ├── main.js │ │ │ ├── populate.js │ │ │ ├── mustache.min.js │ │ │ └── bootstrap.min.js │ │ └── LICENSE │ ├── devices.mustache.html │ └── index.html ├── xactn.log ├── Manifest.plist.template ├── LICENSE ├── README ├── Example.mobileconfig ├── device.py └── server.py ├── references ├── InsideAppleMDM.pdf └── CommandReference.pdf ├── client-reporting ├── client-reporting │ ├── client-reporting-Prefix.pch │ ├── client_reporting.h │ └── client_reporting.m └── client-reporting.xcodeproj │ ├── project.xcworkspace │ └── contents.xcworkspacedata │ └── project.pbxproj ├── README.md ├── scripts └── make_certs.sh ├── LICENSE └── vendor └── mdm_vendor_sign.py /server/problems.py: -------------------------------------------------------------------------------- 1 | problems = [] 2 | -------------------------------------------------------------------------------- /server/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vineetchoudhary/Apple-iOS-MDM-Server/HEAD/server/favicon.ico -------------------------------------------------------------------------------- /references/InsideAppleMDM.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vineetchoudhary/Apple-iOS-MDM-Server/HEAD/references/InsideAppleMDM.pdf -------------------------------------------------------------------------------- /references/CommandReference.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vineetchoudhary/Apple-iOS-MDM-Server/HEAD/references/CommandReference.pdf -------------------------------------------------------------------------------- /server/static/dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vineetchoudhary/Apple-iOS-MDM-Server/HEAD/server/static/dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /server/static/dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vineetchoudhary/Apple-iOS-MDM-Server/HEAD/server/static/dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /server/static/dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vineetchoudhary/Apple-iOS-MDM-Server/HEAD/server/static/dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /client-reporting/client-reporting/client-reporting-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'client-reporting' target in the 'client-reporting' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /client-reporting/client-reporting.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /server/static/dist/css/site-template.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | } 4 | .success{ 5 | color: #468847; 6 | } 7 | .warning{ 8 | color: #F89406; 9 | } 10 | .danger{ 11 | color: #B94A48; 12 | } 13 | .site-template { 14 | padding: 40px 15px; 15 | text-align: center; 16 | } 17 | -------------------------------------------------------------------------------- /server/xactn.log: -------------------------------------------------------------------------------- 1 | ## transaction log 2 | ## this will get filled up with timestamped JSON strings showing commands 3 | ## issued by the server, and requests/responses from the client. 4 | ## 5 | ## Be careful, this will likely include sensitive information, like 6 | ## * Device Tokens 7 | ## * Devie Unlock Tokens 8 | ## * Push Certificate Topic (not super sensitive, but still) 9 | ## * Device information (UDID, phone numbers, blah blah blah) 10 | ## 11 | ## So if you want to send this to anyone, SCRUB IT FIRST 12 | ## 13 | 14 | -------------------------------------------------------------------------------- /client-reporting/client-reporting/client_reporting.h: -------------------------------------------------------------------------------- 1 | // 2 | // client_reporting.h 3 | // client-reporting 4 | // 5 | // Created by Black, Gavin S. on 12/20/13. 6 | // Copyright (c) 2013 Black, Gavin S. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | @interface client_reporting : NSObject 14 | +(void) setHostAddress: (NSString*) host; 15 | +(void) setPause : (BOOL) toggle; 16 | 17 | 18 | +(void) reportJailbreak; 19 | +(void) reportDebugger; 20 | +(void) reportLocation : (CLLocationCoordinate2D*) coords; 21 | 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /server/static/dist/js/setup.js: -------------------------------------------------------------------------------- 1 | function update_cmds(){ 2 | // Populate command list 3 | $.post("/getcommands", function(cmd_list){ 4 | cmds = JSON.parse(cmd_list); 5 | x = document.getElementById("commands"); 6 | x.options.length = 0 7 | x.options[x.options.length] = new Option("Select Command", "", true, false); 8 | for(i=0;i server.pem 10 | 11 | 12 | echo "" 13 | echo "2. Copying keys and certs to server folder" 14 | # Move relevant certs to the /server/ directory 15 | mv server.key ../server/server.key 16 | mv server.crt ../server/server.crt 17 | mv server.pem ../server/server.pem 18 | mv cacert.crt ../server/ca.crt 19 | mv identity.crt ../server/identity.crt 20 | cp identity.p12 ../server/Identity.p12 -------------------------------------------------------------------------------- /server/Manifest.plist.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | items 4 | 5 | 6 | assets 7 | 8 | 9 | kind 10 | software-package 11 | url 12 | https://*** SERVER_IP ***:8080/app 13 | 14 | 15 | metadata 16 | 17 | bundle-identifier 18 | *** APPLICATION BUNDLE ID (like com.example.myapp) *** 19 | bundle-version 20 | 1.0.0 21 | kind 22 | software 23 | subtitle 24 | 25 | title 26 | *** APP NAME *** 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Vineet Choudhary 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /server/static/dist/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2014 Twitter, Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /server/static/dist/js/main.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | // Initial setup 3 | update_cmds() 4 | 5 | // Start polling 6 | populate_devices(); 7 | setInterval(populate_devices, 2000); 8 | 9 | // Check for device-specific content and enable if applicable 10 | if(checkDevice()){ 11 | document.getElementById("showEnroll").style.display="block"; 12 | document.getElementById("showCert").style.display="block"; 13 | } 14 | 15 | // Submit button functionality 16 | $("#submitcmd").click(function(event){ 17 | var checked_devices = []; 18 | 19 | // Get all selected devices 20 | $(".row_box").each(function(){ 21 | if(this.checked){ 22 | checked_devices.push($(this).closest(".panel").attr("id")); 23 | } 24 | }); 25 | 26 | // Input checking. 27 | if ($("#commands").val()==0){ 28 | alert("Please choose a command."); 29 | } 30 | else if (checked_devices.length==0){ 31 | alert("Please choose one or more devices."); 32 | } 33 | else{ 34 | // Send AJAX request 35 | // Variable to pass all necessary data to server 36 | var parameters = { 37 | "cmd":$("#commands").val(), 38 | "dev[]":checked_devices 39 | }; 40 | 41 | // Use stringify to fix odd error with passing just parameters 42 | $.post("/queue", JSON.stringify(parameters), function(){}); 43 | } 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /client-reporting/client-reporting/client_reporting.m: -------------------------------------------------------------------------------- 1 | // 2 | // client_reporting.m 3 | // client-reporting 4 | // 5 | // Created by Black, Gavin S. on 12/20/13. 6 | // Copyright (c) 2013 Black, Gavin S. All rights reserved. 7 | // 8 | 9 | #import "client_reporting.h" 10 | 11 | @implementation client_reporting 12 | 13 | static NSString* mdmAddress = @""; 14 | static BOOL doPause = NO; 15 | 16 | +(void) setHostAddress: (NSString*) host { 17 | mdmAddress = [NSString stringWithFormat:@"https://%@/reporting/?type=", host]; 18 | } 19 | 20 | +(void) setPause : (BOOL) toggle { 21 | doPause = toggle; 22 | } 23 | 24 | +(void) makeCall : (NSString*) urlStr { 25 | NSURL* url = [NSURL URLWithString:urlStr]; 26 | [NSURLRequest requestWithURL:url]; 27 | if(doPause) [NSThread sleepForTimeInterval:2.5]; 28 | } 29 | 30 | +(void) reportJailbreak { 31 | [self makeCall:[NSString stringWithFormat:@"%@jailbreak", mdmAddress]]; 32 | } 33 | 34 | +(void) reportDebugger { 35 | [self makeCall:[NSString stringWithFormat:@"%@debugger", mdmAddress]]; 36 | } 37 | 38 | +(void) reportLocation : (CLLocationCoordinate2D*) coords { 39 | NSString *latitude = [NSString stringWithFormat:@"%f", coords->latitude]; 40 | NSString *longitude = [NSString stringWithFormat:@"%f", coords->longitude]; 41 | 42 | [self makeCall:[NSString stringWithFormat:@"%@location&lat=%@&lon=%@", mdmAddress, latitude, longitude]]; 43 | } 44 | 45 | 46 | 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /server/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2012, Intrepidus Group 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | Neither the name of the authors, nor Intrepidus Group, nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 22 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | POSSIBILITY OF SUCH DAMAGE. 29 | 30 | -------------------------------------------------------------------------------- /server/README: -------------------------------------------------------------------------------- 1 | For more documentation, please see the README file located in the root directory of this repository. 2 | 3 | 4 | -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 5 | Important Files 6 | -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 7 | 8 | 9 | [required, included] 10 | favicon.ico Replace with your website's icon 11 | server.py The server itself 12 | device.py A device class to support the server 13 | xactn.log Log of commands and responses [initially empty] 14 | 15 | certs/make_certs.sh Script to create the various certs and 16 | keys you'll need (uses OpenSSL) 17 | 18 | [required, need to get/create manually] 19 | PushCert.pem Certificate and private key (no passphrase) for APNS 20 | See README in root directory for more instructions 21 | Enroll.mobileconfig Use IPCU to create profile with MDM payload, 22 | used to enroll devices 23 | 24 | 25 | [required, created by make_cert.sh] 26 | CA.crt CA certificate used to sign the server cert 27 | [load onto device] 28 | Server.key Private key (no passphrase) for SSL server 29 | Server.crt Certificate for SSL server 30 | Identity.p12 Device identity cert (for MDM enrollment profile) 31 | 32 | 33 | [optional, to test installing custom apps] 34 | Example.mobileconfig Sample profile to install (disables certain apps, etc.) 35 | MyApp.ipa Bundle for a custom iOS app 36 | MyApp.mobileprovision Mobile provisioning profile for the custom app 37 | Manifest.plist Simple manifest for custom app 38 | [Manifest.plist.template provided] 39 | -------------------------------------------------------------------------------- /server/static/dist/js/populate.js: -------------------------------------------------------------------------------- 1 | // Global variable to avoid changing device list if there are no changes to it 2 | var checksum = '' 3 | 4 | function populate_devices(){ 5 | // Populates device accordion using data from server 6 | $.post("/devices", function(raw){ 7 | 8 | // Check if any changes have been made 9 | if(checksum == JSON.stringify(raw)){ 10 | return; 11 | } 12 | else{ 13 | checksum = JSON.stringify(raw); 14 | } 15 | 16 | // Use mustache to populate the accordion 17 | $.get("devices.mustache.html", function(template){ 18 | var rendered = Mustache.render(template, jQuery.parseJSON(raw)); 19 | $('#accordion').html(rendered); 20 | 21 | // Set response button on click to update the modal 22 | $(".response-btn").click(function(event){ 23 | // Get device UDID and command UUID 24 | var udid = $(this).closest(".panel").attr("id"); 25 | var uuid = $(this).attr("id"); 26 | 27 | // Access /response to get the response string 28 | $.post("/response", JSON.stringify({"UDID":udid, "UUID":uuid}), function(data){ 29 | $("#modal_body").html(data); 30 | }); 31 | }); 32 | 33 | // Puts HTML content inside the popover 34 | $(".trigger").popover({ 35 | html: true, 36 | title: function () { 37 | return $(this).parent().find('.head').html(); 38 | }, 39 | content: function () { 40 | return $(this).parent().find('.content').html(); 41 | } 42 | }); 43 | 44 | // Submit input group when submit button clicked 45 | $('body').on('click', '.popover-submit', function(){ 46 | // Get UDID and form values 47 | var udid = $(this).attr("id"); 48 | var name = $(this).parent().find("#input-name").val(); 49 | var owner = $(this).parent().find("#owner-name").val(); 50 | var location = $(this).parent().find("#location-name").val(); 51 | 52 | // POST data to /metadata endpoint 53 | $.post("/metadata", JSON.stringify({"UDID":udid,"name":name,"owner":owner,"location":location}), function(){}); 54 | 55 | $("#metadatapopover".concat(udid)).popover("hide"); 56 | }); 57 | 58 | }); 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /server/Example.mobileconfig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PayloadContent 6 | 7 | 8 | PayloadDescription 9 | Configures device restrictions. 10 | PayloadDisplayName 11 | Restrictions 12 | PayloadIdentifier 13 | com.intrepidusgroup.djs.test.simple.restrictions 14 | PayloadOrganization 15 | 16 | PayloadType 17 | com.apple.applicationaccess 18 | PayloadUUID 19 | 77D2FFB1-C010-4220-A3D9-BFE4B3EF99FD 20 | PayloadVersion 21 | 1 22 | allowAddingGameCenterFriends 23 | 24 | allowAppInstallation 25 | 26 | allowCamera 27 | 28 | allowExplicitContent 29 | 30 | allowGlobalBackgroundFetchWhenRoaming 31 | 32 | allowInAppPurchases 33 | 34 | allowMultiplayerGaming 35 | 36 | allowSafari 37 | 38 | allowScreenShot 39 | 40 | allowVideoConferencing 41 | 42 | allowVoiceDialing 43 | 44 | allowYouTube 45 | 46 | allowiTunes 47 | 48 | forceEncryptedBackup 49 | 50 | ratingApps 51 | 1000 52 | ratingMovies 53 | 1000 54 | ratingRegion 55 | us 56 | ratingTVShows 57 | 1000 58 | 59 | 60 | PayloadDescription 61 | Very simple profile to test restrictions. 62 | PayloadDisplayName 63 | Very Simple Restriction Test 64 | PayloadIdentifier 65 | com.intrepidusgroup.djs.test.simple 66 | PayloadOrganization 67 | 68 | PayloadRemovalDisallowed 69 | 70 | PayloadType 71 | Configuration 72 | PayloadUUID 73 | 2D9490F4-E890-4B37-9062-7095FEF0E482 74 | PayloadVersion 75 | 1 76 | 77 | 78 | -------------------------------------------------------------------------------- /server/static/devices.mustache.html: -------------------------------------------------------------------------------- 1 | {{#devices}} 2 |
3 |
4 |

5 |
6 | 7 |
8 | 9 |
10 |
{{name}}
11 |
{{owner}}
12 |
{{location}}
13 |
14 | 15 | 16 | 17 |
18 |
Edit Metadata:
19 |
20 |
21 | Device name: 22 | Device owner: 23 | Assigned location: 24 |
25 | 26 | 27 |
28 |
29 |
30 |
31 | 32 |

33 |
34 |
35 |
36 |
37 |
UDID:
{{UDID}}
38 |
IP:
{{ip}}
39 |
{{geo}}
40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | {{#commands}} 52 | 53 | 54 | 55 | 56 | 57 | {{/commands}} 58 | 59 |
StatusCommandOptions
{{cmd.Command.RequestType}}
60 |
61 |
62 | {{/devices}} 63 | -------------------------------------------------------------------------------- /server/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Open Source MDM Server 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 |
32 |

Open Source Mobile Device Management Server

33 |

Choose a command to send to one or more enrolled devices.

34 |
35 | 36 |
37 |
38 |
39 | 43 |
44 | 45 |
46 | 47 |
48 | 49 | 50 |
51 |
52 | 53 |
54 |
55 | 56 |


57 | 58 |
59 | 60 | 61 | 62 | 78 |
79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /vendor/mdm_vendor_sign.py: -------------------------------------------------------------------------------- 1 | # This is based loosely on Softthinker's java code found here 2 | # http://www.softhinker.com/in-the-news/iosmdmvendorcsrsigning 3 | 4 | import argparse 5 | from plistlib import writePlistToString 6 | import os 7 | import subprocess 8 | from base64 import b64encode 9 | import sys 10 | import urllib2 11 | 12 | def p(s): 13 | sys.stdout.write(s) 14 | sys.stdout.flush() 15 | 16 | def mdm_vendor_sign(): 17 | """ 18 | This utility will create a properly encoded certifiate signing request 19 | that you can upload to identity.apple.com/pushcert 20 | """ 21 | 22 | parser = argparse.ArgumentParser(description=mdm_vendor_sign.__doc__) 23 | parser.add_argument('--key', help='Private key', required=True) 24 | parser.add_argument('--csr', help='Certificate signing request', required=True) 25 | parser.add_argument('--mdm', help='MDM vendor certificate', required=True) 26 | parser.add_argument('--out', help='Output filename', required=False) 27 | 28 | 29 | cli_args = vars(parser.parse_args()) 30 | 31 | # Verify CSR 32 | # openssl req -text -noout -verify -in CSR.csr 33 | p('Verifying %s ... ' % cli_args['csr']) 34 | csr_file = open(cli_args['csr']).read() 35 | args = ['openssl', 'req', '-noout', '-verify' ] 36 | command = subprocess.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) 37 | output, error = command.communicate(input = csr_file) 38 | if output.rstrip().split('\n')[0] == 'verify OK': 39 | p('OK\n') 40 | else: 41 | p('FAILED\n') 42 | return 43 | 44 | 45 | # Verify private key 46 | # openssl rsa -in privateKey.key -check 47 | p('Verifying %s ... ' % cli_args['key']) 48 | key_file = open(cli_args['key']).read() 49 | args = ['openssl', 'rsa', '-check', '-noout' ] 50 | command = subprocess.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) 51 | output, error = command.communicate(input = key_file) 52 | if output.rstrip().split('\n')[0] == 'RSA key ok': 53 | p('OK\n') 54 | else: 55 | p('FAILED\n\n') 56 | print """If you don't have the plain private key already, you need 57 | to extract it from the pkcs12 file... 58 | 59 | First convert to PEM 60 | openssl pkcs12 -in filename.p12 -nocerts -out key.pem 61 | 62 | Then export the certificate file from the pfx file 63 | openssl pkcs12 -in filename.pfx -clcerts -nokeys -out cert.pem 64 | 65 | Lastly Remove the passphrase from the private key 66 | openssl rsa -in key.pem -out the_private_key.key 67 | """ 68 | return 69 | 70 | 71 | # Verify MDM vendor certificate 72 | # openssl x509 -noout -in mdm.cer -inform DER 73 | p('Verifying %s ... ' % cli_args['mdm']) 74 | mdm_cert_file = open(cli_args['mdm']).read() 75 | args = ['openssl', 'x509', '-noout', '-inform', 'DER' ] 76 | command = subprocess.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) 77 | output, error = command.communicate(input = mdm_cert_file) 78 | if len(output) == 0: 79 | p('OK\n') 80 | else: 81 | p('FAILED\n') 82 | return 83 | 84 | 85 | # Convert CSR to DER format 86 | # openssl req -inform pem -outform der -in customer.csr -out customer.der 87 | p('Converting %s to DER format... ' % cli_args['csr']) 88 | args = ['openssl', 'req', '-inform', 'pem', '-outform', 'der' ] 89 | command = subprocess.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) 90 | output, error = command.communicate(input = csr_file) 91 | if error: 92 | p('FAILED\n') 93 | return 94 | p('OK\n') 95 | csr_der = output 96 | csr_b64 = b64encode(csr_der) 97 | 98 | 99 | # Sign the CSR with the private key 100 | # openssl sha1 -sign private_key.key -out signed_output.rsa data_to_sign.txt 101 | p('Signing CSR with private key... ') 102 | args = ['openssl', 'sha1', '-sign', cli_args['key'] ] 103 | command = subprocess.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) 104 | output, error = command.communicate(input = csr_der) 105 | if error: 106 | p('FAILED\n') 107 | return 108 | p('OK\n') 109 | signature_bytes = output 110 | signature = b64encode(signature_bytes) 111 | 112 | 113 | def cer_to_pem(cer_data): 114 | # openssl x509 -inform der -in mdm.cer -out mdm.pem 115 | # -in and -out flags are handled by STDIN and STDOUT 116 | args = ['openssl', 'x509', '-inform', 'der' ] 117 | command = subprocess.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) 118 | output, error = command.communicate(input = cer_data) 119 | if error: 120 | p('Error converting from cer to pem: %s' % error) 121 | return output 122 | 123 | 124 | # TODO : Probably should verify these too 125 | 126 | p('Downloading WWDR intermediate certificate...') 127 | intermediate_cer = urllib2.urlopen('https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer').read() 128 | p(' converting to pem...') 129 | intermediate_pem = cer_to_pem(intermediate_cer) 130 | p('OK\n') 131 | 132 | p('Downloading Apple Root Certificate...') 133 | root_cer = urllib2.urlopen('http://www.apple.com/appleca/AppleIncRootCertificate.cer').read() 134 | p(' converting to pem...') 135 | root_pem = cer_to_pem(root_cer) 136 | p('OK\n') 137 | 138 | mdm_pem = cer_to_pem(mdm_cert_file) 139 | 140 | p('Finishing...') 141 | plist_dict = dict( 142 | PushCertRequestCSR = csr_b64, 143 | PushCertCertificateChain = mdm_pem + intermediate_pem + root_pem, 144 | PushCertSignature = signature 145 | ) 146 | plist_xml = writePlistToString(plist_dict) 147 | plist_b64 = b64encode(plist_xml) 148 | 149 | output_filename = cli_args['out'] if cli_args['out'] else 'plist_encoded' 150 | write_path = os.path.join(os.getcwd(), output_filename) 151 | output = open(write_path, 'wb') 152 | output.write(plist_b64) 153 | output.close() 154 | p('DONE\n\nGo upload file \'%s\' to identity.apple.com/pushcert !\n' % output_filename) 155 | 156 | 157 | 158 | if __name__=="__main__": 159 | mdm_vendor_sign() 160 | -------------------------------------------------------------------------------- /server/static/dist/js/mustache.min.js: -------------------------------------------------------------------------------- 1 | (function(root,factory){if(typeof exports==="object"&&exports){module.exports=factory}else if(typeof define==="function"&&define.amd){define(factory)}else{root.Mustache=factory}})(this,function(){var exports={};exports.name="mustache.js";exports.version="0.7.2";exports.tags=["{{","}}"];exports.Scanner=Scanner;exports.Context=Context;exports.Writer=Writer;var whiteRe=/\s*/;var spaceRe=/\s+/;var nonSpaceRe=/\S/;var eqRe=/\s*=/;var curlyRe=/\s*\}/;var tagRe=/#|\^|\/|>|\{|&|=|!/;function testRe(re,string){return RegExp.prototype.test.call(re,string)}function isWhitespace(string){return!testRe(nonSpaceRe,string)}var isArray=Array.isArray||function(obj){return Object.prototype.toString.call(obj)==="[object Array]"};function escapeRe(string){return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};function escapeHtml(string){return String(string).replace(/[&<>"'\/]/g,function(s){return entityMap[s]})}exports.escape=escapeHtml;function Scanner(string){this.string=string;this.tail=string;this.pos=0}Scanner.prototype.eos=function(){return this.tail===""};Scanner.prototype.scan=function(re){var match=this.tail.match(re);if(match&&match.index===0){this.tail=this.tail.substring(match[0].length);this.pos+=match[0].length;return match[0]}return""};Scanner.prototype.scanUntil=function(re){var match,pos=this.tail.search(re);switch(pos){case-1:match=this.tail;this.pos+=this.tail.length;this.tail="";break;case 0:match="";break;default:match=this.tail.substring(0,pos);this.tail=this.tail.substring(pos);this.pos+=pos}return match};function Context(view,parent){this.view=view;this.parent=parent;this.clearCache()}Context.make=function(view){return view instanceof Context?view:new Context(view)};Context.prototype.clearCache=function(){this._cache={}};Context.prototype.push=function(view){return new Context(view,this)};Context.prototype.lookup=function(name){var value=this._cache[name];if(!value){if(name==="."){value=this.view}else{var context=this;while(context){if(name.indexOf(".")>0){var names=name.split("."),i=0;value=context.view;while(value&&i":buffer+=writer._partial(token[1],context);break;case"&":buffer+=writer._name(token[1],context);break;case"name":buffer+=writer._escaped(token[1],context);break;case"text":buffer+=token[1];break}}return buffer}}function nestTokens(tokens){var tree=[];var collector=tree;var sections=[];var token;for(var i=0,len=tokens.length;i0?sections[sections.length-1][4]:tree;break;default:collector.push(token)}}return tree}function squashTokens(tokens){var squashedTokens=[];var token,lastToken;for(var i=0,len=tokens.length;i 0: 202 | for cmd in self.queue: 203 | if now - cmd['TimeStamp'] > self.TIMEOUT: 204 | # Command has time out, add it to cmd list with an error 205 | self.status = 2 206 | self.queue.remove(cmd) 207 | self.cmdList[cmd['CommandUUID']] = {} 208 | self.cmdList[cmd['CommandUUID']]['cmd'] = cmd 209 | self.cmdList[cmd['CommandUUID']]['response'] = {'Status':'TimeoutError'} 210 | self.cmdList[cmd['CommandUUID']]['status'] = 'danger' 211 | self.cmdList[cmd['CommandUUID']]['order'] = len(self.cmdList.keys()) 212 | return 213 | 214 | # Check command list for timed out commands 215 | for commandUUID in self.cmdList: 216 | if self.cmdList[commandUUID]['response'] == "" and now-self.cmdList[commandUUID]['cmd']['TimeStamp'] > self.TIMEOUT: 217 | self.status = 2 218 | self.cmdList[command['cmd']['CommandUUID']]['status'] = 'danger' 219 | self.cmdList[command['cmd']['CommandUUID']]['response'] = {'Status':'TimeoutError'} 220 | -------------------------------------------------------------------------------- /server/static/dist/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.1.1 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | .btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn:active,.btn.active{background-image:none}.btn-default{background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;text-shadow:0 1px 0 #fff;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-primary{background-image:-webkit-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:linear-gradient(to bottom,#428bca 0,#2d6ca2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#2b669a}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-color:#e8e8e8}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-color:#357ebd}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f3f3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#222 0,#282828 100%);background-image:linear-gradient(to bottom,#222 0,#282828 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0)}.progress-bar{background-image:-webkit-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0)}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0)}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0)}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0)}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);border-color:#3278b3}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0)}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /server/static/dist/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.1.1 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | .btn-default, 8 | .btn-primary, 9 | .btn-success, 10 | .btn-info, 11 | .btn-warning, 12 | .btn-danger { 13 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); 14 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 15 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 16 | } 17 | .btn-default:active, 18 | .btn-primary:active, 19 | .btn-success:active, 20 | .btn-info:active, 21 | .btn-warning:active, 22 | .btn-danger:active, 23 | .btn-default.active, 24 | .btn-primary.active, 25 | .btn-success.active, 26 | .btn-info.active, 27 | .btn-warning.active, 28 | .btn-danger.active { 29 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 30 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 31 | } 32 | .btn:active, 33 | .btn.active { 34 | background-image: none; 35 | } 36 | .btn-default { 37 | text-shadow: 0 1px 0 #fff; 38 | background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); 39 | background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); 40 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); 41 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 42 | background-repeat: repeat-x; 43 | border-color: #dbdbdb; 44 | border-color: #ccc; 45 | } 46 | .btn-default:hover, 47 | .btn-default:focus { 48 | background-color: #e0e0e0; 49 | background-position: 0 -15px; 50 | } 51 | .btn-default:active, 52 | .btn-default.active { 53 | background-color: #e0e0e0; 54 | border-color: #dbdbdb; 55 | } 56 | .btn-primary { 57 | background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%); 58 | background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%); 59 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0); 60 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 61 | background-repeat: repeat-x; 62 | border-color: #2b669a; 63 | } 64 | .btn-primary:hover, 65 | .btn-primary:focus { 66 | background-color: #2d6ca2; 67 | background-position: 0 -15px; 68 | } 69 | .btn-primary:active, 70 | .btn-primary.active { 71 | background-color: #2d6ca2; 72 | border-color: #2b669a; 73 | } 74 | .btn-success { 75 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); 76 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); 77 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); 78 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 79 | background-repeat: repeat-x; 80 | border-color: #3e8f3e; 81 | } 82 | .btn-success:hover, 83 | .btn-success:focus { 84 | background-color: #419641; 85 | background-position: 0 -15px; 86 | } 87 | .btn-success:active, 88 | .btn-success.active { 89 | background-color: #419641; 90 | border-color: #3e8f3e; 91 | } 92 | .btn-info { 93 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 94 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); 95 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); 96 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 97 | background-repeat: repeat-x; 98 | border-color: #28a4c9; 99 | } 100 | .btn-info:hover, 101 | .btn-info:focus { 102 | background-color: #2aabd2; 103 | background-position: 0 -15px; 104 | } 105 | .btn-info:active, 106 | .btn-info.active { 107 | background-color: #2aabd2; 108 | border-color: #28a4c9; 109 | } 110 | .btn-warning { 111 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 112 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); 113 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); 114 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 115 | background-repeat: repeat-x; 116 | border-color: #e38d13; 117 | } 118 | .btn-warning:hover, 119 | .btn-warning:focus { 120 | background-color: #eb9316; 121 | background-position: 0 -15px; 122 | } 123 | .btn-warning:active, 124 | .btn-warning.active { 125 | background-color: #eb9316; 126 | border-color: #e38d13; 127 | } 128 | .btn-danger { 129 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 130 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); 131 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); 132 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 133 | background-repeat: repeat-x; 134 | border-color: #b92c28; 135 | } 136 | .btn-danger:hover, 137 | .btn-danger:focus { 138 | background-color: #c12e2a; 139 | background-position: 0 -15px; 140 | } 141 | .btn-danger:active, 142 | .btn-danger.active { 143 | background-color: #c12e2a; 144 | border-color: #b92c28; 145 | } 146 | .thumbnail, 147 | .img-thumbnail { 148 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 149 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 150 | } 151 | .dropdown-menu > li > a:hover, 152 | .dropdown-menu > li > a:focus { 153 | background-color: #e8e8e8; 154 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 155 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 156 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 157 | background-repeat: repeat-x; 158 | } 159 | .dropdown-menu > .active > a, 160 | .dropdown-menu > .active > a:hover, 161 | .dropdown-menu > .active > a:focus { 162 | background-color: #357ebd; 163 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); 164 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 165 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 166 | background-repeat: repeat-x; 167 | } 168 | .navbar-default { 169 | background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); 170 | background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); 171 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 172 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 173 | background-repeat: repeat-x; 174 | border-radius: 4px; 175 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 176 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 177 | } 178 | .navbar-default .navbar-nav > .active > a { 179 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%); 180 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%); 181 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0); 182 | background-repeat: repeat-x; 183 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 184 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 185 | } 186 | .navbar-brand, 187 | .navbar-nav > li > a { 188 | text-shadow: 0 1px 0 rgba(255, 255, 255, .25); 189 | } 190 | .navbar-inverse { 191 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); 192 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); 193 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 194 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 195 | background-repeat: repeat-x; 196 | } 197 | .navbar-inverse .navbar-nav > .active > a { 198 | background-image: -webkit-linear-gradient(top, #222 0%, #282828 100%); 199 | background-image: linear-gradient(to bottom, #222 0%, #282828 100%); 200 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0); 201 | background-repeat: repeat-x; 202 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 203 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 204 | } 205 | .navbar-inverse .navbar-brand, 206 | .navbar-inverse .navbar-nav > li > a { 207 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); 208 | } 209 | .navbar-static-top, 210 | .navbar-fixed-top, 211 | .navbar-fixed-bottom { 212 | border-radius: 0; 213 | } 214 | .alert { 215 | text-shadow: 0 1px 0 rgba(255, 255, 255, .2); 216 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 217 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 218 | } 219 | .alert-success { 220 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 221 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 222 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 223 | background-repeat: repeat-x; 224 | border-color: #b2dba1; 225 | } 226 | .alert-info { 227 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 228 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 229 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 230 | background-repeat: repeat-x; 231 | border-color: #9acfea; 232 | } 233 | .alert-warning { 234 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 235 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 236 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 237 | background-repeat: repeat-x; 238 | border-color: #f5e79e; 239 | } 240 | .alert-danger { 241 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 242 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 243 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 244 | background-repeat: repeat-x; 245 | border-color: #dca7a7; 246 | } 247 | .progress { 248 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 249 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 250 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 251 | background-repeat: repeat-x; 252 | } 253 | .progress-bar { 254 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%); 255 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); 256 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); 257 | background-repeat: repeat-x; 258 | } 259 | .progress-bar-success { 260 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 261 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 262 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 263 | background-repeat: repeat-x; 264 | } 265 | .progress-bar-info { 266 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 267 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 268 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 269 | background-repeat: repeat-x; 270 | } 271 | .progress-bar-warning { 272 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 273 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 274 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 275 | background-repeat: repeat-x; 276 | } 277 | .progress-bar-danger { 278 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 279 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 280 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 281 | background-repeat: repeat-x; 282 | } 283 | .list-group { 284 | border-radius: 4px; 285 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 286 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 287 | } 288 | .list-group-item.active, 289 | .list-group-item.active:hover, 290 | .list-group-item.active:focus { 291 | text-shadow: 0 -1px 0 #3071a9; 292 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%); 293 | background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%); 294 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0); 295 | background-repeat: repeat-x; 296 | border-color: #3278b3; 297 | } 298 | .panel { 299 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 300 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 301 | } 302 | .panel-default > .panel-heading { 303 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 304 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 305 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 306 | background-repeat: repeat-x; 307 | } 308 | .panel-primary > .panel-heading { 309 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); 310 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 311 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 312 | background-repeat: repeat-x; 313 | } 314 | .panel-success > .panel-heading { 315 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 316 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 317 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 318 | background-repeat: repeat-x; 319 | } 320 | .panel-info > .panel-heading { 321 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 322 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 323 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 324 | background-repeat: repeat-x; 325 | } 326 | .panel-warning > .panel-heading { 327 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 328 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 329 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 330 | background-repeat: repeat-x; 331 | } 332 | .panel-danger > .panel-heading { 333 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 334 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 335 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 336 | background-repeat: repeat-x; 337 | } 338 | .well { 339 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 340 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 341 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 342 | background-repeat: repeat-x; 343 | border-color: #dcdcdc; 344 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 345 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 346 | } 347 | /*# sourceMappingURL=bootstrap-theme.css.map */ 348 | -------------------------------------------------------------------------------- /server/server.py: -------------------------------------------------------------------------------- 1 | import web, os, json, uuid, sys 2 | import cPickle as pickle 3 | from device import device # Custom device class 4 | from plistlib import * 5 | from APNSWrapper import * 6 | from problems import * 7 | from datetime import datetime 8 | # needed to handle verification of signed messages from devices 9 | from M2Crypto import SMIME, X509, BIO 10 | 11 | # 12 | # Simple, basic, bare-bones example test server 13 | # Implements Apple's Mobile Device Management (MDM) protocol 14 | # Compatible with iOS 4.x devices 15 | # 16 | # 17 | # David Schuetz, Senior Consultant, Intrepidus Group 18 | # 19 | # Copyright 2011, Intrepidus Group 20 | # http://intrepidusgroup.com 21 | 22 | # Reuse permitted under terms of BSD License (see LICENSE file). 23 | # No warranties, expressed or implied. 24 | # This is experimental software, for research only. Use at your own risk. 25 | 26 | # 27 | # Revision History: 28 | # 29 | # * August 2011 - initial release, Black Hat USA 30 | # * January 2012 - minor tweaks, including favicon, useful README, and 31 | # scripts to create certs, log file, etc. 32 | # * January 2012 - Added support for some iOS 5 functions. ShmooCon 8. 33 | # * February 2012 - Can now verify signed messages from devices 34 | # - Tweaks to CherryPy startup to avoid errors on console 35 | # * January 2014 - Support for multiple enrollments 36 | # - Supports reporting problems 37 | # * April 2014 - Support for new front end 38 | # - Tweaks and bug fixes 39 | # * May 2014 - New device class 40 | # - Rework server to use device class 41 | # - Fixes a number of problems with using multiple devices 42 | # - Support for new device-based front end 43 | 44 | 45 | # Global variable setup 46 | LOGFILE = 'xactn.log' 47 | 48 | # Dummy socket to get the hostname 49 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 50 | s.connect(('8.8.8.8', 0)) 51 | 52 | # NOTE: Will need to overwrite this if behind a firewall 53 | MY_ADDR = s.getsockname()[0] + ":8080" 54 | 55 | # Set up some smime objects to verify signed messages coming from devices 56 | sm_obj = SMIME.SMIME() 57 | x509 = X509.load_cert('identity.crt') 58 | sk = X509.X509_Stack() 59 | sk.push(x509) 60 | sm_obj.set_x509_stack(sk) 61 | 62 | st = X509.X509_Store() 63 | st.load_info('CA.crt') 64 | sm_obj.set_x509_store(st) 65 | 66 | 67 | ########################################################################### 68 | # Update this to match the UUID in the test provisioning profiles, in order 69 | # to demonstrate removal of the profile 70 | 71 | my_test_provisioning_uuid = 'REPLACE-ME-WITH-REAL-UUIDSTRING' 72 | 73 | from web.wsgiserver import CherryPyWSGIServer 74 | 75 | # Python 2.7 requires the PyOpenSSL library 76 | # Python 3.x should use be able to use the default python SSL 77 | try: 78 | from OpenSSL import SSL 79 | from OpenSSL import crypto 80 | except ImportError: 81 | SSL = None 82 | 83 | 84 | CherryPyWSGIServer.ssl_certificate = "Server.crt" 85 | CherryPyWSGIServer.ssl_private_key = "Server.key" 86 | 87 | ########################################################################### 88 | 89 | device_list = dict() 90 | 91 | global mdm_commands 92 | 93 | urls = ( 94 | '/', 'root', 95 | '/queue', 'queue_cmd_post', 96 | '/checkin', 'do_mdm', 97 | '/server', 'do_mdm', 98 | '/ServerURL', 'do_mdm', 99 | '/CheckInURL', 'do_mdm', 100 | '/enroll', 'enroll_profile', 101 | '/ca', 'mdm_ca', 102 | '/favicon.ico', 'favicon', 103 | '/manifest', 'app_manifest', 104 | '/app', 'app_ipa', 105 | '/problem', 'do_problem', 106 | '/problemjb', 'do_problem', 107 | '/poll', 'poll', 108 | '/getcommands', 'get_commands', 109 | '/devices', 'dev_tab', 110 | '/response', 'get_response', 111 | '/metadata', 'metadata', 112 | ) 113 | 114 | 115 | 116 | def setup_commands(): 117 | # Function to generate dictionary of valid commands 118 | global my_test_provisioning_uuid 119 | 120 | ret_list = dict() 121 | 122 | for cmd in ['DeviceLock', 'ProfileList', 'Restrictions', 123 | 'CertificateList', 'InstalledApplicationList', 124 | 'ProvisioningProfileList', 125 | # new for iOS 5: 126 | 'ManagedApplicationList',]: 127 | ret_list[cmd] = dict( Command = dict( RequestType = cmd )) 128 | 129 | ret_list['SecurityInfo'] = dict( 130 | Command = dict( 131 | RequestType = 'SecurityInfo', 132 | Queries = [ 133 | 'HardwareEncryptionCaps', 'PasscodePresent', 134 | 'PasscodeCompliant', 'PasscodeCompliantWithProfiles', 135 | ] 136 | ) 137 | ) 138 | 139 | ret_list['DeviceInformation'] = dict( 140 | Command = dict( 141 | RequestType = 'DeviceInformation', 142 | Queries = [ 143 | 'AvailableDeviceCapacity', 'BluetoothMAC', 'BuildVersion', 144 | 'CarrierSettingsVersion', 'CurrentCarrierNetwork', 145 | 'CurrentMCC', 'CurrentMNC', 'DataRoamingEnabled', 146 | 'DeviceCapacity', 'DeviceName', 'ICCID', 'IMEI', 'IsRoaming', 147 | 'Model', 'ModelName', 'ModemFirmwareVersion', 'OSVersion', 148 | 'PhoneNumber', 'Product', 'ProductName', 'SIMCarrierNetwork', 149 | 'SIMMCC', 'SIMMNC', 'SerialNumber', 'UDID', 'WiFiMAC', 'UDID', 150 | 'UnlockToken', 'MEID', 'CellularTechnology', 'BatteryLevel', 151 | 'SubscriberCarrierNetwork', 'VoiceRoamingEnabled', 152 | 'SubscriberMCC', 'SubscriberMNC', 'DataRoaming', 'VoiceRoaming', 153 | 'JailbreakDetected' 154 | ] 155 | ) 156 | ) 157 | 158 | ret_list['ClearPasscode'] = dict( 159 | Command = dict( 160 | RequestType = 'ClearPasscode', 161 | # When ClearPasscode is used, the device specific unlock token needs to be added 162 | # UnlockToken = Data(my_UnlockToken) 163 | ) 164 | ) 165 | 166 | # commented out, and command string changed, to avoid accidentally 167 | # erasing test devices. 168 | # 169 | # ret_list['EraseDevice'] = dict( 170 | # Command = dict( 171 | # RequestType = 'DONT_EraseDevice', 172 | # ) 173 | # ) 174 | # 175 | if 'Example.mobileconfig' in os.listdir('.'): 176 | my_test_cfg_profile = open('Example.mobileconfig', 'rb').read() 177 | pl = readPlistFromString(my_test_cfg_profile) 178 | 179 | ret_list['InstallProfile'] = dict( 180 | Command = dict( 181 | RequestType = 'InstallProfile', 182 | Payload = Data(my_test_cfg_profile) 183 | ) 184 | ) 185 | 186 | ret_list['RemoveProfile'] = dict( 187 | Command = dict( 188 | RequestType = 'RemoveProfile', 189 | Identifier = pl['PayloadIdentifier'] 190 | ) 191 | ) 192 | 193 | else: 194 | print "Can't find Example.mobileconfig in current directory." 195 | 196 | 197 | if 'MyApp.mobileprovision' in os.listdir('.'): 198 | my_test_prov_profile = open('MyApp.mobileprovision', 'rb').read() 199 | 200 | ret_list['InstallProvisioningProfile'] = dict( 201 | Command = dict( 202 | RequestType = 'InstallProvisioningProfile', 203 | ProvisioningProfile = Data(my_test_prov_profile) 204 | ) 205 | ) 206 | 207 | ret_list['RemoveProvisioningProfile'] = dict( 208 | Command = dict( 209 | RequestType = 'RemoveProvisioningProfile', 210 | # need an ASN.1 parser to snarf the UUID out of the signed profile 211 | UUID = my_test_provisioning_uuid 212 | ) 213 | ) 214 | 215 | else: 216 | print "Can't find MyApp.mobileprovision in current directory." 217 | 218 | # 219 | # iOS 5: 220 | # 221 | ret_list['InstallApplication'] = dict( 222 | Command = dict( 223 | RequestType = 'InstallApplication', 224 | ManagementFlags = 4, # do not delete app when unenrolling from MDM 225 | iTunesStoreID=471966214, # iTunes Movie Trailers 226 | )) 227 | 228 | if ('MyApp.ipa' in os.listdir('.')) and ('Manifest.plist' in os.listdir('.')): 229 | ret_list['InstallCustomApp'] = dict( 230 | Command = dict( 231 | RequestType = 'InstallApplication', 232 | ManifestURL = 'https://%s/manifest' % MY_ADDR, 233 | ManagementFlags = 1, # delete app when unenrolling from MDM 234 | )) 235 | print ret_list['InstallCustomApp'] 236 | else: 237 | print "Need both MyApp.ipa and Manifest.plist to enable InstallCustomApp." 238 | 239 | 240 | ret_list['RemoveApplication'] = dict( 241 | Command = dict( 242 | RequestType = 'RemoveApplication', 243 | Identifier = 'com.apple.movietrailers', 244 | )) 245 | 246 | ret_list['RemoveCustomApplication'] = dict( 247 | Command = dict( 248 | RequestType = 'RemoveApplication', 249 | Identifier = 'mitre.managedTest', 250 | )) 251 | 252 | # 253 | # on an ipad, you'll likely get errors for the "VoiceRoaming" part. 254 | # Since, you know...it's not a phone. 255 | # 256 | ret_list['Settings'] = dict( 257 | Command = dict( 258 | RequestType = 'Settings', 259 | Settings = [ 260 | dict( 261 | Item = 'DataRoaming', 262 | Enabled = False, 263 | ), 264 | dict( 265 | Item = 'VoiceRoaming', 266 | Enabled = True, 267 | ), 268 | ] 269 | )) 270 | 271 | # 272 | # haven't figured out how to make this one work yet. :( 273 | # 274 | # ret_list['ApplyRedemptionCode'] = dict( 275 | # Command = dict( 276 | # RequestType = 'ApplyRedemptionCode', 277 | ## do I maybe need to add an iTunesStoreID in here? 278 | # RedemptionCode = '3WABCDEFGXXX', 279 | # iTunesStoreID=471966214, # iTunes Movie Trailers 280 | # ManagementFlags = 1, 281 | # )) 282 | 283 | 284 | 285 | return ret_list 286 | 287 | class root: 288 | def GET(self): 289 | return web.redirect("/static/index.html") 290 | 291 | def queue(cmd, dev_UDID): 292 | # Function to add a command to a device queue 293 | global device_list, mdm_commands 294 | 295 | mylocal_PushMagic, mylocal_DeviceToken = device_list[dev_UDID].getQueueInfo() 296 | 297 | cmd_data = mdm_commands[cmd] 298 | cmd_data['CommandUUID'] = str(uuid.uuid4()) 299 | 300 | # Have to search through device_list using pushmagic or devtoken to get UDID 301 | for key in device_list: 302 | if device_list[key].UDID == dev_UDID: 303 | device_list[key].addCommand(cmd_data) 304 | print "*Adding CMD:", cmd_data['CommandUUID'], "to device:", key 305 | break 306 | 307 | store_devices() 308 | 309 | 310 | # Send request to Apple 311 | wrapper = APNSNotificationWrapper('PushCert.pem', False) 312 | message = APNSNotification() 313 | message.token(mylocal_DeviceToken) 314 | message.appendProperty(APNSProperty('mdm', mylocal_PushMagic)) 315 | wrapper.append(message) 316 | wrapper.notify() 317 | 318 | 319 | 320 | class queue_cmd_post: 321 | def POST(self): 322 | global device_list 323 | 324 | i = json.loads(web.data()) 325 | cmd = i.pop("cmd", []) 326 | dev = i.pop("dev[]", []) 327 | 328 | for UDID in dev: 329 | queue(cmd, UDID) 330 | 331 | # Update page - currently not using update() 332 | #return update() 333 | return 334 | 335 | class do_mdm: 336 | def PUT(self): 337 | global sm_obj, device_list 338 | HIGH='' 339 | LOW='' 340 | NORMAL='' 341 | 342 | i = web.data() 343 | pl = readPlistFromString(i) 344 | 345 | if 'HTTP_MDM_SIGNATURE' in web.ctx.environ: 346 | raw_sig = web.ctx.environ['HTTP_MDM_SIGNATURE'] 347 | cooked_sig = '\n'.join(raw_sig[pos:pos+76] for pos in xrange(0, len(raw_sig), 76)) 348 | 349 | signature = '\n-----BEGIN PKCS7-----\n%s\n-----END PKCS7-----\n' % cooked_sig 350 | 351 | # Verify client signature - necessary? 352 | buf = BIO.MemoryBuffer(signature) 353 | p7 = SMIME.load_pkcs7_bio(buf) 354 | data_bio = BIO.MemoryBuffer(i) 355 | try: 356 | v = sm_obj.verify(p7, data_bio) 357 | if v: 358 | print "Client signature verified." 359 | except: 360 | print "*** INVALID CLIENT MESSAGE SIGNATURE ***" 361 | 362 | print "%sReceived %4d bytes: %s" % (HIGH, len(web.data()), NORMAL), 363 | 364 | if pl.get('Status') == 'Idle': 365 | print HIGH + "Idle Status" + NORMAL 366 | 367 | print "*FETCHING CMD TO BE SENT FROM DEVICE:", pl['UDID'] 368 | rd = device_list[pl['UDID']].sendCommand() 369 | 370 | # If no commands in queue, return empty string to avoid infinite idle loop 371 | if(not rd): 372 | return '' 373 | 374 | print "%sSent: %s%s" % (HIGH, rd['Command']['RequestType'], NORMAL) 375 | 376 | elif pl.get('MessageType') == 'TokenUpdate': 377 | print HIGH+"Token Update"+NORMAL 378 | rd = do_TokenUpdate(pl) 379 | print HIGH+"Device Enrolled!"+NORMAL 380 | 381 | elif pl.get('Status') == 'Acknowledged': 382 | print HIGH+"Acknowledged"+NORMAL 383 | rd = dict() 384 | # A command has returned a response 385 | # Add the response to the given device 386 | print "*CALLING ADD RESPONSE TO CMD:", pl['CommandUUID'] 387 | device_list[pl['UDID']].addResponse(pl['CommandUUID'], pl) 388 | 389 | # If we grab device information, we should also update the device info 390 | if pl.get('QueryResponses'): 391 | print "DeviceInformation should update here..." 392 | p = pl['QueryResponses'] 393 | device_list[pl['UDID']].updateInfo(p['DeviceName'], p['ModelName'], p['OSVersion']) 394 | 395 | # Update pickle file with new response 396 | store_devices() 397 | else: 398 | rd = dict() 399 | if pl.get('MessageType') == 'Authenticate': 400 | print HIGH+"Authenticate"+NORMAL 401 | elif pl.get('MessageType') == 'CheckOut': 402 | print HIGH+"Device leaving MDM"+ NORMAL 403 | elif pl.get('Status') == 'Error': 404 | print "*CALLING ADD RESPONSE WITH ERROR TO CMD:", pl['CommandUUID'] 405 | device_list[pl['UDID']].addResponse(pl['CommandUUID'], pl) 406 | else: 407 | print HIGH+"(other)"+NORMAL 408 | print HIGH, pl, NORMAL 409 | log_data(pl) 410 | log_data(rd) 411 | 412 | out = writePlistToString(rd) 413 | #print LOW, out, NORMAL 414 | 415 | return out 416 | 417 | class get_commands: 418 | def POST(self): 419 | # Function to return static list of commands to the front page 420 | # Should be called once by document.ready 421 | global mdm_commands 422 | 423 | drop_list = [] 424 | for key in sorted(mdm_commands.iterkeys()): 425 | drop_list.append([key, key]) 426 | return json.dumps(drop_list) 427 | 428 | def update(): 429 | 430 | # DEPRICATED 431 | # Current polling endpoint is /devices 432 | # May be updated later on for intelligent updating of devices 433 | 434 | # Function to update devices on the frontend 435 | # Is called on page load and polling 436 | 437 | global problems, device_list 438 | 439 | # Create list of devices 440 | dev_list_out = [] 441 | for UDID in device_list: 442 | dev_list_out.append([device_list[UDID].IP, device_list[UDID].pushMagic]) 443 | 444 | # Format output as a dict and then return as JSON 445 | out = dict() 446 | out['dev_list'] = dev_list_out 447 | out['problems'] = '
'.join(problems) 448 | 449 | return json.dumps(out) 450 | 451 | 452 | class poll: 453 | def POST(self): 454 | 455 | # DEPRICATED 456 | # Current polling endpoint is /devices 457 | # May be updated later on for intelligent updating of devices 458 | 459 | 460 | # Polling function to update page with new data 461 | return update() 462 | 463 | class get_response: 464 | def POST(self): 465 | # Endpoint to return a reponse given a UDID and command UUID 466 | global device_list 467 | 468 | i = json.loads(web.data()) 469 | try: 470 | return device_list[i['UDID']].getResponse(i['UUID']) 471 | except: 472 | print "ERROR: Unable to lookup response by command UUID" 473 | return "ERROR: Unable to retrieve response" 474 | 475 | 476 | class dev_tab: 477 | def POST(self): 478 | # Endpoint to return list of devices with a list of device info 479 | global device_list 480 | devices = [] 481 | 482 | for key in device_list: 483 | device_list[key].checkTimeout() 484 | devices.append(device_list[key].populate()) 485 | 486 | # A device-sorting functionality could happen here 487 | 488 | out = {} 489 | out['devices'] = devices 490 | 491 | # return JSON 492 | return json.dumps(out) 493 | 494 | class metadata: 495 | def POST(self): 496 | # Endpoint to update device metadata 497 | global device_list 498 | 499 | i = json.loads(web.data()) 500 | 501 | device_list[i['UDID']].updateMetadata(i['name'], i['owner'], i['location']) 502 | 503 | store_devices() 504 | 505 | return 506 | 507 | def store_devices(): 508 | # Function to convert the device list and write to a file 509 | global device_list 510 | 511 | print "STORING DEVICES..." 512 | 513 | # Use pickle to store list of devices 514 | pickle.dump(device_list, file('devicelist.pickle', 'w')) 515 | 516 | def read_devices(): 517 | # Function to open and read the device list 518 | # Is called when the server loads 519 | global device_list 520 | 521 | try: 522 | device_list = pickle.load(file('devicelist.pickle')) 523 | print "LOADED PICKLE" 524 | except: 525 | print "NO DATA IN PICKLE FILE or PICKLE FILE DOES NOT EXIST" 526 | # Creating new pickle file if need be 527 | open('devicelist.pickle', 'a').close() 528 | 529 | 530 | def do_TokenUpdate(pl): 531 | global mdm_commands 532 | 533 | my_PushMagic = pl['PushMagic'] 534 | my_DeviceToken = pl['Token'].data 535 | my_UnlockToken = pl['UnlockToken'].data 536 | 537 | newTuple = (web.ctx.ip, my_PushMagic, my_DeviceToken, my_UnlockToken) 538 | 539 | print "NEW DEVICE UDID:", pl.get('UDID') 540 | # A new device has enrolled, add a new device 541 | if pl.get('UDID') not in device_list: 542 | # Device does not already exist, create new instance of device 543 | device_list[pl.get('UDID')] = device(pl['UDID'], newTuple) 544 | print "ADDING DEVICE TO DEVICE_LIST" 545 | else: 546 | # Device exists, update information - token stays the same 547 | device_list[pl['UDID']].reenroll(web.ctx.ip, my_PushMagic, my_UnlockToken) 548 | print "DEVICE ALREADY EXISTS, UPDATE INFORMATION" 549 | 550 | 551 | # Queue a DeviceInformation command to populate fields in device_list 552 | # Is this command causing an off-by-one error with commands? 553 | queue('DeviceInformation', pl['UDID']) 554 | 555 | # Store devices in a file for persistence 556 | store_devices() 557 | 558 | # Return empty dictionary for use in do_mdm 559 | return dict() 560 | 561 | 562 | class enroll_profile: 563 | def GET(self): 564 | # Enroll an iPad/iPhone/iPod when requested 565 | if 'Enroll.mobileconfig' in os.listdir('.'): 566 | web.header('Content-Type', 'application/x-apple-aspen-config;charset=utf-8') 567 | web.header('Content-Disposition', 'attachment;filename="Enroll.mobileconfig"') 568 | return open('Enroll.mobileconfig', "rb").read() 569 | else: 570 | raise web.notfound() 571 | 572 | class do_problem: 573 | # DEBUG 574 | # DEPRICATED? 575 | # TODO: Problems may need to be reworked a bit 576 | # Stop storing in a .py file 577 | # What is the purpose of problems? Whats the end goal? 578 | def GET(self): 579 | global problems 580 | problem_detect = ' (' 581 | problem_detect += datetime.now().strftime("%Y-%m-%d %H:%M:%S") 582 | if web.ctx.path == "/problem": 583 | problem_detect += ') Debugger attached to ' 584 | elif web.ctx.path == "/problemjb": 585 | problem_detect += ') Jailbreak detected for ' 586 | problem_detect += web.ctx.ip 587 | 588 | problems.insert(0, problem_detect) 589 | out = "\nproblems = %s" % problems 590 | fd = open('problems.py', 'w') 591 | fd.write(out) 592 | fd.close() 593 | 594 | class mdm_ca: 595 | def GET(self): 596 | 597 | if 'CA.crt' in os.listdir('.'): 598 | web.header('Content-Type', 'application/octet-stream;charset=utf-8') 599 | web.header('Content-Disposition', 'attachment;filename="CA.crt"') 600 | return open('CA.crt', "rb").read() 601 | else: 602 | raise web.notfound() 603 | 604 | 605 | class favicon: 606 | def GET(self): 607 | if 'favicon.ico' in os.listdir('.'): 608 | web.header('Content-Type', 'image/x-icon;charset=utf-8') 609 | return open('favicon.ico', "rb").read() 610 | elif 'favicon.ico' in os.listdir('./static/'): 611 | web.header('Content-Type', 'image/x-icon;charset=utf-8') 612 | return open('/static/favicon.ico', "rb").read() 613 | else: 614 | raise web.ok 615 | 616 | 617 | class app_manifest: 618 | def GET(self): 619 | 620 | if 'Manifest.plist' in os.listdir('.'): 621 | web.header('Content-Type', 'text/xml;charset=utf-8') 622 | return open('Manifest.plist', "rb").read() 623 | else: 624 | raise web.notfound() 625 | 626 | 627 | class app_ipa: 628 | def GET(self): 629 | 630 | if 'MyApp.ipa' in os.listdir('.'): 631 | web.header('Content-Type', 'application/octet-stream;charset=utf-8') 632 | web.header('Content-Disposition', 'attachment;filename="MyApp.ipa"') 633 | return open('MyApp.ipa', "rb").read() 634 | else: 635 | return web.ok 636 | 637 | 638 | def log_data(out): 639 | fd = open(LOGFILE, "a") 640 | fd.write(datetime.now().ctime()) 641 | fd.write(" %s\n" % repr(out)) 642 | fd.close() 643 | 644 | 645 | if __name__ == "__main__": 646 | print "Starting Server" 647 | app = web.application(urls, globals()) 648 | app.internalerror = web.debugerror 649 | 650 | try: 651 | app.run() 652 | except: 653 | sys.exit(0) 654 | else: 655 | # app.run() seems to use server.py as a module 656 | # Placing these in main causes them not to run 657 | # Placing these above main causes them to run twice 658 | mdm_commands = setup_commands() 659 | read_devices() 660 | 661 | -------------------------------------------------------------------------------- /server/static/dist/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.1.1 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.isLoading=!1};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",f.resetText||d.data("resetText",d[e]()),d[e](f[b]||this.options[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},b.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});return this.$element.trigger(j),j.isDefaultPrevented()?void 0:(this.sliding=!0,f&&this.pause(),this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid.bs.carousel")},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid.bs.carousel")),f&&this.cycle(),this)};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("collapse in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);!e&&f.toggle&&"show"==c&&(c=!c),e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(b){a(d).remove(),a(e).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;(e||"destroy"!=c)&&(e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]())})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(a(c).is("body")?window:c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);{var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})}},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);if(g&&b<=e[0])return g!=(a=f[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parentsUntil(this.options.target,".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(b.RESET).addClass("affix");var a=this.$window.scrollTop(),c=this.$element.offset();return this.pinnedOffset=c.top-a},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"top"==this.affixed&&(e.top+=d),"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top(this.$element)),"function"==typeof h&&(h=f.bottom(this.$element));var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;if(this.affixed!==i){this.unpin&&this.$element.css("top","");var j="affix"+(i?"-"+i:""),k=a.Event(j+".bs.affix");this.$element.trigger(k),k.isDefaultPrevented()||(this.affixed=i,this.unpin="bottom"==i?this.getPinnedOffset():null,this.$element.removeClass(b.RESET).addClass(j).trigger(a.Event(j.replace("affix","affixed"))),"bottom"==i&&this.$element.offset({top:c-h-this.$element.height()}))}}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery); -------------------------------------------------------------------------------- /server/static/dist/css/bootstrap-theme.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["less/theme.less","less/mixins.less"],"names":[],"mappings":"AAeA;AACA;AACA;AACA;AACA;AACA;EACE,wCAAA;ECoGA,2FAAA;EACQ,mFAAA;;ADhGR,YAAC;AAAD,YAAC;AAAD,YAAC;AAAD,SAAC;AAAD,YAAC;AAAD,WAAC;AACD,YAAC;AAAD,YAAC;AAAD,YAAC;AAAD,SAAC;AAAD,YAAC;AAAD,WAAC;EC8FD,wDAAA;EACQ,gDAAA;;ADnER,IAAC;AACD,IAAC;EACC,sBAAA;;AAKJ;EC4PI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EAEA,sHAAA;EAoCF,mEAAA;ED7TA,2BAAA;EACA,qBAAA;EAyB2C,yBAAA;EAA2B,kBAAA;;AAvBtE,YAAC;AACD,YAAC;EACC,yBAAA;EACA,4BAAA;;AAGF,YAAC;AACD,YAAC;EACC,yBAAA;EACA,qBAAA;;AAeJ;EC2PI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EAEA,sHAAA;EAoCF,mEAAA;ED7TA,2BAAA;EACA,qBAAA;;AAEA,YAAC;AACD,YAAC;EACC,yBAAA;EACA,4BAAA;;AAGF,YAAC;AACD,YAAC;EACC,yBAAA;EACA,qBAAA;;AAgBJ;EC0PI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EAEA,sHAAA;EAoCF,mEAAA;ED7TA,2BAAA;EACA,qBAAA;;AAEA,YAAC;AACD,YAAC;EACC,yBAAA;EACA,4BAAA;;AAGF,YAAC;AACD,YAAC;EACC,yBAAA;EACA,qBAAA;;AAiBJ;ECyPI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EAEA,sHAAA;EAoCF,mEAAA;ED7TA,2BAAA;EACA,qBAAA;;AAEA,SAAC;AACD,SAAC;EACC,yBAAA;EACA,4BAAA;;AAGF,SAAC;AACD,SAAC;EACC,yBAAA;EACA,qBAAA;;AAkBJ;ECwPI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EAEA,sHAAA;EAoCF,mEAAA;ED7TA,2BAAA;EACA,qBAAA;;AAEA,YAAC;AACD,YAAC;EACC,yBAAA;EACA,4BAAA;;AAGF,YAAC;AACD,YAAC;EACC,yBAAA;EACA,qBAAA;;AAmBJ;ECuPI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EAEA,sHAAA;EAoCF,mEAAA;ED7TA,2BAAA;EACA,qBAAA;;AAEA,WAAC;AACD,WAAC;EACC,yBAAA;EACA,4BAAA;;AAGF,WAAC;AACD,WAAC;EACC,yBAAA;EACA,qBAAA;;AA2BJ;AACA;EC6CE,kDAAA;EACQ,0CAAA;;ADpCV,cAAe,KAAK,IAAG;AACvB,cAAe,KAAK,IAAG;ECmOnB,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;EDpOF,yBAAA;;AAEF,cAAe,UAAU;AACzB,cAAe,UAAU,IAAG;AAC5B,cAAe,UAAU,IAAG;EC6NxB,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;ED9NF,yBAAA;;AAUF;ECiNI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;EAoCF,mEAAA;EDrPA,kBAAA;ECaA,2FAAA;EACQ,mFAAA;;ADjBV,eAOE,YAAY,UAAU;EC0MpB,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;EApMF,wDAAA;EACQ,gDAAA;;ADLV;AACA,WAAY,KAAK;EACf,8CAAA;;AAIF;EC+LI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;EAoCF,mEAAA;;ADtOF,eAIE,YAAY,UAAU;EC2LpB,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;EApMF,uDAAA;EACQ,+CAAA;;ADCV,eASE;AATF,eAUE,YAAY,KAAK;EACf,yCAAA;;AAKJ;AACA;AACA;EACE,gBAAA;;AAUF;EACE,6CAAA;EChCA,0FAAA;EACQ,kFAAA;;AD2CV;ECqJI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;ED5JF,qBAAA;;AAKF;ECoJI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;ED5JF,qBAAA;;AAMF;ECmJI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;ED5JF,qBAAA;;AAOF;ECkJI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;ED5JF,qBAAA;;AAgBF;ECyII,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;ADlIJ;EC+HI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;ADjIJ;EC8HI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;ADhIJ;EC6HI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;AD/HJ;EC4HI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;AD9HJ;EC2HI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;ADtHJ;EACE,kBAAA;EC/EA,kDAAA;EACQ,0CAAA;;ADiFV,gBAAgB;AAChB,gBAAgB,OAAO;AACvB,gBAAgB,OAAO;EACrB,6BAAA;EC4GE,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;ED7GF,qBAAA;;AAUF;ECjGE,iDAAA;EACQ,yCAAA;;AD0GV,cAAe;ECsFX,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;ADxFJ,cAAe;ECqFX,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;ADvFJ,cAAe;ECoFX,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;ADtFJ,WAAY;ECmFR,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;ADrFJ,cAAe;ECkFX,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;ADpFJ,aAAc;ECiFV,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;AD5EJ;ECyEI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;ED1EF,qBAAA;EC1HA,yFAAA;EACQ,iFAAA","sourcesContent":["\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-bg, 5%); @end-color: darken(@navbar-default-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-bg; @end-color: lighten(@navbar-inverse-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255,255,255,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n}\n\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n}\n","//\n// Mixins\n// --------------------------------------------------\n\n\n// Utilities\n// -------------------------\n\n// Clearfix\n// Source: http://nicolasgallagher.com/micro-clearfix-hack/\n//\n// For modern browsers\n// 1. The space content is one way to avoid an Opera bug when the\n// contenteditable attribute is included anywhere else in the document.\n// Otherwise it causes space to appear at the top and bottom of elements\n// that are clearfixed.\n// 2. The use of `table` rather than `block` is only necessary if using\n// `:before` to contain the top-margins of child elements.\n.clearfix() {\n &:before,\n &:after {\n content: \" \"; // 1\n display: table; // 2\n }\n &:after {\n clear: both;\n }\n}\n\n// WebKit-style focus\n.tab-focus() {\n // Default\n outline: thin dotted;\n // WebKit\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n\n// Center-align a block level element\n.center-block() {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n\n// Sizing shortcuts\n.size(@width; @height) {\n width: @width;\n height: @height;\n}\n.square(@size) {\n .size(@size; @size);\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n &::-moz-placeholder { color: @color; // Firefox\n opacity: 1; } // See https://github.com/twbs/bootstrap/pull/11526\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Text overflow\n// Requires inline-block or block for proper styling\n.text-overflow() {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n// CSS image replacement\n//\n// Heads up! v3 launched with with only `.hide-text()`, but per our pattern for\n// mixins being reused as classes with the same name, this doesn't hold up. As\n// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`. Note\n// that we cannot chain the mixins together in Less, so they are repeated.\n//\n// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757\n\n// Deprecated as of v3.0.1 (will be removed in v4)\n.hide-text() {\n font: ~\"0/0\" a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n// New mixin to use as of v3.0.1\n.text-hide() {\n .hide-text();\n}\n\n\n\n// CSS3 PROPERTIES\n// --------------------------------------------------\n\n// Single side border-radius\n.border-top-radius(@radius) {\n border-top-right-radius: @radius;\n border-top-left-radius: @radius;\n}\n.border-right-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-top-right-radius: @radius;\n}\n.border-bottom-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-bottom-left-radius: @radius;\n}\n.border-left-radius(@radius) {\n border-bottom-left-radius: @radius;\n border-top-left-radius: @radius;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support the\n// standard `box-shadow` property.\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Transitions\n.transition(@transition) {\n -webkit-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n// Transformations\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n transform: rotate(@degrees);\n}\n.scale(@ratio; @ratio-y...) {\n -webkit-transform: scale(@ratio, @ratio-y);\n -ms-transform: scale(@ratio, @ratio-y); // IE9 only\n transform: scale(@ratio, @ratio-y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n transform: translate(@x, @y);\n}\n.skew(@x; @y) {\n -webkit-transform: skew(@x, @y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n transform: skew(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n.backface-visibility(@visibility){\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// User select\n// For selecting text on the page\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n\n// Resize anything\n.resizable(@direction) {\n resize: @direction; // Options: horizontal, vertical, both\n overflow: auto; // Safari fix\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Opacity\n.opacity(@opacity) {\n opacity: @opacity;\n // IE8 filter\n @opacity-ie: (@opacity * 100);\n filter: ~\"alpha(opacity=@{opacity-ie})\";\n}\n\n\n\n// GRADIENTS\n// --------------------------------------------------\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, color-stop(@start-color @start-percent), color-stop(@end-color @end-percent)); // Safari 5.1-6, Chrome 10+\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n\n// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n\n\n\n// Retina images\n//\n// Short retina mixin for setting background-image and -size\n\n.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {\n background-image: url(\"@{file-1x}\");\n\n @media\n only screen and (-webkit-min-device-pixel-ratio: 2),\n only screen and ( min--moz-device-pixel-ratio: 2),\n only screen and ( -o-min-device-pixel-ratio: 2/1),\n only screen and ( min-device-pixel-ratio: 2),\n only screen and ( min-resolution: 192dpi),\n only screen and ( min-resolution: 2dppx) {\n background-image: url(\"@{file-2x}\");\n background-size: @width-1x @height-1x;\n }\n}\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n\n.img-responsive(@display: block) {\n display: @display;\n max-width: 100%; // Part 1: Set a maximum relative to the parent\n height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching\n}\n\n\n// COMPONENT MIXINS\n// --------------------------------------------------\n\n// Horizontal dividers\n// -------------------------\n// Dividers (basically an hr) within dropdowns and nav lists\n.nav-divider(@color: #e5e5e5) {\n height: 1px;\n margin: ((@line-height-computed / 2) - 1) 0;\n overflow: hidden;\n background-color: @color;\n}\n\n// Panels\n// -------------------------\n.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) {\n border-color: @border;\n\n & > .panel-heading {\n color: @heading-text-color;\n background-color: @heading-bg-color;\n border-color: @heading-border;\n\n + .panel-collapse .panel-body {\n border-top-color: @border;\n }\n }\n & > .panel-footer {\n + .panel-collapse .panel-body {\n border-bottom-color: @border;\n }\n }\n}\n\n// Alerts\n// -------------------------\n.alert-variant(@background; @border; @text-color) {\n background-color: @background;\n border-color: @border;\n color: @text-color;\n\n hr {\n border-top-color: darken(@border, 5%);\n }\n .alert-link {\n color: darken(@text-color, 10%);\n }\n}\n\n// Tables\n// -------------------------\n.table-row-variant(@state; @background) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table > thead > tr,\n .table > tbody > tr,\n .table > tfoot > tr {\n > td.@{state},\n > th.@{state},\n &.@{state} > td,\n &.@{state} > th {\n background-color: @background;\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover > tbody > tr {\n > td.@{state}:hover,\n > th.@{state}:hover,\n &.@{state}:hover > td,\n &.@{state}:hover > th {\n background-color: darken(@background, 5%);\n }\n }\n}\n\n// List Groups\n// -------------------------\n.list-group-item-variant(@state; @background; @color) {\n .list-group-item-@{state} {\n color: @color;\n background-color: @background;\n\n a& {\n color: @color;\n\n .list-group-item-heading { color: inherit; }\n\n &:hover,\n &:focus {\n color: @color;\n background-color: darken(@background, 5%);\n }\n &.active,\n &.active:hover,\n &.active:focus {\n color: #fff;\n background-color: @color;\n border-color: @color;\n }\n }\n }\n}\n\n// Button variants\n// -------------------------\n// Easily pump out default styles, as well as :hover, :focus, :active,\n// and disabled options for all buttons\n.button-variant(@color; @background; @border) {\n color: @color;\n background-color: @background;\n border-color: @border;\n\n &:hover,\n &:focus,\n &:active,\n &.active,\n .open .dropdown-toggle& {\n color: @color;\n background-color: darken(@background, 8%);\n border-color: darken(@border, 12%);\n }\n &:active,\n &.active,\n .open .dropdown-toggle& {\n background-image: none;\n }\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &,\n &:hover,\n &:focus,\n &:active,\n &.active {\n background-color: @background;\n border-color: @border;\n }\n }\n\n .badge {\n color: @background;\n background-color: @color;\n }\n}\n\n// Button sizes\n// -------------------------\n.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n}\n\n// Pagination\n// -------------------------\n.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @border-radius) {\n > li {\n > a,\n > span {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n }\n &:first-child {\n > a,\n > span {\n .border-left-radius(@border-radius);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius);\n }\n }\n }\n}\n\n// Labels\n// -------------------------\n.label-variant(@color) {\n background-color: @color;\n &[href] {\n &:hover,\n &:focus {\n background-color: darken(@color, 10%);\n }\n }\n}\n\n// Contextual backgrounds\n// -------------------------\n.bg-variant(@color) {\n background-color: @color;\n a&:hover {\n background-color: darken(@color, 10%);\n }\n}\n\n// Typography\n// -------------------------\n.text-emphasis-variant(@color) {\n color: @color;\n a&:hover {\n color: darken(@color, 10%);\n }\n}\n\n// Navbar vertical align\n// -------------------------\n// Vertically center elements in the navbar.\n// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.\n.navbar-vertical-align(@element-height) {\n margin-top: ((@navbar-height - @element-height) / 2);\n margin-bottom: ((@navbar-height - @element-height) / 2);\n}\n\n// Progress bars\n// -------------------------\n.progress-bar-variant(@color) {\n background-color: @color;\n .progress-striped & {\n #gradient > .striped();\n }\n}\n\n// Responsive utilities\n// -------------------------\n// More easily include all the states for responsive-utilities.less.\n.responsive-visibility() {\n display: block !important;\n table& { display: table; }\n tr& { display: table-row !important; }\n th&,\n td& { display: table-cell !important; }\n}\n\n.responsive-invisibility() {\n display: none !important;\n}\n\n\n// Grid System\n// -----------\n\n// Centered container element\n.container-fixed() {\n margin-right: auto;\n margin-left: auto;\n padding-left: (@grid-gutter-width / 2);\n padding-right: (@grid-gutter-width / 2);\n &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n margin-left: (@gutter / -2);\n margin-right: (@gutter / -2);\n &:extend(.clearfix all);\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n float: left;\n width: percentage((@columns / @grid-columns));\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n}\n.make-xs-column-offset(@columns) {\n @media (min-width: @screen-xs-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-xs-column-push(@columns) {\n @media (min-width: @screen-xs-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-xs-column-pull(@columns) {\n @media (min-width: @screen-xs-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-sm-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-offset(@columns) {\n @media (min-width: @screen-sm-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-push(@columns) {\n @media (min-width: @screen-sm-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-pull(@columns) {\n @media (min-width: @screen-sm-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-md-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-offset(@columns) {\n @media (min-width: @screen-md-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-push(@columns) {\n @media (min-width: @screen-md-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-pull(@columns) {\n @media (min-width: @screen-md-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-lg-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-offset(@columns) {\n @media (min-width: @screen-lg-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-push(@columns) {\n @media (min-width: @screen-lg-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-pull(@columns) {\n @media (min-width: @screen-lg-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n\n// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `@grid-columns`.\n\n.make-grid-columns() {\n // Common styles for all sizes of grid columns, widths 1-12\n .col(@index) when (@index = 1) { // initial\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general; \"=<\" isn't a typo\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n position: relative;\n // Prevent columns from collapsing when empty\n min-height: 1px;\n // Inner gutter via padding\n padding-left: (@grid-gutter-width / 2);\n padding-right: (@grid-gutter-width / 2);\n }\n }\n .col(1); // kickstart it\n}\n\n.float-grid-columns(@class) {\n .col(@index) when (@index = 1) { // initial\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n float: left;\n }\n }\n .col(1); // kickstart it\n}\n\n.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {\n .col-@{class}-@{index} {\n width: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) {\n .col-@{class}-push-@{index} {\n left: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) {\n .col-@{class}-pull-@{index} {\n right: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = offset) {\n .col-@{class}-offset-@{index} {\n margin-left: percentage((@index / @grid-columns));\n }\n}\n\n// Basic looping in LESS\n.loop-grid-columns(@index, @class, @type) when (@index >= 0) {\n .calc-grid-column(@index, @class, @type);\n // next iteration\n .loop-grid-columns((@index - 1), @class, @type);\n}\n\n// Create grid for specific class\n.make-grid(@class) {\n .float-grid-columns(@class);\n .loop-grid-columns(@grid-columns, @class, width);\n .loop-grid-columns(@grid-columns, @class, pull);\n .loop-grid-columns(@grid-columns, @class, push);\n .loop-grid-columns(@grid-columns, @class, offset);\n}\n\n// Form validation states\n//\n// Used in forms.less to generate the form validation CSS for warnings, errors,\n// and successes.\n\n.form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) {\n // Color the label and help text\n .help-block,\n .control-label,\n .radio,\n .checkbox,\n .radio-inline,\n .checkbox-inline {\n color: @text-color;\n }\n // Set the border and box shadow on specific inputs to match\n .form-control {\n border-color: @border-color;\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work\n &:focus {\n border-color: darken(@border-color, 10%);\n @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@border-color, 20%);\n .box-shadow(@shadow);\n }\n }\n // Set validation states also for addons\n .input-group-addon {\n color: @text-color;\n border-color: @border-color;\n background-color: @background-color;\n }\n // Optional feedback icon\n .form-control-feedback {\n color: @text-color;\n }\n}\n\n// Form control focus state\n//\n// Generate a customized focus state and for any input with the specified color,\n// which defaults to the `@input-focus-border` variable.\n//\n// We highly encourage you to not customize the default value, but instead use\n// this to tweak colors on an as-needed basis. This aesthetic change is based on\n// WebKit's default styles, but applicable to a wider range of browsers. Its\n// usability and accessibility should be taken into account with any change.\n//\n// Example usage: change the default blue border and shadow to white for better\n// contrast against a dark gray background.\n\n.form-control-focus(@color: @input-border-focus) {\n @color-rgba: rgba(red(@color), green(@color), blue(@color), .6);\n &:focus {\n border-color: @color;\n outline: 0;\n .box-shadow(~\"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}\");\n }\n}\n\n// Form control sizing\n//\n// Relative text size, padding, and border-radii changes for form controls. For\n// horizontal sizing, wrap controls in the predefined grid classes. `