├── .bowerrc ├── .editorconfig ├── .gitignore ├── .jshintrc ├── LICENSE.md ├── README.md ├── bower.json ├── gulp ├── .jshintrc ├── build.js ├── conf.js ├── inject.js ├── license.js ├── scripts.js ├── server.js └── watch.js ├── gulpfile.js ├── package.json └── src ├── app ├── components │ ├── about │ │ ├── about.controller.js │ │ └── about.tmpl.html │ ├── analyticDialog │ │ └── analyticDialog.tmpl.html │ ├── analyticLoader │ │ └── analyticLoader.service.js │ ├── d3 │ │ ├── d3.service.js │ │ ├── hive.directive.js │ │ ├── test.controller.js │ │ └── test.css │ ├── grid │ │ ├── grid.controller.css │ │ ├── grid.controller.js │ │ └── grid.tmpl.html │ ├── lodash │ │ └── lodash.service.js │ └── navbar │ │ ├── navbar.css │ │ ├── navbar.directive.js │ │ └── navbar.html ├── index.config.js ├── index.constants.js ├── index.css ├── index.module.js ├── index.route.js ├── index.run.js └── main │ ├── main.controller.css │ ├── main.controller.js │ └── main.html ├── assets └── fonts │ ├── MaterialIcons-Regular.eot │ ├── MaterialIcons-Regular.ttf │ ├── MaterialIcons-Regular.woff │ ├── MaterialIcons-Regular.woff2 │ ├── RobotoSlab-Bold.woff │ └── RobotoSlab-Regular.woff └── index.html /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bower_components/ 3 | .sass-cache/ 4 | .idea/ 5 | .tmp/ 6 | dist/ 7 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "strict": true, 3 | "bitwise": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "latedef": false, 7 | "noarg": true, 8 | "undef": true, 9 | "unused": true, 10 | "validthis": true, 11 | "jasmine": true, 12 | "globals": { 13 | "angular": false, 14 | "inject": false, 15 | "module": false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 The MITRE Corporation. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | NOTICE 10 | 11 | This software was produced for the U. S. Government 12 | under Basic Contract No. W15P7T-13-C-A802, and is 13 | subject to the Rights in Noncommercial Computer Software 14 | and Noncommercial Computer Software Documentation 15 | Clause 252.227-7014 (FEB 2012) 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to CARET 2 | 3 | The Cyber Analytic Repository Exploration Tool (CARET) is a proof-of-concept graphical user interface designed to connect the groups and techniques highlighted in [ATT&CK](https://attack.mitre.org)® to the analytics, data model, and sensors highlighted in MITRE's [Cyber Analytics Repository](https://car.mitre.org) (CAR). 4 | CARET is used to develop an understanding of defensive capabilities and to aid in their development and use. 5 | Additional information explaining CARET and the types of questions it helps solve can be found at . 6 | 7 | 8 | ## To get started: 9 | 1. Clone this repository 10 | 2. Download and install [node.js](https://nodejs.org/en/) 11 | 3. In an elevated command, prompt run `npm install -g gulp bower` 12 | 4. In the caret/ directory, run `npm install` 13 | 4. In the caret/ directory, run `bower install` 14 | 5. To start the development server, run `gulp serve` 15 | 16 | ## Assorted features: 17 | - To create a minified distributable suitable for running on a standard webserver: 18 | - Modify the host URL for CAR to the appropriate location of the CAR instance to which you will deploy (e.g., in grid.controller.js and test.controller.js) 19 | - Run `gulp build` 20 | - The distributable is in the dist/ directory 21 | 22 | *** 23 | ATT&CK is a trademark of The MITRE Corporation. 24 | 25 | Approved for Public Release; Distribution Unlimited. 16-2823 26 | 27 | Copyright 2020 The MITRE Corporation. ALL RIGHTS RESERVED. 28 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "caret", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular-animate": "~1.4.0", 6 | "angular-aria": "~1.4.0", 7 | "angular-cookies": "~1.4.0", 8 | "angular-sanitize": "~1.4.0", 9 | "jquery": "~2.1.4", 10 | "angular-route": "~1.4.0", 11 | "angular-material": "1.1.1", 12 | "angular-messages": "~1.4.0", 13 | "toastr": "~2.1.1", 14 | "angular": "~1.4.0", 15 | "lodash": "~4.11.0", 16 | "d3": "3.5.6" 17 | }, 18 | "devDependencies": { 19 | "angular-mocks": "~1.4.0" 20 | }, 21 | "resolutions": { 22 | "jquery": "~2.1.4", 23 | "angular": "~1.4.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gulp/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.jshintrc", 3 | "node": true 4 | } 5 | -------------------------------------------------------------------------------- /gulp/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var gulp = require('gulp'); 5 | var conf = require('./conf'); 6 | 7 | var $ = require('gulp-load-plugins')({ 8 | pattern: ['gulp-*', 'main-bower-files', 'uglify-save-license', 'del'] 9 | }); 10 | 11 | gulp.task('partials', function () { 12 | return gulp.src([ 13 | path.join(conf.paths.src, '/app/**/*.html'), 14 | path.join(conf.paths.tmp, '/serve/app/**/*.html') 15 | ]) 16 | .pipe($.minifyHtml({ 17 | empty: true, 18 | spare: true, 19 | quotes: true 20 | })) 21 | .pipe($.angularTemplatecache('templateCacheHtml.js', { 22 | module: 'caret', 23 | root: 'app' 24 | })) 25 | .pipe(gulp.dest(conf.paths.tmp + '/partials/')); 26 | }); 27 | 28 | gulp.task('html', ['inject', 'partials'], function () { 29 | var partialsInjectFile = gulp.src(path.join(conf.paths.tmp, '/partials/templateCacheHtml.js'), { read: false }); 30 | var partialsInjectOptions = { 31 | starttag: '', 32 | ignorePath: path.join(conf.paths.tmp, '/partials'), 33 | addRootSlash: false 34 | }; 35 | 36 | var replaceFonts = function (x) { 37 | var fonts = $.filenames.get("fonts"); 38 | for (var i=0; i < fonts.length; i++ ) { 39 | var font = fonts[i].replace(/\\/g, '/'); 40 | x = x.pipe($.replace('/' + font, font)); 41 | } 42 | return x; 43 | }; 44 | 45 | var htmlFilter = $.filter('*.html'); 46 | var jsFilter = $.filter('**/*.js'); 47 | var cssFilter = $.filter('**/*.css'); 48 | var assets; 49 | 50 | var src = gulp.src(path.join(conf.paths.tmp, '/serve/*.html')) 51 | .pipe($.inject(partialsInjectFile, partialsInjectOptions)) 52 | .pipe(assets = $.useref.assets()) 53 | .pipe($.rev()) 54 | .pipe(jsFilter) 55 | .pipe($.ngAnnotate()) 56 | .pipe($.uglify({ preserveComments: $.uglifySaveLicense })).on('error', conf.errorHandler('Uglify')) 57 | .pipe(jsFilter.restore()) 58 | .pipe(cssFilter) 59 | .pipe($.csso()); 60 | src = replaceFonts(src); 61 | return src.pipe(cssFilter.restore()) 62 | .pipe(assets.restore()) 63 | .pipe($.useref()) 64 | .pipe($.revReplace()) 65 | .pipe(htmlFilter) 66 | .pipe($.minifyHtml({ 67 | empty: true, 68 | spare: true, 69 | quotes: true, 70 | conditionals: true 71 | })) 72 | .pipe(htmlFilter.restore()) 73 | .pipe(gulp.dest(path.join(conf.paths.dist, '/'))) 74 | .pipe($.size({ title: path.join(conf.paths.dist, '/'), showFiles: true })); 75 | }); 76 | 77 | // Only applies for fonts from bower dependencies 78 | // Custom fonts are handled by the "other" task 79 | gulp.task('fonts', function () { 80 | return gulp.src($.mainBowerFiles()) 81 | .pipe($.filter('**/*.{eot,svg,ttf,woff,woff2}')) 82 | .pipe($.flatten()) 83 | .pipe(gulp.dest(path.join(conf.paths.dist, '/fonts/'))); 84 | }); 85 | 86 | gulp.task('other-fonts', function () { 87 | var fileFilter = $.filter(function (file) { 88 | return file.stat.isFile(); 89 | }); 90 | 91 | return gulp.src([ 92 | path.join(conf.paths.src, '/**/*'), 93 | path.join('!' + conf.paths.src, '/**/*.{html,css,js}') 94 | ]) 95 | .pipe(fileFilter) 96 | .pipe($.filter('**/*.{eot,ttf,woff,woff2}')) 97 | .pipe($.filenames("fonts")) 98 | .pipe(gulp.dest(path.join(conf.paths.dist, '/styles/'))); 99 | }); 100 | 101 | gulp.task('other', function () { 102 | var fileFilter = $.filter(function (file) { 103 | return file.stat.isFile(); 104 | }); 105 | 106 | return gulp.src([ 107 | path.join(conf.paths.src, '/**/*'), 108 | path.join('!' + conf.paths.src, '/**/*.{html,css,js,eot,ttf,woff,woff2}') 109 | ]) 110 | .pipe(fileFilter) 111 | .pipe(gulp.dest(path.join(conf.paths.dist, '/'))); 112 | }); 113 | 114 | gulp.task('clean', function (done) { 115 | $.del([path.join(conf.paths.dist, '/'), path.join(conf.paths.tmp, '/')], done); 116 | }); 117 | 118 | gulp.task('build', ['html', 'fonts', 'other-fonts', 'other']); 119 | -------------------------------------------------------------------------------- /gulp/conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains the variables used in other gulp files 3 | * which defines tasks 4 | * By design, we only put there very generic config values 5 | * which are used in several places to keep good readability 6 | * of the tasks 7 | */ 8 | 9 | var gutil = require('gulp-util'); 10 | 11 | /** 12 | * The main paths of your project handle these with care 13 | */ 14 | exports.paths = { 15 | src: 'src', 16 | dist: 'dist', 17 | tmp: '.tmp', 18 | lic: 'licenses' 19 | }; 20 | 21 | /** 22 | * Wiredep is the lib which inject bower dependencies in your project 23 | * Mainly used to inject script tags in the index.html but also used 24 | * to inject css preprocessor deps and js files in karma 25 | */ 26 | exports.wiredep = { 27 | directory: 'bower_components' 28 | }; 29 | 30 | /** 31 | * Common implementation for an error handler of a Gulp plugin 32 | */ 33 | exports.errorHandler = function(title) { 34 | 'use strict'; 35 | 36 | return function(err) { 37 | gutil.log(gutil.colors.red('[' + title + ']'), err.toString()); 38 | this.emit('end'); 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /gulp/inject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var gulp = require('gulp'); 5 | var conf = require('./conf'); 6 | 7 | var $ = require('gulp-load-plugins')(); 8 | 9 | var wiredep = require('wiredep').stream; 10 | var _ = require('lodash'); 11 | 12 | gulp.task('inject', ['scripts'], function () { 13 | var injectStyles = gulp.src([ 14 | path.join(conf.paths.src, '/app/**/*.css') 15 | ], { read: false }); 16 | 17 | var injectScripts = gulp.src([ 18 | path.join(conf.paths.src, '/app/**/*.module.js'), 19 | path.join(conf.paths.src, '/app/**/*.js'), 20 | path.join('!' + conf.paths.src, '/app/**/*.spec.js'), 21 | path.join('!' + conf.paths.src, '/app/**/*.mock.js') 22 | ]) 23 | .pipe($.angularFilesort()).on('error', conf.errorHandler('AngularFilesort')); 24 | 25 | var injectOptions = { 26 | ignorePath: [conf.paths.src, path.join(conf.paths.tmp, '/serve')], 27 | addRootSlash: false 28 | }; 29 | 30 | return gulp.src(path.join(conf.paths.src, '/*.html')) 31 | .pipe($.inject(injectStyles, injectOptions)) 32 | .pipe($.inject(injectScripts, injectOptions)) 33 | .pipe(wiredep(_.extend({}, conf.wiredep))) 34 | .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve'))); 35 | }); 36 | -------------------------------------------------------------------------------- /gulp/license.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var gulp = require('gulp'); 5 | var conf = require('./conf'); 6 | var concat = require('gulp-concat'); 7 | 8 | var fs = require('fs'); 9 | 10 | 11 | gulp.task('license', function() { 12 | var a = gulp.src(path.join(conf.paths.lic, '*.txt')); 13 | 14 | var licenses = []; 15 | a.on('data', function(chunk) { 16 | var contents = chunk.contents.toString().trim(); 17 | var filename = chunk.path.split('\\'); 18 | filename = filename[filename.length - 1].split('.'); 19 | if (filename.length > 1) { 20 | filename.pop(); 21 | } 22 | filename = filename.join('.'); 23 | 24 | if (contents.length > 1) { 25 | process.stdout.write(JSON.stringify({name: filename, pre: contents}) + ',\n'); 26 | } 27 | }); 28 | }); -------------------------------------------------------------------------------- /gulp/scripts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var gulp = require('gulp'); 5 | var conf = require('./conf'); 6 | 7 | var browserSync = require('browser-sync'); 8 | 9 | var $ = require('gulp-load-plugins')(); 10 | 11 | gulp.task('scripts', function () { 12 | return gulp.src(path.join(conf.paths.src, '/app/**/*.js')) 13 | .pipe($.jshint()) 14 | .pipe($.jshint.reporter('jshint-stylish')) 15 | .pipe(browserSync.reload({ stream: true })) 16 | .pipe($.size()) 17 | }); 18 | -------------------------------------------------------------------------------- /gulp/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var gulp = require('gulp'); 5 | var conf = require('./conf'); 6 | 7 | var browserSync = require('browser-sync'); 8 | var browserSyncSpa = require('browser-sync-spa'); 9 | 10 | var util = require('util'); 11 | 12 | var proxyMiddleware = require('http-proxy-middleware'); 13 | 14 | function browserSyncInit(baseDir, browser) { 15 | browser = browser === undefined ? 'default' : browser; 16 | 17 | var routes = null; 18 | if(baseDir === conf.paths.src || (util.isArray(baseDir) && baseDir.indexOf(conf.paths.src) !== -1)) { 19 | routes = { 20 | '/bower_components': 'bower_components' 21 | }; 22 | } 23 | 24 | var server = { 25 | baseDir: baseDir, 26 | routes: routes 27 | }; 28 | 29 | /* 30 | * You can add a proxy to your backend by uncommenting the line bellow. 31 | * You just have to configure a context which will we redirected and the target url. 32 | * Example: $http.get('/users') requests will be automatically proxified. 33 | * 34 | * For more details and option, https://github.com/chimurai/http-proxy-middleware/blob/v0.0.5/README.md 35 | */ 36 | // server.middleware = proxyMiddleware('/users', {target: 'http://jsonplaceholder.typicode.com', proxyHost: 'jsonplaceholder.typicode.com'}); 37 | 38 | browserSync.instance = browserSync.init({ 39 | startPath: '/', 40 | server: server, 41 | browser: browser 42 | }); 43 | } 44 | 45 | browserSync.use(browserSyncSpa({ 46 | selector: '[ng-app]'// Only needed for angular apps 47 | })); 48 | 49 | gulp.task('serve', ['watch'], function () { 50 | browserSyncInit([path.join(conf.paths.tmp, '/serve'), conf.paths.src]); 51 | }); 52 | 53 | gulp.task('serve:dist', ['build'], function () { 54 | browserSyncInit(conf.paths.dist); 55 | }); 56 | -------------------------------------------------------------------------------- /gulp/watch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var gulp = require('gulp'); 5 | var conf = require('./conf'); 6 | 7 | var browserSync = require('browser-sync'); 8 | 9 | function isOnlyChange(event) { 10 | return event.type === 'changed'; 11 | } 12 | 13 | gulp.task('watch', ['inject'], function () { 14 | 15 | gulp.watch([path.join(conf.paths.src, '/*.html'), 'bower.json'], ['inject']); 16 | 17 | gulp.watch(path.join(conf.paths.src, '/app/**/*.css'), function(event) { 18 | if(isOnlyChange(event)) { 19 | browserSync.reload(event.path); 20 | } else { 21 | gulp.start('inject'); 22 | } 23 | }); 24 | 25 | gulp.watch(path.join(conf.paths.src, '/app/**/*.js'), function(event) { 26 | if(isOnlyChange(event)) { 27 | gulp.start('scripts'); 28 | } else { 29 | gulp.start('inject'); 30 | } 31 | }); 32 | 33 | gulp.watch(path.join(conf.paths.src, '/app/**/*.html'), function(event) { 34 | browserSync.reload(event.path); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your gulpfile! 3 | * The gulp tasks are splitted in several files in the gulp directory 4 | * because putting all here was really too long 5 | */ 6 | 7 | 'use strict'; 8 | 9 | var gulp = require('gulp'); 10 | var wrench = require('wrench'); 11 | var ghPages = require('gulp-gh-pages'); 12 | 13 | /** 14 | * This will load all js or coffee files in the gulp directory 15 | * in order to load all gulp tasks 16 | */ 17 | wrench.readdirSyncRecursive('./gulp').filter(function(file) { 18 | return (/\.(js|coffee)$/i).test(file); 19 | }).map(function(file) { 20 | require('./gulp/' + file); 21 | }); 22 | 23 | 24 | /** 25 | * Default task clean temporaries directories and launch the 26 | * main optimization build task 27 | */ 28 | gulp.task('default', ['clean'], function () { 29 | gulp.start('build'); 30 | }); 31 | 32 | /** 33 | * Deploy to github pages 34 | */ 35 | gulp.task('deploy', function() { 36 | return gulp.src('./dist/**/*') 37 | .pipe(ghPages()); 38 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "caret", 3 | "version": "0.0.0", 4 | "dependencies": {}, 5 | "scripts": { 6 | "test": "gulp test" 7 | }, 8 | "devDependencies": { 9 | "browser-sync": "^2.7.13", 10 | "browser-sync-spa": "~1.0.2", 11 | "chalk": "~1.0.0", 12 | "concat-stream": "~1.5.0", 13 | "del": "~1.2.0", 14 | "gulp": "~3.9.0", 15 | "gulp-angular-filesort": "~1.1.1", 16 | "gulp-angular-templatecache": "~1.6.0", 17 | "gulp-autoprefixer": "~2.3.1", 18 | "gulp-concat": "^2.6.1", 19 | "gulp-csso": "~1.0.0", 20 | "gulp-filenames": "~2.0.0", 21 | "gulp-filter": "~2.0.2", 22 | "gulp-flatten": "~0.0.4", 23 | "gulp-gh-pages": "^0.5.4", 24 | "gulp-inject": "~1.3.1", 25 | "gulp-jshint": "~1.11.0", 26 | "gulp-load-plugins": "^0.10.0", 27 | "gulp-minify-html": "~1.0.3", 28 | "gulp-ng-annotate": "~1.0.0", 29 | "gulp-rename": "~1.2.2", 30 | "gulp-replace": "~0.5.3", 31 | "gulp-rev": "~5.0.0", 32 | "gulp-rev-replace": "~0.4.2", 33 | "gulp-size": "~1.2.1", 34 | "gulp-sourcemaps": "~1.5.2", 35 | "gulp-uglify": "~1.2.0", 36 | "gulp-useref": "~1.2.0", 37 | "gulp-util": "~3.0.5", 38 | "http-proxy-middleware": "~0.0.5", 39 | "jshint-stylish": "~2.0.0", 40 | "lodash": "~4.17.5", 41 | "main-bower-files": "~2.8.0", 42 | "merge-stream": "~0.1.7", 43 | "require-dir": "~0.3.0", 44 | "uglify-save-license": "~0.4.1", 45 | "wiredep": "^2.2.2", 46 | "wrench": "^1.5.9" 47 | }, 48 | "engines": { 49 | "node": ">=0.10.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/app/components/about/about.controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('caret') 6 | .controller('AboutController', AboutController); 7 | 8 | /** @ngInject */ 9 | function AboutController($mdDialog) { 10 | var vm = this; 11 | 12 | vm.hide = function() { 13 | $mdDialog.hide(); 14 | }; 15 | vm.cancel = function() { 16 | $mdDialog.cancel(); 17 | }; 18 | vm.answer = function(answer) { 19 | $mdDialog.hide(answer); 20 | }; 21 | 22 | vm.licenses = [ 23 | { 24 | name: "MITRE", 25 | pre: "NOTICE\n\n" + 26 | "This software was produced for the U. S. Government under Contract No.\n" + 27 | "W15P7T-13-C-A802, and is subject to the Rights in Noncommercial Computer\n" + 28 | "Software and Noncommercial Computer Software Documentation Clause\n" + 29 | "252.227-7014 (FEB 2012)\n\n" + 30 | "© 2015-2016 The MITRE Corporation. All Rights Reserved" 31 | }, 32 | {"name":"Angular Material","pre":"The MIT License\r\n\r\nCopyright (c) 2014-2015 Google, Inc. http://angularjs.org\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in\r\nall copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\nTHE SOFTWARE."}, 33 | {"name":"Angular, Angular Animate, Angular Aria, Angular Cookies, Angular Route, Angular Mocks, Angular Touch, Angular Sanitize","pre":"The MIT License\r\n\r\nCopyright (c) 2010-2015 Google, Inc. http://angularjs.org\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in\r\nall copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\nTHE SOFTWARE."}, 34 | {"name":"Material Design Icons","pre":"Icons courtesy Google, Inc. \r\nhttps://github.com/google/material-design-icons/"}, 35 | {"name":"animate.css","pre":"The MIT License (MIT)\r\n\r\nCopyright (c) 2015 Daniel Eden\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in\r\nall copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\nTHE SOFTWARE."}, 36 | {"name":"backbone","pre":"Copyright (c) 2010-2016 Jeremy Ashkenas, DocumentCloud\r\n\r\nPermission is hereby granted, free of charge, to any person\r\nobtaining a copy of this software and associated documentation\r\nfiles (the \"Software\"), to deal in the Software without\r\nrestriction, including without limitation the rights to use,\r\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the\r\nSoftware is furnished to do so, subject to the following\r\nconditions:\r\n\r\nThe above copyright notice and this permission notice shall be\r\nincluded in all copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\nOTHER DEALINGS IN THE SOFTWARE."}, 37 | {"name":"d3","pre":"Copyright (c) 2010-2015, Michael Bostock\r\nAll rights reserved.\r\n\r\nRedistribution and use in source and binary forms, with or without\r\nmodification, are permitted provided that the following conditions are met:\r\n\r\n* Redistributions of source code must retain the above copyright notice, this\r\n list of conditions and the following disclaimer.\r\n\r\n* Redistributions in binary form must reproduce the above copyright notice,\r\n this list of conditions and the following disclaimer in the documentation\r\n and/or other materials provided with the distribution.\r\n\r\n* The name Michael Bostock may not be used to endorse or promote products\r\n derived from this software without specific prior written permission.\r\n\r\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\r\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\r\nDISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,\r\nINDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\r\nBUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\r\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\r\nOF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,\r\nEVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."}, 38 | {"name":"firebug-lite","pre":"Software License Agreement (BSD License)\r\n\r\nCopyright (c) 2007, Parakey Inc.\r\nAll rights reserved.\r\n\r\nRedistribution and use of this software in source and binary forms, with or without modification,\r\nare permitted provided that the following conditions are met:\r\n\r\n* Redistributions of source code must retain the above\r\n copyright notice, this list of conditions and the\r\n following disclaimer.\r\n\r\n* Redistributions in binary form must reproduce the above\r\n copyright notice, this list of conditions and the\r\n following disclaimer in the documentation and/or other\r\n materials provided with the distribution.\r\n\r\n* Neither the name of Parakey Inc. nor the names of its\r\n contributors may be used to endorse or promote products\r\n derived from this software without specific prior\r\n written permission of Parakey Inc.\r\n\r\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR\r\nIMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND\r\nFITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r\nCONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\r\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\r\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER\r\nIN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT\r\nOF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."}, 39 | {"name":"generator-gulp-angular","pre":"Copyright (c) 2015 Matthieu Lux, Swiip\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in\r\nall copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\nTHE SOFTWARE."}, 40 | {"name":"jQuery","pre":"Copyright 2014 jQuery Foundation and other contributors\r\nhttp://jquery.com/\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining\r\na copy of this software and associated documentation files (the\r\n\"Software\"), to deal in the Software without restriction, including\r\nwithout limitation the rights to use, copy, modify, merge, publish,\r\ndistribute, sublicense, and/or sell copies of the Software, and to\r\npermit persons to whom the Software is furnished to do so, subject to\r\nthe following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be\r\nincluded in all copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."}, 41 | {"name":"lodash","pre":"Copyright jQuery Foundation and other contributors \r\n\r\nBased on Underscore.js, copyright Jeremy Ashkenas,\r\nDocumentCloud and Investigative Reporters & Editors \r\n\r\nThis software consists of voluntary contributions made by many\r\nindividuals. For exact contribution history, see the revision history\r\navailable at https://github.com/lodash/lodash\r\n\r\nThe following license applies to all parts of this software except as\r\ndocumented below:\r\n\r\n====\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining\r\na copy of this software and associated documentation files (the\r\n\"Software\"), to deal in the Software without restriction, including\r\nwithout limitation the rights to use, copy, modify, merge, publish,\r\ndistribute, sublicense, and/or sell copies of the Software, and to\r\npermit persons to whom the Software is furnished to do so, subject to\r\nthe following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be\r\nincluded in all copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n\r\n====\r\n\r\nCopyright and related rights for sample code are waived via CC0. Sample\r\ncode is defined as all source code displayed within the prose of the\r\ndocumentation.\r\n\r\nCC0: http://creativecommons.org/publicdomain/zero/1.0/\r\n\r\n====\r\n\r\nFiles located in the node_modules and vendor directories are externally\r\nmaintained libraries used by this software which have their own\r\nlicenses; we recommend you read them, as their terms may differ from the\r\nterms above."}, 42 | {"name":"moment","pre":"Copyright (c) 2011-2015 Tim Wood, Iskren Chernev, Moment.js contributors\r\n\r\nPermission is hereby granted, free of charge, to any person\r\nobtaining a copy of this software and associated documentation\r\nfiles (the \"Software\"), to deal in the Software without\r\nrestriction, including without limitation the rights to use,\r\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the\r\nSoftware is furnished to do so, subject to the following\r\nconditions:\r\n\r\nThe above copyright notice and this permission notice shall be\r\nincluded in all copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\nOTHER DEALINGS IN THE SOFTWARE."}, 43 | {"name":"toastr","pre":"The MIT License (MIT)\r\n\r\nCopyright © 2012-2015 John Papa, Tim Ferrell, and Hans Fjällemark\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in\r\nall copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\nTHE SOFTWARE."}, 44 | {"name":"underscore","pre":"Copyright (c) 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative\r\nReporters & Editors\r\n\r\nPermission is hereby granted, free of charge, to any person\r\nobtaining a copy of this software and associated documentation\r\nfiles (the \"Software\"), to deal in the Software without\r\nrestriction, including without limitation the rights to use,\r\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the\r\nSoftware is furnished to do so, subject to the following\r\nconditions:\r\n\r\nThe above copyright notice and this permission notice shall be\r\nincluded in all copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\nOTHER DEALINGS IN THE SOFTWARE."}, 45 | ]; 46 | } 47 | })(); 48 | -------------------------------------------------------------------------------- /src/app/components/about/about.tmpl.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |

About CARET - The CAR Exploration Tool

6 | 7 | 8 | close 9 | 10 |
11 |
12 | 13 |

Copyright Notices

14 |
15 |

{{license.name}}

16 |
{{license.pre}}
17 | 18 |
19 |
20 |
21 |
-------------------------------------------------------------------------------- /src/app/components/analyticDialog/analyticDialog.tmpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

{{name}}

5 | 6 | 7 | close 8 | 9 |
10 |
11 | 12 | 13 | 14 |
-------------------------------------------------------------------------------- /src/app/components/analyticLoader/analyticLoader.service.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('caret') 6 | .factory('analyticLoader', analyticLoader); 7 | 8 | /** @ngInject */ 9 | function analyticLoader($log, $http, $window, _) { 10 | var service = { 11 | getAnalytics: getAnalytics, 12 | getTechniques: getTechniques, 13 | getGroups: getGroups, 14 | getSensors: getSensors, 15 | getDataModel: getDataModel 16 | }; 17 | 18 | function getGroups() { 19 | return $http.get('https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json', {cache: true}) 20 | .then(getGroupsComplete) 21 | .catch(errorFunction('XHR Failed for getGroups.\n')); 22 | 23 | function getGroupsComplete(response) { 24 | var results = []; 25 | angular.forEach(response.data.objects, function (value) { 26 | // Check if the object is a Group 27 | if(value.type == 'intrusion-set') { 28 | var ID = _.map(value.external_references, function (v) {if(v.source_name == 'mitre-attack') return v.external_id;}).join(''); //string 29 | var name = 'Group/' + ID; //string 30 | var aliases = (value.aliases != undefined ? value.aliases : []); //list of strings 31 | //Getting the Techniques 32 | var target_references = _.map(_.filter(response.data.objects, {'type':'relationship', 'relationship_type':'uses', 'source_ref':value.id}), 'target_ref'); //target_references is a list of strings that must be matched with a technique id 33 | var target_techniques = []; //target_techniques is a list of dicts that has the group techniques 34 | angular.forEach(target_references, function (value) { 35 | var t = _.filter(response.data.objects, {'type':'attack-pattern', 'id':value}); 36 | if(t.length > 0) { 37 | target_techniques.push(t[0]); 38 | } 39 | }); 40 | var techniques = []; //list of strings 41 | angular.forEach(target_techniques, function (value) { 42 | techniques.push(_.map(value.external_references, function (v) {if(v.source_name == 'mitre-attack') return 'Technique/' + v.external_id;}).join('')); 43 | }); 44 | results.push({name: name, techniques: techniques, ID: ID, aliases: aliases}); 45 | } 46 | }); 47 | return results; 48 | } 49 | } 50 | 51 | function getTechniques() { 52 | return $http.get('https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json', {cache: true}) 53 | .then(getTechniquesComplete) 54 | .catch(errorFunction('XHR Failed for getTechniques.\n')); 55 | 56 | function getTechniquesComplete(response) { 57 | var results = []; 58 | angular.forEach(response.data.objects, function (value, key) { 59 | //Check if the object is a Technique 60 | if(value.type == 'attack-pattern') { 61 | var ID = _.map(value.external_references, function (v) { if (v.source_name == 'mitre-attack') return v.external_id; }).join(''); //string 62 | var name = 'Technique/' + ID; //string 63 | var tactics = _.map(value.kill_chain_phases, function (v) { if (v.kill_chain_name == 'mitre-attack') return _.startCase(v.phase_name); }); // list of dicts where 'phase_name' contains the tactic 64 | var display_name = value.name; //string 65 | results.push({name: name, tactics: tactics, ID: ID, 'display_name': display_name}); 66 | } 67 | }); 68 | return results; 69 | } 70 | } 71 | 72 | function getAnalytics(host) { 73 | return $http.get(host + '/data/analytics.json') 74 | .then(function(response) {return response.data.analytics}) 75 | .catch(errorFunction('XHR Failed for getAnalytics.\n')) 76 | } 77 | 78 | function getSensors(host) { 79 | return $http.get(host + '/data/sensors.json') 80 | .then(function(response) {return response.data.sensors}) 81 | .catch(errorFunction('XHR Failed for getSensors.\n')) 82 | } 83 | 84 | function getDataModel(host) { 85 | return $http.get(host + '/data/data_model.json') 86 | .then(function(response) {return response.data.objects}) 87 | .catch(errorFunction('XHR Failed for getDataModel.\n')) 88 | } 89 | 90 | function errorFunction(message) { 91 | return function (error) {$log.error(message + angular.toJson(error.data, true));}; 92 | } 93 | 94 | return service; 95 | } 96 | })(); 97 | -------------------------------------------------------------------------------- /src/app/components/d3/d3.service.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('d3', []) 6 | .factory('d3', ['$window', function($window) { 7 | return $window.d3; // assumes d3 has already been loaded on the page 8 | }]); 9 | })(); 10 | -------------------------------------------------------------------------------- /src/app/components/d3/hive.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('caret') 6 | .directive('hive', hiveDirective); 7 | 8 | /** @ngInject */ 9 | function hiveDirective($parse, d3, _) { 10 | var directive = { 11 | restrict: 'E', 12 | replace: false, 13 | scope: { 14 | width: '=width', 15 | height: '=height', 16 | links: '=links', 17 | border: '=border', 18 | labels: '=labels', 19 | nodes: '=nodes' 20 | }, 21 | link: link 22 | }; 23 | 24 | return directive; 25 | 26 | /** @ngInject */ 27 | function link (scope, element) { 28 | var root = d3.select(element[0]); 29 | d3.select(element[0]).append("svg"); 30 | d3.select(element[0]).append("p") 31 | .style("height", "1em"); 32 | 33 | var graph = resizeGraph(scope); 34 | 35 | var nl = buildNodesAndLinks(scope), 36 | _nodes = nl[0], 37 | _links = nl[1]; 38 | 39 | drawHive(root, graph, _nodes, _links); 40 | 41 | scope.$watch("height", function() { 42 | graph = resizeGraph(scope); 43 | drawHive(root, graph, _nodes, _links); 44 | }); 45 | 46 | scope.$watch("width", function() { 47 | graph = resizeGraph(scope); 48 | drawHive(root, graph, _nodes, _links); 49 | }); 50 | 51 | scope.$watch("border", function() { 52 | graph = resizeGraph(scope); 53 | drawHive(root, graph, _nodes, _links); 54 | }); 55 | 56 | scope.$watch("labels", function () { 57 | drawHive(root, graph, _nodes, _links); 58 | }); 59 | 60 | scope.$watchCollection("nodes", function() { 61 | graph = resizeGraph(scope); 62 | nl = buildNodesAndLinks(scope); 63 | _nodes = nl[0]; 64 | _links = nl[1]; 65 | drawHive(root, graph, _nodes, _links); 66 | }); 67 | 68 | scope.$watchCollection("links", function() { 69 | graph = resizeGraph(scope); 70 | nl = buildNodesAndLinks(scope); 71 | _nodes = nl[0]; 72 | _links = nl[1]; 73 | drawHive(root, graph, _nodes, _links); 74 | }); 75 | } 76 | 77 | function reposition(svg, graph) { 78 | //this function will ignore animations, which is fine 79 | svg = svg.attr("width", graph.width) 80 | .attr("height", graph.height); 81 | 82 | //reposition nodes 83 | svg.selectAll(".node-group") 84 | .attr("transform", function(d) { return "translate(" + graph.xs(d.x) + ", " + graph.ys(d.y) + ")"; }); 85 | 86 | //reposition links 87 | svg.selectAll(".link") 88 | .attr("d", 89 | hiveLink() 90 | .projection(function (d) { return {x: graph.xs(d.x), y: graph.ys(d.y)}; })); 91 | 92 | //reposition axis 93 | svg.selectAll(".axis") 94 | .attr("class", "axis") 95 | .attr("x1", function(d) { return graph.xs(d); }) 96 | .attr("x2", function(d) { return graph.xs(d); }) 97 | .attr("y1", graph.ys(0)) 98 | .attr("y2", graph.ys(1)); 99 | 100 | //reposition axis-label 101 | svg.selectAll(".label") 102 | .attr("x", function (_, i) { return graph.xs(i); }) 103 | .attr("y", graph.border / 2.0 ); 104 | } 105 | 106 | function buildNodesAndLinks(scope) { 107 | var oldNodeMap = {}; 108 | var _nodes = _.map(scope.nodes, function (node) { 109 | var temp = {}, 110 | newObj = _.clone(node); 111 | temp[node.x + ':' + node.y] = newObj; 112 | angular.extend(oldNodeMap, temp); 113 | newObj.selected = false; 114 | newObj.links = []; 115 | return newObj; 116 | }); 117 | 118 | var _links = _.map(scope.links, function (link) { 119 | var newObj = _.clone(link); 120 | newObj.source = oldNodeMap[newObj.source.x + ':' + newObj.source.y]; 121 | newObj.target = oldNodeMap[newObj.target.x + ':' + newObj.target.y]; 122 | return angular.extend(newObj, {selected: false}); 123 | }); 124 | 125 | angular.forEach(_links, function (l) { 126 | l.target.links.push(l); 127 | l.source.links.push(l); 128 | }); 129 | 130 | return [_nodes, _links]; 131 | } 132 | 133 | function resizeGraph(scope) { 134 | var width = scope.width, 135 | height = scope.height, 136 | numAxis = _.reduce(scope.nodes, function(memo, elem){ return memo < elem.x ? elem.x : memo; }, -1) + 1, 137 | border = scope.border, 138 | labels = scope.labels, 139 | y_range = [border, height - 5], 140 | xs = d3.scale.ordinal().domain(d3.range(numAxis)).rangeRoundPoints([0, width], 0.35), 141 | ys = d3.scale.linear().range(y_range), 142 | color = d3.scale.category10().domain(d3.range(20)); 143 | 144 | return { 145 | width: width, 146 | height: height, 147 | numAxis: numAxis, 148 | border: border, 149 | labels: labels, 150 | y_range: y_range, 151 | xs: xs, 152 | ys: ys, 153 | color: color 154 | }; 155 | } 156 | 157 | function addButton(graph, toolbar, text, dy, clickHandler, font_size, font_family) { 158 | if (graph.buttons === undefined) { 159 | graph.buttons = 1; 160 | } else { 161 | graph.buttons = graph.buttons + 1; 162 | } 163 | 164 | // each button is 32 wide and there is 6 in between them 165 | toolbar 166 | .attr('transform', 'translate(' + (graph.width - 38*graph.buttons - 6) + ', ' + (graph.height - 50) + ')'); 167 | 168 | var button = toolbar.append('g') 169 | .attr('class', 'button'); 170 | 171 | if (graph.buttons > 1) { 172 | button.attr('transform', 'translate(' + 38 * (graph.buttons - 1) + ', 0)'); 173 | } 174 | button.append('rect') 175 | .attr("x", "0") 176 | .attr("y", "0") 177 | .attr("rx", "5") 178 | .attr("ry", "5") 179 | .attr("width", "32") 180 | .attr("height", "32") 181 | .style("fill", "rgb(63,81,181)") 182 | .style("stroke", "black") 183 | .style("stroke-width", "1") 184 | .style("cursor", "pointer") 185 | .on("click", clickHandler); 186 | 187 | var t = button.append("text"); 188 | t 189 | .attr("class", "button-label") 190 | .attr("x", "16") 191 | .attr("y", "16") 192 | .attr("text-anchor", "middle") 193 | .style("cursor", "pointer") 194 | .attr("dy", dy) 195 | .text(text) 196 | .on("click", clickHandler); 197 | 198 | if (font_size !== undefined) { 199 | t.style("font-size", font_size); 200 | } 201 | 202 | if (font_family !== undefined) { 203 | t.style("font-family", font_family); 204 | } 205 | } 206 | 207 | function clearHive(svg) { 208 | svg.selectAll("*").remove(); 209 | } 210 | 211 | function drawHive(root, graph, _nodes, _links) { 212 | graph.nodesVisible = true; 213 | 214 | var svg = root.select("svg"); 215 | var p = root.select("p"); 216 | 217 | clearHive(svg); 218 | 219 | svg = svg.attr("width", graph.width) 220 | .attr("height", graph.height); 221 | 222 | var scalable = svg.append("g"); 223 | 224 | var zoom = d3.behavior.zoom(); 225 | svg.call(zoom.scaleExtent([0.25, 4]).on("zoom", function() { 226 | scalable.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")"); 227 | d3.event.sourceEvent.stopPropagation(); 228 | })) 229 | // remove the double click zoom behavior 230 | .on("dblclick.zoom", null); 231 | 232 | scalable.selectAll(".axis") 233 | .data(d3.range(graph.numAxis)) 234 | .enter().append("line") 235 | .attr("class", "axis") 236 | .attr("x1", function(d) { return graph.xs(d); }) 237 | .attr("x2", function(d) { return graph.xs(d); }) 238 | .attr("y1", graph.ys(0)) 239 | .attr("y2", graph.ys(1)); 240 | 241 | scalable.selectAll(".link") 242 | .data(_links) 243 | .enter().append("path") 244 | .attr("class", "link") 245 | .attr("d", 246 | hiveLink() 247 | .projection(function (d) { return {x: graph.xs(d.x), y: graph.ys(d.y)}; })) 248 | .style("stroke", "grey"); 249 | 250 | var nodeGroup = scalable.selectAll(".node-group").data(_nodes), 251 | nodeGroupEnter = nodeGroup.enter().append("g") 252 | .attr("class", 'node-group') 253 | .attr("transform", function(d) { return "translate(" + graph.xs(d.x) + ", " + graph.ys(d.y) + ")"; }); 254 | 255 | nodeGroupEnter.append("circle") 256 | .attr("class", "node") 257 | .attr("cx", 0) 258 | .attr("cy", 0) 259 | .attr("r", 4) 260 | .style("fill", function(d) { return graph.color(d.x); }) 261 | .on("mouseover", _.partial(mouseover, p)) 262 | .on("mouseout", _.partial(mouseout, p)) 263 | .on("click", _.partial(nodeclick, svg, graph, _nodes, _links)); 264 | 265 | nodeGroupEnter.append("text") 266 | .attr("class", "node-label") 267 | .attr("x", 0) 268 | .attr("y", 0) 269 | .attr("dy",".3em") 270 | .attr("text-anchor", "middle") 271 | .attr("font-size", "6px") 272 | .on("mouseover", _.partial(mouseover, p)) 273 | .on("mouseout", _.partial(mouseout, p)) 274 | .on("click", _.partial(nodeclick, svg, graph, _nodes, _links)) 275 | .style("cursor", "default") 276 | .text(function (d) { return d.name; }); 277 | 278 | svg.selectAll(".node-label").classed("invisible", true); 279 | 280 | scalable.selectAll(".label") 281 | .data(graph.labels) 282 | .enter().append("text") 283 | .attr("class", "label") 284 | .attr("x", function (_, i) { return graph.xs(i); }) 285 | .attr("y", graph.border / 2.0 ) 286 | .attr("text-anchor", "middle") 287 | .style("cursor", "default") 288 | .text(function (d) { return d; }); 289 | 290 | var toolbar = svg.append("g"); 291 | toolbar 292 | .attr('class', 'toolbar'); 293 | 294 | addButton(graph, toolbar, "A/\u2022", ".3em", _.partial(toggleNode, svg, graph)); 295 | addButton(graph, toolbar, "\uE8C4", ".5em", _.partial(centerGraph, scalable, zoom), "1.5em", "Material Icons"); 296 | // addButton(graph, toolbar, "\uE5D4", ".5em", _.partial(centerGraph, scalable, zoom), "1.5em", "Material Icons"); 297 | 298 | svg.on("click", function () { 299 | // prevent drags from triggering click handler 300 | if (d3.event.defaultPrevented) { return; } 301 | 302 | if (!d3.select(d3.event.target).classed('node') && 303 | !d3.select(d3.event.target).classed('node-label') && 304 | !d3.select(d3.event.target).classed('button') && 305 | !d3.select(d3.event.target).classed('button-label')) { 306 | deselectNode(svg, graph, _nodes, _links); 307 | } 308 | }); 309 | } 310 | 311 | function centerGraph(scalable, zoom) { 312 | scalable.attr("transform", "translate(0, 0) scale(1)"); 313 | zoom.scale(1); 314 | zoom.translate([0, 0]); 315 | } 316 | 317 | function toggleNode(svg, graph) { 318 | graph.nodesVisible = !graph.nodesVisible; 319 | svg.selectAll(".node").classed("invisible", !graph.nodesVisible); 320 | svg.selectAll(".node-label").classed("invisible", graph.nodesVisible); 321 | } 322 | 323 | function deselectNode(svg, graph, _nodes, _links) { 324 | angular.forEach(_links, function (l) {l.selected = false;}); 325 | angular.forEach(_nodes, function (n) {n.selected = false; n.primary = false;}); 326 | 327 | svg.selectAll(".node").classed("primary", function (p) { return p.primary; }); 328 | svg.selectAll(".node-label").classed("primary", function (p) { return p.primary; }); 329 | 330 | // on deselect send back to original position 331 | svg.selectAll(".node-group.selected") 332 | .transition() 333 | .attr("transform", function(d) { 334 | return "translate(" + graph.xs(d.x) + ", " + graph.ys(d.y) + ")"; 335 | }); 336 | 337 | svg.selectAll(".link.selected") 338 | .transition() 339 | .attr("d", 340 | hiveLink() 341 | .projection(function (d) { return {x: graph.xs(d.x), y: graph.ys(d.y)}; })); 342 | 343 | svg.selectAll(".node-label").attr("font-size", "6px"); 344 | 345 | svg.selectAll(".active").classed("active", false); 346 | svg.selectAll(".selected").classed("selected", false); 347 | svg.selectAll(".unselected").classed("unselected", false); 348 | } 349 | 350 | /* Note because this function uses a partial d must always be the last argument */ 351 | function nodeclick(svg, graph, _nodes, _links, d) { 352 | deselectNode(svg, graph, _nodes, _links); 353 | 354 | d3.select(this).classed("selected", true); 355 | 356 | d.primary = true; 357 | d.selected = true; 358 | 359 | svg.selectAll(".node").classed("primary", function (p) { return p.primary; }); 360 | svg.selectAll(".node-label").classed("primary", function (p) { return p.primary; }); 361 | 362 | var left = d.x, 363 | right = d.x, 364 | highlightLinkIf = function (l, r) { 365 | return function(p) { 366 | if ((p.source.selected || p.target.selected) && 367 | !(p.source.x <= r && p.source.x >= l && 368 | p.target.x <= r && p.target.x >= l)) 369 | { 370 | p.selected = true; 371 | } 372 | return p; 373 | }; 374 | }, 375 | highlightNodes = function(p) { 376 | if (p.selected) 377 | { 378 | p.source.selected = true; 379 | p.target.selected = true; 380 | } 381 | return p; 382 | }; 383 | 384 | svg.selectAll(".link.selected").classed("unselected", true); 385 | svg.selectAll(".node").classed("unselected", true); 386 | svg.selectAll(".node-group").classed("unselected", true); 387 | svg.selectAll(".node-label").classed("unselected", true); 388 | 389 | while (left !== 0 || right !== graph.numAxis - 1) 390 | { 391 | _links = _.map(_links, highlightLinkIf(left, right)); 392 | angular.forEach(_links, highlightNodes); 393 | svg.selectAll(".link").classed("selected", function (p) { return p.selected; }); 394 | svg.selectAll(".node").classed("selected", function (p) { return p.selected; }); 395 | left = left - 1 > 0 ? left - 1 : 0; 396 | right = right + 1 < graph.numAxis - 1 ? right + 1 : graph.numAxis - 1; 397 | } 398 | svg.selectAll(".link").classed("unselected", function (p) { return !p.selected; }); 399 | svg.selectAll(".node").classed("unselected", function (p) { return !p.selected; }); 400 | svg.selectAll(".node-group").classed("selected", function (p) { return p.selected; }); 401 | svg.selectAll(".node-group").classed("unselected", function (p) { return !p.selected; }); 402 | svg.selectAll(".node-label").classed("selected", function (p) { return p.selected; }); 403 | svg.selectAll(".node-label").classed("unselected", function (p) { return !p.selected; }); 404 | 405 | // setup transitions and font size 406 | for (var i = 0; i < graph.numAxis; i++) { 407 | var ns = _.filter(_nodes, {x: i, selected: true}); 408 | 409 | var new_ys = d3.scale.ordinal().domain(d3.range(ns.length)).rangePoints([0, 1], 1.0), 410 | font_size_band = d3.scale.ordinal().domain(d3.range(ns.length)).rangeRoundBands(graph.y_range).rangeBand(), 411 | font_size = font_size_band < 16 ? font_size_band : 16; 412 | 413 | for (var j = 0; j < ns.length; j++) { 414 | angular.extend(ns[j], {new_y : new_ys(j), font_size: font_size}); 415 | } 416 | } 417 | svg.selectAll(".node-group.selected") 418 | .transition() 419 | .attr("transform", function(d) { 420 | return "translate(" + graph.xs(d.x) + ", " + graph.ys(d.new_y) + ")"; 421 | }); 422 | 423 | svg.selectAll(".node-label.selected") 424 | .attr("font-size", function (d) { return d.font_size.toString() + 'px'; }); 425 | 426 | svg.selectAll(".link.selected") 427 | .transition() 428 | .attr("d", 429 | hiveLink() 430 | .projection(function (d) { return {x: graph.xs(d.x), y: graph.ys(d.new_y)}; })); 431 | } 432 | 433 | function addToWorkingSet(d) { 434 | d3.select(this); 435 | } 436 | 437 | function mouseover(p, d) { 438 | d3.select(this).classed("active", true); 439 | p.text(d.name); 440 | } 441 | 442 | function mouseout(p) { 443 | d3.select(this).classed("active", false); 444 | p.text(""); 445 | } 446 | 447 | function hiveLink() { 448 | var projection = function(d) { return {x: d.x, y: d.y}; }; 449 | 450 | function link(d) { 451 | //d has source and target 452 | var proj_s = d3.functor(projection).call(this, d.source), 453 | proj_t = d3.functor(projection).call(this, d.target), 454 | l = [[proj_s.x, proj_s.y], 455 | [0.67 * proj_s.x + 0.33 * proj_t.x, proj_s.y], 456 | [0.33 * proj_s.x + 0.67 * proj_t.x, proj_t.y], 457 | [proj_t.x, proj_t.y]], 458 | f = d3.svg.line().interpolate('bundle'); 459 | 460 | return f(l); 461 | } 462 | 463 | link.projection = function(value) { 464 | if (!arguments.length) { 465 | return projection; 466 | } 467 | projection = value; 468 | return link; 469 | }; 470 | 471 | return link; 472 | } 473 | } 474 | })(); -------------------------------------------------------------------------------- /src/app/components/d3/test.controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('caret') 6 | .controller('TestController', TestController); 7 | 8 | /** @ngInject */ 9 | function TestController($window, analyticLoader, _) { 10 | var vm = this; 11 | 12 | vm.width = 0; 13 | vm.height = 0; 14 | vm.border = 30; 15 | 16 | function windowSize() { 17 | vm.height = $window.innerHeight - 200; 18 | vm.width = $window.innerWidth - 35; 19 | } 20 | 21 | windowSize(); 22 | angular.element($window).bind('resize', windowSize); 23 | 24 | vm.nodes = []; 25 | vm.links = []; 26 | vm.labels = []; 27 | 28 | var carHost = 'https://car.mitre.org'; 29 | var analytics = []; 30 | var techniques = []; 31 | var groups = []; 32 | var sensors = []; 33 | var dataModel = []; 34 | 35 | loadCAR(); 36 | 37 | function loadCAR() { 38 | analyticLoader.getAnalytics(carHost) 39 | .then(function (response) { 40 | analytics = response; 41 | buildLinksAndNodes(); 42 | }); 43 | 44 | analyticLoader.getTechniques() 45 | .then(function (response) { 46 | _.map(response, function (technique) { 47 | angular.forEach(technique.tactics, function (tactic) { 48 | techniques.push({name: technique.name, 49 | ID: technique.ID, 50 | display_name: technique.display_name, 51 | tactic: tactic}); 52 | }); 53 | }); 54 | buildLinksAndNodes(); 55 | }); 56 | 57 | analyticLoader.getGroups() 58 | .then(function (response) { 59 | groups = response; 60 | buildLinksAndNodes(); 61 | }); 62 | 63 | analyticLoader.getSensors(carHost) 64 | .then(function (response) { 65 | sensors = response; 66 | buildLinksAndNodes(); 67 | }); 68 | 69 | analyticLoader.getDataModel(carHost) 70 | .then(function (response) { 71 | dataModel = _.map(response, function (e) { return _.map(e.actions, function (d) { return {name: e.name + '/' + d}; }); }); 72 | dataModel = _.flatten(dataModel); 73 | buildLinksAndNodes(); 74 | }); 75 | } 76 | 77 | function buildLinksAndNodes() { 78 | if (groups.length === 0 || 79 | techniques.length === 0 || 80 | analytics.length === 0 || 81 | dataModel.length === 0 || 82 | sensors.length === 0) 83 | { 84 | return; 85 | } 86 | 87 | function buildNode(/*l, x, displayAccessor*/) { 88 | var l = arguments[0]; 89 | var x = arguments[1]; 90 | var displayAccessor = function (d) { return d.name; }; 91 | 92 | if (arguments.length === 3) { 93 | displayAccessor = arguments[2]; 94 | } 95 | 96 | var interval = 1.0 / l.length; 97 | var offset = interval / 2.0; 98 | for (var i=0; i < l.length; i++) { 99 | var node = {name: displayAccessor(l[i]), x: x, y: offset + interval * i}; 100 | vm.nodes.push(node); 101 | angular.extend(l[i], {node: node}); 102 | } 103 | } 104 | 105 | buildNode(groups, 0, function (g) { return g.aliases.join(', ');}); 106 | buildNode(techniques, 1, function (t) { return t.tactic + '/' + t.display_name; }); 107 | buildNode(analytics, 2, function (a) { return a.shortName; }); 108 | buildNode(dataModel, 3); 109 | buildNode(sensors, 4); 110 | vm.labels = ['Groups', 'Techniques', 'Analytics', 'Data Model', 'Sensors']; 111 | 112 | //groups -> techniques 113 | angular.forEach(groups, function (group) { 114 | angular.forEach(group.techniques, function (elem) { 115 | var targets = _.filter(techniques, {name: elem}); 116 | angular.forEach(targets, function (target) { 117 | vm.links.push({source: group.node, target: target.node}); 118 | }); 119 | }); 120 | }); 121 | 122 | angular.forEach(analytics, function (analytic) { 123 | angular.forEach(analytic.attack, function (attack) { 124 | angular.forEach(attack.tactics, function (tactic) { 125 | //analytics -> techniques 126 | var target = _.find(techniques, {name: attack.technique, tactic: tactic}); 127 | if (target !== undefined) { 128 | vm.links.push({source: analytic.node, target: target.node}); 129 | } 130 | }); 131 | }); 132 | angular.forEach(analytic.fields, function (field) { 133 | //analytics -> data 134 | var name = field.split('/')[0] + '/' + field.split('/')[1]; 135 | var target = _.find(dataModel, {name: name}); 136 | if (target !== undefined) { 137 | // Remove duplicates because we are compacting data 138 | if (undefined === _.find(vm.links, {source: analytic.node, target: target.node})) { 139 | vm.links.push({source: analytic.node, target: target.node}); 140 | } 141 | } 142 | }); 143 | }); 144 | 145 | //sensor -> data 146 | angular.forEach(sensors, function (sensor) { 147 | angular.forEach(sensor.fields, function (field) { 148 | var name = field.split('/')[0] + '/' + field.split('/')[1]; 149 | var target = _.find(dataModel, {name: name}); 150 | if (target !== undefined) { 151 | // Remove duplicates because we are compacting data 152 | if (undefined === _.find(vm.links, {source: sensor.node, target: target.node})) { 153 | vm.links.push({source: sensor.node, target: target.node}); 154 | } 155 | } 156 | }); 157 | }); 158 | } 159 | } 160 | })(); -------------------------------------------------------------------------------- /src/app/components/d3/test.css: -------------------------------------------------------------------------------- 1 | .link { 2 | fill: none; 3 | stroke-width: 1.5px; 4 | opacity: 0.3; 5 | } 6 | 7 | .axis, .node { 8 | stroke: #000; 9 | stroke-width: 1.5px; 10 | } 11 | 12 | .link.active { 13 | stroke-width: 2px; 14 | opacity: 1; 15 | } 16 | 17 | .link.selected { 18 | stroke-width: 2px; 19 | opacity: 1; 20 | } 21 | 22 | .link.unselected { 23 | visibility: hidden; 24 | } 25 | 26 | .node.active { 27 | stroke: red; 28 | stroke-width: 2px; 29 | } 30 | 31 | .node.selected { 32 | stroke-width: 2px; 33 | } 34 | 35 | .node.primary { 36 | stroke: yellow; 37 | } 38 | 39 | .node-label.primary { 40 | } 41 | 42 | .node.unselected { 43 | visibility: hidden; 44 | } 45 | 46 | .node-label.active:not(.selected) { 47 | font-size: 8px; 48 | } 49 | 50 | .node-label.unselected { 51 | visibility: hidden; 52 | } 53 | 54 | .node-label.invisible { 55 | visibility: hidden; 56 | } 57 | 58 | .node.invisible { 59 | visibility: hidden; 60 | } 61 | 62 | .button-label { 63 | fill: white; 64 | } -------------------------------------------------------------------------------- /src/app/components/grid/grid.controller.css: -------------------------------------------------------------------------------- 1 | .gray { 2 | background: #f5f5f5; } 3 | .green { 4 | background: #99ff9a; } 5 | .yellow { 6 | background: #ffff8d; } 7 | .blue { 8 | background: #84ffff; } 9 | .darkBlue { 10 | background: #80d8ff; } 11 | .deepBlue { 12 | background: #448aff; } 13 | .purple { 14 | background: #b388ff; } 15 | .lightPurple { 16 | background: #8c9eff; } 17 | .red { 18 | background: #ff4a60; } 19 | .pink { 20 | background: #ff80ab; } 21 | 22 | .text { 23 | overflow: hidden; 24 | text-overflow: ellipsis; 25 | display: -webkit-box; 26 | line-height: 16px; /* fallback */ 27 | max-height: 32px; /* fallback */ 28 | -webkit-line-clamp: 2; /* number of lines to show */ 29 | -webkit-box-orient: vertical; 30 | } 31 | 32 | .highlight { 33 | background: #ffff8d; 34 | } 35 | 36 | .autocomplete-custom-template li { 37 | border-bottom: 1px solid #ccc; 38 | } 39 | .autocomplete-custom-template li:last-child { 40 | border-bottom-width: 0; 41 | } 42 | 43 | .outline { 44 | outline: 3px solid #3f51bf; 45 | } -------------------------------------------------------------------------------- /src/app/components/grid/grid.controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('caret') 6 | .controller('GridController', GridController); 7 | 8 | /** @ngInject */ 9 | function GridController($scope, $timeout, $log, $mdDialog, $document, toastr, analyticLoader, _) { 10 | var vm = this; 11 | var defaultBackground = 'red'; 12 | vm.loading = true; 13 | vm.loadCAR = loadCAR; 14 | vm.analytics = []; 15 | vm.techniques = []; 16 | vm.groups = []; 17 | vm.grid = []; 18 | vm.gridLarge = true; 19 | vm.gridRatio = '7:3'; 20 | vm.gridStyle = {width: '100%'}; 21 | vm.gridColumns = 0; 22 | vm.showAdvanced = showAdvanced; 23 | vm.filter = ''; 24 | vm.groupSearch = groupSearch; 25 | vm.selectedGroup = ''; 26 | vm.clearAll = clearAll; 27 | vm.selectAll = selectAll; 28 | vm.selectedGroups = []; 29 | vm.unselectedGroups = []; 30 | vm.analyticMouseenter = analyticMouseenter; 31 | vm.analyticMouseleave = analyticMouseleave; 32 | vm.enableOutline = true; 33 | vm.sensors = []; 34 | 35 | var carHost = 'https://car.mitre.org'; 36 | var attackHost = 'https://attack.mitre.org'; 37 | 38 | function analyticMouseenter (analytic) { 39 | if (!vm.enableOutline) { 40 | return; 41 | } 42 | 43 | angular.forEach(analytic.attack, function (technique) { 44 | var name = technique.technique; 45 | angular.forEach(technique.tactics, function (tactic) { 46 | //find the technique 47 | var techObject = _.find(vm.grid, {name: name, tactic: tactic}); 48 | if (techObject !== undefined) { 49 | techObject.outline = 'outline'; 50 | } 51 | }); 52 | }); 53 | } 54 | 55 | function analyticMouseleave (analytic) { 56 | if (!vm.enableOutline) { 57 | return; 58 | } 59 | 60 | angular.forEach(analytic.attack, function (technique) { 61 | var name = technique.technique; 62 | angular.forEach(technique.tactics, function (tactic) { 63 | //find the technique 64 | var techObject = _.find(vm.grid, {name: name, tactic: tactic}); 65 | if (techObject !== undefined) { 66 | techObject.outline = ''; 67 | } 68 | }); 69 | }); 70 | } 71 | 72 | $scope.$watch(function () { 73 | return vm.analytics; 74 | }, changedAnalytics, true); 75 | 76 | $scope.$watch(function () { 77 | return vm.gridLarge; 78 | }, changedGridSize, true); 79 | 80 | $scope.$watch(function () { 81 | return vm.filter; 82 | }, changedFilter, true); 83 | 84 | $scope.$watch(function () { 85 | return vm.selectedGroups; 86 | }, changedGroups, true); 87 | 88 | loadCAR() 89 | 90 | function selectAll() { 91 | angular.forEach(vm.analytics, function (analytic) { 92 | if (analytic.visible) { 93 | analytic.active = true; 94 | } 95 | }); 96 | } 97 | 98 | function clearAll() { 99 | angular.forEach(vm.analytics, function (analytic) { 100 | if (analytic.visible) { 101 | analytic.active = false; 102 | } 103 | }); 104 | } 105 | 106 | function changedGroups(newGroups) { 107 | vm.unselectedGroups = _.difference(vm.groups, newGroups); 108 | 109 | angular.forEach(vm.grid, function (technique) { 110 | if (newGroups.length === 0) { 111 | technique.opacity = '1.0'; 112 | } else if (_.every(newGroups, function (group) { 113 | return -1 === _.indexOf(group.techniques, technique.name); 114 | })) { 115 | technique.opacity = '0.3'; 116 | } else { 117 | technique.opacity = '1.0'; 118 | } 119 | }); 120 | 121 | changedFilter(vm.filter); 122 | } 123 | 124 | function groupSearch (query) { 125 | var groups = vm.unselectedGroups; 126 | 127 | if (query) 128 | { 129 | return groups.filter( function (value) { 130 | if (angular.lowercase(value.name).indexOf(angular.lowercase(query)) === 0) { 131 | return true; 132 | } 133 | angular.forEach(value.aliases, function (alias) { 134 | if (angular.lowercase(alias).indexOf(angular.lowercase(query)) === 0) { 135 | return true; 136 | } 137 | }); 138 | return false; 139 | }); 140 | } else { 141 | return groups; 142 | } 143 | } 144 | 145 | function showAdvanced(ev, name) { 146 | if (name === '') { 147 | return; 148 | } 149 | $mdDialog.show({ 150 | templateUrl: 'app/components/analyticDialog/analyticDialog.tmpl.html', 151 | parent: angular.element($document.body), 152 | controller: buildDialogController(name), 153 | targetEvent: ev, 154 | clickOutsideToClose:true 155 | }) 156 | .then(function(answer) { 157 | $scope.status = 'You said the information was "' + answer + '".'; 158 | }, function() { 159 | $scope.status = 'You cancelled the dialog.'; 160 | }); 161 | } 162 | 163 | function buildDialogController(name) { 164 | return /** @ngInject */ function ($sce, $scope, $mdDialog) { 165 | $scope.name = name; 166 | var temp = name.split("/"); 167 | name = temp[0].toLowerCase() + 's/' + temp[1]; 168 | $scope.uri = $sce.trustAsResourceUrl(attackHost + "/techniques/" + encodeURIComponent(name.split('/')[1]) + '/'); 169 | $scope.hide = function() { 170 | $mdDialog.hide(); 171 | }; 172 | $scope.cancel = function() { 173 | $mdDialog.cancel(); 174 | }; 175 | $scope.answer = function(answer) { 176 | $mdDialog.hide(answer); 177 | }; 178 | }; 179 | } 180 | 181 | function changedGridSize(newValue) { 182 | if (newValue) { 183 | // Large grid 184 | vm.gridRatio = '7:3'; 185 | vm.grid = produceGrid(vm.techniques, vm.gridLarge); 186 | vm.gridStyle = {width: '100%'}; 187 | } else { 188 | // small grid 189 | vm.gridRatio = '1:1'; 190 | vm.grid = produceGrid(vm.techniques, vm.gridLarge); 191 | vm.gridStyle = {width: '200px'}; 192 | } 193 | changedAnalytics(vm.analytics); 194 | } 195 | 196 | function changedAnalytics(newValue) { 197 | // compute analytic coverage 198 | var actives = _.filter(newValue, 'active'); 199 | //reset the background of all techniques 200 | angular.forEach(vm.grid, function (technique) { 201 | technique.background = technique.defaultBackground; 202 | }); 203 | angular.forEach(actives, function (active) { 204 | angular.forEach(active.attack, function (technique) { 205 | var name = technique.technique; 206 | var coverage = technique.coverage; 207 | angular.forEach(technique.tactics, function (tactic) { 208 | //find the technique 209 | var techObject = _.find(vm.grid, {name: name, tactic: tactic}); 210 | if (techObject !== undefined) { 211 | if ((techObject.background === 'red' && (coverage === 'Partial' || coverage === 'Complete' )) || 212 | (techObject.background === 'yellow' && coverage === 'Complete' ) ) { 213 | techObject.background = coverage === 'Partial' ? 'yellow' : 'green'; 214 | } 215 | } 216 | }); 217 | }); 218 | }); 219 | 220 | changedGroups(vm.selectedGroups); 221 | } 222 | 223 | function changedFilter(newValue) { 224 | // take the filter value and update 225 | newValue = newValue.toLowerCase(); 226 | var selectedGroupsTechniques; 227 | if (vm.selectedGroups.length !== 0) { 228 | // does any selected group use any of these techniques? 229 | selectedGroupsTechniques = _.flatten(_.map(vm.selectedGroups, function (group) {return group.techniques;})); 230 | } 231 | 232 | for (var i = 0; i < vm.analytics.length; i++) { 233 | var analytic = vm.analytics[i]; 234 | // If there are any active groups, hide analytics that aren't in that group 235 | if (selectedGroupsTechniques !== undefined) { 236 | // what techniques does this analytic detect? 237 | var techniques = _.map(analytic.attack, 'technique'); 238 | 239 | // does any selected group use any of these techniques? 240 | if (_.intersection(techniques, selectedGroupsTechniques).length === 0) { 241 | analytic.visible = false; 242 | continue; 243 | } 244 | } 245 | 246 | var nameMatch = analytic.name.toLowerCase().search(newValue); 247 | var shortNameMatch = analytic.shortName.toLowerCase().search(newValue); 248 | 249 | analytic.visible = (newValue === '' || nameMatch !== -1 || shortNameMatch !== -1); 250 | } 251 | } 252 | 253 | function loadCAR() { 254 | analyticLoader.getAnalytics(carHost) 255 | .then(function (response) { 256 | if(response) { 257 | vm.analytics = _.map(response, function (r) { return angular.extend(r, {active: false, visible: true}); }); 258 | toastr.info('Loaded analytics'); 259 | } 260 | }); 261 | 262 | analyticLoader.getTechniques() 263 | .then(function (response) { 264 | if(response) { 265 | vm.techniques = _.map(response, function (r) { return angular.extend(r, {background: defaultBackground}); }); 266 | vm.grid = produceGrid(vm.techniques, vm.gridLarge); 267 | toastr.info('Loaded techniques'); 268 | } 269 | }); 270 | 271 | analyticLoader.getGroups() 272 | .then(function (response) { 273 | if(response) { 274 | vm.groups = _.map(response, function (r) { return angular.extend(r, {active: false, displayName: groupDisplayName(r)}); }); 275 | vm.unselectedGroups = vm.groups; 276 | toastr.info('Loaded groups'); 277 | } 278 | }); 279 | 280 | analyticLoader.getSensors(carHost) 281 | .then(function (response) { 282 | if(response) { 283 | vm.sensors = response; 284 | toastr.info('Loaded sensors'); 285 | } 286 | }); 287 | } 288 | 289 | function groupDisplayName(group) { 290 | return group.name + ': ' + group.aliases.join(', '); 291 | } 292 | 293 | function produceGrid(techniques, large) { 294 | var dedup = []; 295 | angular.forEach(techniques, function (technique) { 296 | var name = technique.name; 297 | var ID = technique.ID; 298 | var display_name = technique.display_name; 299 | angular.forEach(technique.tactics, function (tactic) { 300 | dedup.push({name:name, display_name: display_name, tactic: tactic, ID:ID, defaultBackground: defaultBackground, background: defaultBackground}); 301 | }); 302 | }); 303 | 304 | var columns = []; 305 | angular.forEach(dedup, function (technique) { 306 | var col = _.find(columns, {name: technique.tactic}); 307 | if (col === undefined) { 308 | col = {name: technique.tactic, techniques: []}; 309 | columns.push(col); 310 | } 311 | col.techniques.push(technique); 312 | }); 313 | 314 | //Sort columns in the order: Initial Access, Execution, Persistence, Privilege Escalation, Defense Evasion, Credential Access, Discovery, Lateral Movement, Collection, Exfiltration, Command And Control 315 | if(columns.length > 0) { 316 | columns = _.map(['Initial Access', 'Execution', 'Persistence', 'Privilege Escalation', 'Defense Evasion', 'Credential Access', 'Discovery', 'Lateral Movement', 'Collection', 'Exfiltration', 'Command And Control'], function (i) { 317 | var index = _.findIndex(columns, {'name': i}); 318 | return columns[index]; 319 | }); 320 | } 321 | 322 | var linearized = []; 323 | var longestLength = 0; 324 | angular.forEach(columns, function (col) { 325 | longestLength = col.techniques.length > longestLength ? col.techniques.length : longestLength; 326 | }); 327 | 328 | if (large) { 329 | angular.forEach(columns, function (col) { 330 | linearized.push({display_name: col.name, background: 'white', defaultBackground: "white"}); 331 | }); 332 | } 333 | 334 | vm.gridColumns = columns.length; 335 | 336 | for (var i=0; i < longestLength; i++) { 337 | for (var j=0; j < columns.length; j++) { 338 | if (i >= columns[j].techniques.length) { 339 | linearized.push({name: "", defaultBackground: "white", background: 'white', outline: ''}); 340 | } else { 341 | linearized.push(columns[j].techniques[i]); 342 | } 343 | } 344 | } 345 | 346 | if(linearized.length > 0) { 347 | vm.loading = false; 348 | } 349 | 350 | return linearized; 351 | } 352 | 353 | function generateCSV(groups, techniques, analytics, data, sensors) { 354 | var output = []; 355 | output.push(generateCSV("groups", groups)); 356 | output.push(generateCSV("techniques", techniques)); 357 | output.push(generateCSV("analytics", analytics)); 358 | output.push(generateCSV("data", data)); 359 | output.push(generateCSV("sensors", sensors)); 360 | return "".join(output); 361 | } 362 | 363 | function _generateCSV(name, obj_list) { 364 | var output = []; 365 | var id = 0; 366 | output.push("# " + name + "\n"); 367 | output.push('id'); 368 | var k = obj_list[0].keys(); 369 | for (var i; i < k.length; i++) { 370 | output.push(',' + k[i]); 371 | } 372 | output.push("\n"); 373 | angular.forEach(obj_list, function (obj) { 374 | output.push(id.toString() + ','); 375 | for (var i; i < k.length; i++) { 376 | output.push(obj[k[i]]); 377 | } 378 | output.push('\n'); 379 | }); 380 | return output; 381 | } 382 | } 383 | })(); 384 | -------------------------------------------------------------------------------- /src/app/components/grid/grid.tmpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | Detailed grid 7 | 8 | 9 | Enable outlines 10 | 11 |
12 | 13 | 22 | 23 | {{group.displayName}} 24 | 25 | 26 | No groups matching "{{main.searchText}}" were found. 27 | 28 | 29 | 30 | 31 | {{$chip.displayName}} 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 |

Analytics

41 |
42 |
43 | Select All 44 |
45 |
46 | Clear All 47 |
48 |
49 |
50 |
51 |
52 | 53 | 54 | 59 |
60 |

{{analytic.shortName}}

61 |

{{analytic.name}}

62 |
63 | 64 |
65 |
66 |
67 |
68 |
69 |
70 | 73 |
Loading...
74 |
75 | 81 | 88 | 89 | {{ tile.display_name }} 90 | 91 |

{{ tile.display_name }}

92 |
93 |
94 |
95 |
-------------------------------------------------------------------------------- /src/app/components/lodash/lodash.service.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('lodash', []) 6 | .factory('_', ['$window', function($window) { 7 | return $window._; // assumes underscore has already been loaded on the page 8 | }]); 9 | })(); 10 | -------------------------------------------------------------------------------- /src/app/components/navbar/navbar.css: -------------------------------------------------------------------------------- 1 | .acme-navbar-text { 2 | color: white; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/components/navbar/navbar.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('caret') 6 | .directive('acmeNavbar', acmeNavbar); 7 | 8 | /** @ngInject */ 9 | function acmeNavbar($document, $window, $mdDialog, $q, analyticLoader) { 10 | var directive = { 11 | restrict: 'E', 12 | templateUrl: 'app/components/navbar/navbar.html', 13 | scope: {}, 14 | controller: NavbarController, 15 | controllerAs: 'vm', 16 | bindToController: true 17 | }; 18 | 19 | return directive; 20 | 21 | /** @ngInject */ 22 | function NavbarController() { 23 | var vm = this; 24 | 25 | vm.showAbout = function (ev) { 26 | $mdDialog.show({ 27 | templateUrl: 'app/components/about/about.tmpl.html', 28 | parent: angular.element($document.body), 29 | controller: 'AboutController', 30 | controllerAs: 'vm', 31 | targetEvent: ev, 32 | clickOutsideToClose:true 33 | }); 34 | }; 35 | 36 | vm.downloadURL = ""; 37 | downloadData(); 38 | 39 | function downloadData () { 40 | var carHost = 'https://car.mitre.org'; 41 | var analytics = []; 42 | var techniques = []; 43 | var groups = []; 44 | var sensors = []; 45 | var dataModel = []; 46 | 47 | loadCAR(); 48 | 49 | function loadCAR() { 50 | var all = []; 51 | 52 | all.push(analyticLoader.getAnalytics(carHost) 53 | .then(function (response) { 54 | analytics = response; 55 | })); 56 | all.push(analyticLoader.getTechniques() 57 | .then(function (response) { 58 | techniques = response; 59 | })); 60 | 61 | all.push(analyticLoader.getGroups() 62 | .then(function (response) { 63 | groups = response; 64 | })); 65 | 66 | all.push(analyticLoader.getSensors(carHost) 67 | .then(function (response) { 68 | sensors = response; 69 | })); 70 | 71 | all.push(analyticLoader.getDataModel(carHost) 72 | .then(function (response) { 73 | dataModel = response; 74 | })); 75 | 76 | $q.all(all).then(function () { 77 | var downloadObject = {analytics: analytics, techniques: techniques, groups: groups, sensors: sensors, dataModel: dataModel}; 78 | var b = new $window.Blob([angular.toJson(downloadObject, true)], {type: 'text/json'} ); 79 | vm.downloadURL = $window.URL.createObjectURL(b); 80 | }); 81 | } 82 | } 83 | } 84 | } 85 | })(); 86 | -------------------------------------------------------------------------------- /src/app/components/navbar/navbar.html: -------------------------------------------------------------------------------- 1 | 2 | Caret 3 | Download Data 4 | About 5 |
6 |
7 | 8 | Version 0.0.8 9 | 10 |
11 | -------------------------------------------------------------------------------- /src/app/index.config.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('caret') 6 | .config(config); 7 | 8 | /** @ngInject */ 9 | function config($logProvider, $compileProvider, toastr) { 10 | // Enable log 11 | $logProvider.debugEnabled(true); 12 | 13 | // Enable blob: 14 | $compileProvider.aHrefSanitizationWhitelist(/(http|https|blob)/); 15 | 16 | // Set options third-party lib 17 | toastr.options.timeOut = 3000; 18 | toastr.options.positionClass = 'toast-top-right'; 19 | toastr.options.preventDuplicates = true; 20 | toastr.options.progressBar = false; 21 | } 22 | })(); 23 | -------------------------------------------------------------------------------- /src/app/index.constants.js: -------------------------------------------------------------------------------- 1 | /* global toastr:false */ 2 | (function() { 3 | 'use strict'; 4 | 5 | angular 6 | .module('caret') 7 | .constant('toastr', toastr); 8 | 9 | })(); 10 | -------------------------------------------------------------------------------- /src/app/index.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto Slab'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Roboto Slab Regular'), 6 | local('RobotoSlab-Regular'), 7 | url(/assets/fonts/RobotoSlab-Regular.woff) format('woff'); 8 | } 9 | @font-face { 10 | font-family: 'Roboto Slab'; 11 | font-style: normal; 12 | font-weight: 700; 13 | src: local('Roboto Slab Bold'), 14 | local('RobotoSlab-Bold'), 15 | url(/assets/fonts/RobotoSlab-Bold.woff) format('woff'); 16 | } 17 | @font-face { 18 | font-family: 'Material Icons'; 19 | font-style: normal; 20 | font-weight: 400; 21 | src: url(/assets/fonts/MaterialIcons-Regular.eot); /* For IE6-8 */ 22 | src: local('Material Icons'), 23 | local('MaterialIcons-Regular'), 24 | url(/assets/fonts/MaterialIcons-Regular.woff2) format('woff2'), 25 | url(/assets/fonts/MaterialIcons-Regular.woff) format('woff'), 26 | url(/assets/fonts/MaterialIcons-Regular.ttf) format('truetype'); 27 | } 28 | .material-icons { 29 | font-family: 'Material Icons'; 30 | font-weight: normal; 31 | font-style: normal; 32 | font-size: 24px; /* Preferred icon size */ 33 | display: inline-block; 34 | width: 1em; 35 | height: 1em; 36 | line-height: 1; 37 | text-transform: none; 38 | letter-spacing: normal; 39 | word-wrap: normal; 40 | white-space: nowrap; 41 | direction: ltr; 42 | 43 | /* Support for all WebKit browsers. */ 44 | -webkit-font-smoothing: antialiased; 45 | /* Support for Safari and Chrome. */ 46 | text-rendering: optimizeLegibility; 47 | 48 | /* Support for Firefox. */ 49 | -moz-osx-font-smoothing: grayscale; 50 | 51 | /* Support for IE. */ 52 | font-feature-settings: 'liga'; 53 | } 54 | 55 | html { 56 | font-family: 'Roboto Slab', serif; 57 | } 58 | 59 | [layout=row] { 60 | flex-direction: row; 61 | } 62 | 63 | .browsehappy { 64 | margin: 0.2em 0; 65 | background: #ccc; 66 | color: #000; 67 | padding: 0.2em 0; 68 | } 69 | 70 | .thumbnail { 71 | height: 200px; 72 | } 73 | 74 | .thumbnail img.pull-right { 75 | width: 50px; 76 | } 77 | 78 | md-toolbar.md-default-theme { 79 | background-color: black; 80 | } 81 | 82 | section.jumbotron { 83 | margin-bottom: 30px; 84 | padding: 1px 30px; 85 | background-color: #5aadbb; 86 | text-align: center; 87 | 88 | color: white; 89 | } 90 | 91 | section.jumbotron h1 { 92 | font-size: 3em; 93 | } 94 | 95 | .techs { 96 | display: flex; 97 | flex-flow: row wrap; 98 | } 99 | 100 | .techs md-card { 101 | width: 30%; 102 | } 103 | 104 | .techs md-card img.pull-right { 105 | float: right; 106 | width: 50px; 107 | } 108 | 109 | /* -----Loader-Spinner------ */ 110 | .lds-facebook { 111 | display: inline-block; 112 | position: relative; 113 | margin: 20% 0 0 43%; 114 | width: 64px; 115 | height: 64px; 116 | } 117 | .lds-facebook div { 118 | display: inline-block; 119 | position: absolute; 120 | left: 6px; 121 | width: 13px; 122 | background: #FF4081; 123 | animation: lds-facebook 0.8s cubic-bezier(0, 0.5, 0.5, 1) infinite; 124 | } 125 | .lds-facebook div:nth-child(1) { 126 | left: 6px; 127 | animation-delay: -0.24s; 128 | } 129 | .lds-facebook div:nth-child(2) { 130 | left: 26px; 131 | animation-delay: -0.12s; 132 | } 133 | .lds-facebook div:nth-child(3) { 134 | left: 45px; 135 | animation-delay: 0; 136 | } 137 | @keyframes lds-facebook { 138 | 0% { 139 | top: 6px; 140 | height: 85px; 141 | } 142 | 50%, 100% { 143 | top: 19px; 144 | height: 40px; 145 | } 146 | } -------------------------------------------------------------------------------- /src/app/index.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('caret', ['ngAnimate', 'ngCookies', 'ngSanitize', 'ngRoute', 'ngMaterial', 'lodash', 'd3']); 6 | 7 | })(); 8 | -------------------------------------------------------------------------------- /src/app/index.route.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('caret') 6 | .config(routeConfig); 7 | 8 | function routeConfig($routeProvider) { 9 | $routeProvider 10 | .when('/', { 11 | templateUrl: 'app/main/main.html', 12 | controller: 'MainController', 13 | controllerAs: 'main' 14 | }) 15 | .otherwise({ 16 | redirectTo: '/' 17 | }); 18 | } 19 | 20 | })(); 21 | -------------------------------------------------------------------------------- /src/app/index.run.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('caret') 6 | .run(runBlock); 7 | 8 | /** @ngInject */ 9 | function runBlock($log) { 10 | 11 | $log.debug('runBlock end'); 12 | } 13 | 14 | })(); 15 | -------------------------------------------------------------------------------- /src/app/main/main.controller.css: -------------------------------------------------------------------------------- 1 | .gray { 2 | background: #f5f5f5; } 3 | .green { 4 | background: #99ff9a; } 5 | .yellow { 6 | background: #ffff8d; } 7 | .blue { 8 | background: #84ffff; } 9 | .darkBlue { 10 | background: #80d8ff; } 11 | .deepBlue { 12 | background: #448aff; } 13 | .purple { 14 | background: #b388ff; } 15 | .lightPurple { 16 | background: #8c9eff; } 17 | .red { 18 | background: #ff8a80; } 19 | .pink { 20 | background: #ff80ab; } 21 | 22 | .footer { 23 | background: #3f51bf; 24 | color: rgba(255,255,255,0.87); 25 | } 26 | 27 | .footer p { 28 | text-align: center; 29 | max-width: 100%; 30 | } -------------------------------------------------------------------------------- /src/app/main/main.controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('caret') 6 | .controller('MainController', MainController); 7 | 8 | /** @ngInject */ 9 | function MainController() { 10 | return; 11 | } 12 | 13 | })(); 14 | -------------------------------------------------------------------------------- /src/app/main/main.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 | 13 | 14 |
15 | 21 |
22 |
23 |
24 |
25 | 32 |
-------------------------------------------------------------------------------- /src/assets/fonts/MaterialIcons-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre-attack/caret/a9db8c8636d8e71cc30780669866524d6b30d0f0/src/assets/fonts/MaterialIcons-Regular.eot -------------------------------------------------------------------------------- /src/assets/fonts/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre-attack/caret/a9db8c8636d8e71cc30780669866524d6b30d0f0/src/assets/fonts/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /src/assets/fonts/MaterialIcons-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre-attack/caret/a9db8c8636d8e71cc30780669866524d6b30d0f0/src/assets/fonts/MaterialIcons-Regular.woff -------------------------------------------------------------------------------- /src/assets/fonts/MaterialIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre-attack/caret/a9db8c8636d8e71cc30780669866524d6b30d0f0/src/assets/fonts/MaterialIcons-Regular.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/RobotoSlab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre-attack/caret/a9db8c8636d8e71cc30780669866524d6b30d0f0/src/assets/fonts/RobotoSlab-Bold.woff -------------------------------------------------------------------------------- /src/assets/fonts/RobotoSlab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre-attack/caret/a9db8c8636d8e71cc30780669866524d6b30d0f0/src/assets/fonts/RobotoSlab-Regular.woff -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | caret 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | --------------------------------------------------------------------------------