├── client ├── src │ ├── components │ │ ├── Details │ │ │ ├── HeaderSection.less │ │ │ ├── IPDetails.less │ │ │ ├── FileDetails.less │ │ │ ├── URLDetails.less │ │ │ ├── Details.test.js │ │ │ ├── ReputationHeader.less │ │ │ ├── HeaderSection.js │ │ │ ├── ReputationHeader.js │ │ │ ├── Details.less │ │ │ ├── Details.js │ │ │ ├── URLDetails.js │ │ │ └── IPDetails.js │ │ ├── UIComponents │ │ │ ├── Table.less │ │ │ ├── ExpandableHeader.less │ │ │ ├── ExpandableHeader.js │ │ │ └── Table.js │ │ ├── variables.less │ │ └── Application.less │ ├── utils │ │ ├── constants.js │ │ ├── api.js │ │ └── utils.js │ ├── index.css │ ├── index.js │ └── serviceWorker.js ├── public │ ├── favicon.ico │ ├── img │ │ ├── 404.png │ │ ├── fav.png │ │ ├── favicon.ico │ │ ├── icon1.png │ │ ├── icon3-2.png │ │ ├── icon3.png │ │ ├── icon4.png │ │ ├── icon5.png │ │ ├── icon6.png │ │ ├── list1.png │ │ ├── list2.png │ │ ├── list3.png │ │ ├── list4.png │ │ ├── videobg.mp4 │ │ ├── 2-layers.png │ │ ├── 6-layers.png │ │ ├── slack_rgb.png │ │ ├── arrowSprite.png │ │ ├── bg-demisto.jpg │ │ ├── dbot-banner.png │ │ ├── demisto-logo.png │ │ ├── shots │ │ │ ├── shot1.jpg │ │ │ ├── shot2.jpg │ │ │ ├── shot3.jpg │ │ │ ├── shot4.jpg │ │ │ ├── shot5.jpg │ │ │ └── macbookpro.png │ │ ├── analysis-image.png │ │ ├── centered-image.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── iphone-minimal.png │ │ ├── apple-touch-icon.png │ │ ├── favicon │ │ │ ├── demisto.png │ │ │ ├── D icon 3232.png │ │ │ ├── D icon 5757.png │ │ │ ├── D icon 7272.png │ │ │ ├── D icon 9696.png │ │ │ ├── D icon 114114.png │ │ │ └── D icon 144144.png │ │ ├── slack-title-image.png │ │ ├── slackscreenshot.png │ │ ├── versions │ │ │ └── hipchat.png │ │ ├── android-chrome-144x144.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-36x36.png │ │ ├── android-chrome-48x48.png │ │ ├── android-chrome-72x72.png │ │ ├── android-chrome-96x96.png │ │ ├── apple-touch-icon-57x57.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-72x72.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon-114x114.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-144x144.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ └── apple-touch-icon-precomposed.png │ ├── css │ │ └── assets │ │ │ └── fonts │ │ │ ├── icons.eot │ │ │ ├── icons.ttf │ │ │ ├── icons.woff │ │ │ └── icons.woff2 │ ├── fonts │ │ ├── themovation-icons.eot │ │ ├── themovation-icons.ttf │ │ ├── themovation-icons.woff │ │ └── themovation-icons.svg │ ├── js │ │ ├── skip-link-focus-fix.js │ │ └── navigation.js │ ├── 404.html │ └── details.html ├── config │ ├── jest │ │ ├── cssTransform.js │ │ └── fileTransform.js │ ├── paths.js │ ├── env.js │ └── webpackDevServer.config.js ├── scripts │ ├── test.js │ ├── start.js │ └── build.js ├── README.md └── package.json ├── etc ├── teams.sql ├── Dockerfile.bot ├── Dockerfile.dedup ├── Dockerfile.run ├── worker-run.sh ├── Dockerfile.single ├── Dockerfile.worker └── Dockerfile.build ├── circle.yml ├── slack ├── conversations_test.go ├── conversations.go └── slack.go ├── web ├── error_test.go ├── banned_test.go ├── context.go ├── error.go ├── work.go ├── confhandlers.go ├── router.go └── middleware.go ├── bot ├── noclamav.go └── clamav.go ├── .hooks └── pre-commit ├── util ├── object_test.go ├── encryption_test.go ├── util_test.go ├── logger.go ├── object.go ├── encryption.go └── util.go ├── tools ├── dumper │ └── dumper.go ├── tokencrypt │ └── encryptor.go ├── crypt │ └── crypt.go └── pscleaner │ └── pscleaner.go ├── queue ├── db_test.go ├── queue.go └── db.go ├── LICENSE ├── domain ├── configuration_test.go ├── stats.go ├── configuration.go └── user.go ├── .gitignore ├── circle-test.sh ├── alfred.go ├── README.md ├── autofocus └── af.go ├── repo └── repo_test.go └── conf └── conf.go /client/src/components/Details/HeaderSection.less: -------------------------------------------------------------------------------- 1 | @import url('../Application.less'); 2 | -------------------------------------------------------------------------------- /client/src/components/Details/IPDetails.less: -------------------------------------------------------------------------------- 1 | 2 | .ip-details { 3 | width: 100%; 4 | } 5 | -------------------------------------------------------------------------------- /client/src/components/Details/FileDetails.less: -------------------------------------------------------------------------------- 1 | 2 | .file-details { 3 | width: 100%; 4 | } 5 | -------------------------------------------------------------------------------- /client/src/components/Details/URLDetails.less: -------------------------------------------------------------------------------- 1 | 2 | .url-details { 3 | width: 100%; 4 | } 5 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/img/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/404.png -------------------------------------------------------------------------------- /client/public/img/fav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/fav.png -------------------------------------------------------------------------------- /client/public/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/favicon.ico -------------------------------------------------------------------------------- /client/public/img/icon1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/icon1.png -------------------------------------------------------------------------------- /client/public/img/icon3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/icon3-2.png -------------------------------------------------------------------------------- /client/public/img/icon3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/icon3.png -------------------------------------------------------------------------------- /client/public/img/icon4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/icon4.png -------------------------------------------------------------------------------- /client/public/img/icon5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/icon5.png -------------------------------------------------------------------------------- /client/public/img/icon6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/icon6.png -------------------------------------------------------------------------------- /client/public/img/list1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/list1.png -------------------------------------------------------------------------------- /client/public/img/list2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/list2.png -------------------------------------------------------------------------------- /client/public/img/list3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/list3.png -------------------------------------------------------------------------------- /client/public/img/list4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/list4.png -------------------------------------------------------------------------------- /client/public/img/videobg.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/videobg.mp4 -------------------------------------------------------------------------------- /client/public/img/2-layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/2-layers.png -------------------------------------------------------------------------------- /client/public/img/6-layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/6-layers.png -------------------------------------------------------------------------------- /client/public/img/slack_rgb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/slack_rgb.png -------------------------------------------------------------------------------- /client/public/img/arrowSprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/arrowSprite.png -------------------------------------------------------------------------------- /client/public/img/bg-demisto.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/bg-demisto.jpg -------------------------------------------------------------------------------- /client/public/img/dbot-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/dbot-banner.png -------------------------------------------------------------------------------- /client/public/img/demisto-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/demisto-logo.png -------------------------------------------------------------------------------- /client/public/img/shots/shot1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/shots/shot1.jpg -------------------------------------------------------------------------------- /client/public/img/shots/shot2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/shots/shot2.jpg -------------------------------------------------------------------------------- /client/public/img/shots/shot3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/shots/shot3.jpg -------------------------------------------------------------------------------- /client/public/img/shots/shot4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/shots/shot4.jpg -------------------------------------------------------------------------------- /client/public/img/shots/shot5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/shots/shot5.jpg -------------------------------------------------------------------------------- /client/public/img/analysis-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/analysis-image.png -------------------------------------------------------------------------------- /client/public/img/centered-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/centered-image.png -------------------------------------------------------------------------------- /client/public/img/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/favicon-16x16.png -------------------------------------------------------------------------------- /client/public/img/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/favicon-32x32.png -------------------------------------------------------------------------------- /client/public/img/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/favicon-96x96.png -------------------------------------------------------------------------------- /client/public/img/iphone-minimal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/iphone-minimal.png -------------------------------------------------------------------------------- /client/public/img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/apple-touch-icon.png -------------------------------------------------------------------------------- /client/public/img/favicon/demisto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/favicon/demisto.png -------------------------------------------------------------------------------- /client/public/img/shots/macbookpro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/shots/macbookpro.png -------------------------------------------------------------------------------- /client/public/img/slack-title-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/slack-title-image.png -------------------------------------------------------------------------------- /client/public/img/slackscreenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/slackscreenshot.png -------------------------------------------------------------------------------- /client/public/img/versions/hipchat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/versions/hipchat.png -------------------------------------------------------------------------------- /client/public/css/assets/fonts/icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/css/assets/fonts/icons.eot -------------------------------------------------------------------------------- /client/public/css/assets/fonts/icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/css/assets/fonts/icons.ttf -------------------------------------------------------------------------------- /client/public/css/assets/fonts/icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/css/assets/fonts/icons.woff -------------------------------------------------------------------------------- /client/public/fonts/themovation-icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/fonts/themovation-icons.eot -------------------------------------------------------------------------------- /client/public/fonts/themovation-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/fonts/themovation-icons.ttf -------------------------------------------------------------------------------- /client/public/img/favicon/D icon 3232.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/favicon/D icon 3232.png -------------------------------------------------------------------------------- /client/public/img/favicon/D icon 5757.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/favicon/D icon 5757.png -------------------------------------------------------------------------------- /client/public/img/favicon/D icon 7272.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/favicon/D icon 7272.png -------------------------------------------------------------------------------- /client/public/img/favicon/D icon 9696.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/favicon/D icon 9696.png -------------------------------------------------------------------------------- /client/public/css/assets/fonts/icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/css/assets/fonts/icons.woff2 -------------------------------------------------------------------------------- /client/public/fonts/themovation-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/fonts/themovation-icons.woff -------------------------------------------------------------------------------- /client/public/img/android-chrome-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/android-chrome-144x144.png -------------------------------------------------------------------------------- /client/public/img/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/android-chrome-192x192.png -------------------------------------------------------------------------------- /client/public/img/android-chrome-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/android-chrome-36x36.png -------------------------------------------------------------------------------- /client/public/img/android-chrome-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/android-chrome-48x48.png -------------------------------------------------------------------------------- /client/public/img/android-chrome-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/android-chrome-72x72.png -------------------------------------------------------------------------------- /client/public/img/android-chrome-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/android-chrome-96x96.png -------------------------------------------------------------------------------- /client/public/img/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /client/public/img/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /client/public/img/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /client/public/img/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /client/public/img/favicon/D icon 114114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/favicon/D icon 114114.png -------------------------------------------------------------------------------- /client/public/img/favicon/D icon 144144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/favicon/D icon 144144.png -------------------------------------------------------------------------------- /client/public/img/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /client/public/img/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /client/public/img/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /client/public/img/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /client/public/img/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /client/src/components/UIComponents/Table.less: -------------------------------------------------------------------------------- 1 | @import url('../Application.less'); 2 | 3 | .custom-table { 4 | margin-top: 20px; 5 | } 6 | -------------------------------------------------------------------------------- /client/public/img/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisto/alfred/HEAD/client/public/img/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /etc/teams.sql: -------------------------------------------------------------------------------- 1 | select u.name, u.real_name, u.email, t.name, t.domain, t.created from users u, teams t where t.status = 0 and u.team = t.id order by t.created -------------------------------------------------------------------------------- /client/src/components/UIComponents/ExpandableHeader.less: -------------------------------------------------------------------------------- 1 | @import url('../Application.less'); 2 | @import url('../variables.less'); 3 | 4 | .expandable-header { 5 | cursor: pointer; 6 | font-size: large; 7 | color: @anchor-color; 8 | } 9 | -------------------------------------------------------------------------------- /client/src/components/variables.less: -------------------------------------------------------------------------------- 1 | // Colors 2 | @anchor-color: #4a90e2; 3 | 4 | @clean-rep-bg-color: rgba(0, 205, 51, 0.7); 5 | @dirty-rep-bg-color: rgba(255, 23, 68, 0.7); 6 | @unknown-rep-bg-color: rgba(197, 197, 197, 0.7); 7 | -------------------------------------------------------------------------------- /client/src/utils/constants.js: -------------------------------------------------------------------------------- 1 | export const API_RESPONSE_STATUS = { 2 | success: 'success', 3 | error: 'err', 4 | }; 5 | 6 | export const REPUTATION_RESULT = { 7 | clean: 0, 8 | dirty: 1, 9 | unknown: 2 10 | }; 11 | 12 | export const TABLE_DATE_FORMAT = 'MMMM Do YYYY, h:mm a'; 13 | 14 | -------------------------------------------------------------------------------- /etc/Dockerfile.bot: -------------------------------------------------------------------------------- 1 | FROM busybox:ubuntu-14.04 2 | MAINTAINER Slavik Markovich 3 | 4 | COPY ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 5 | COPY g-bot.conf /etc/g-bot.conf 6 | COPY alfred /bin/alfred 7 | COPY build-date / 8 | 9 | CMD alfred -conf=/etc/g-bot.conf -loglevel=debug 10 | -------------------------------------------------------------------------------- /etc/Dockerfile.dedup: -------------------------------------------------------------------------------- 1 | FROM busybox:ubuntu-14.04 2 | MAINTAINER Slavik Markovich 3 | 4 | COPY ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 5 | COPY g-dedup.conf /etc/g-dedup.conf 6 | COPY alfred /bin/alfred 7 | COPY build-date / 8 | 9 | CMD alfred -conf=/etc/g-dedup.conf -loglevel=debug 10 | -------------------------------------------------------------------------------- /client/src/components/Application.less: -------------------------------------------------------------------------------- 1 | 2 | .no-padding { 3 | padding: 0 !important; 4 | } 5 | 6 | .no-margin { 7 | margin: 0 !important; 8 | } 9 | 10 | .bold { 11 | font-weight: bold; 12 | } 13 | 14 | .ellipsis { 15 | text-overflow: ellipsis; 16 | white-space: nowrap; 17 | overflow: hidden; 18 | } 19 | -------------------------------------------------------------------------------- /client/src/components/Details/Details.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Details from './Details'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(
, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /etc/Dockerfile.run: -------------------------------------------------------------------------------- 1 | FROM busybox:ubuntu-14.04 2 | MAINTAINER Slavik Markovich 3 | 4 | EXPOSE 443 80 5 | 6 | COPY ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 7 | COPY g-web.conf /etc/g-web.conf 8 | COPY alfred /bin/alfred 9 | COPY build-date / 10 | 11 | CMD alfred -conf=/etc/g-web.conf -loglevel=debug 12 | -------------------------------------------------------------------------------- /client/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /client/src/components/Details/ReputationHeader.less: -------------------------------------------------------------------------------- 1 | @import url('../Application.less'); 2 | @import url('../variables.less'); 3 | 4 | .reputation-header { 5 | border-width: medium !important; 6 | width: 50%; 7 | margin: 15px auto !important; 8 | 9 | &.clean { 10 | background: @clean-rep-bg-color; 11 | } 12 | 13 | &.dirty { 14 | background: @dirty-rep-bg-color; 15 | } 16 | 17 | &.unknown { 18 | background: @unknown-rep-bg-color; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 0.12.0 4 | services: 5 | - docker 6 | pre: 7 | - bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer) 8 | - source $HOME/.gvm/scripts/gvm; gvm install go1.5 --binary 9 | 10 | dependencies: 11 | pre: 12 | - npm install --global gulp 13 | override: 14 | - echo "Overriding default dependencies" 15 | 16 | test: 17 | override: 18 | - bash circle-test.sh 19 | -------------------------------------------------------------------------------- /client/src/components/UIComponents/ExpandableHeader.js: -------------------------------------------------------------------------------- 1 | import './ExpandableHeader.less'; 2 | import React from 'react'; 3 | import classNames from 'classnames'; 4 | 5 | export function ExpandableHeader({ title, expand ,onClick}) { 6 | const iconClass = classNames('chevron', { down: expand, right: !expand } , 'icon'); 7 | 8 | return ( 9 |
10 | 11 | {title} 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /slack/conversations_test.go: -------------------------------------------------------------------------------- 1 | package slack 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestClient_Conversations(t *testing.T) { 10 | s := Client{Token: ""} 11 | conversations, err := s.Conversations("") 12 | assert.NoError(t, err) 13 | found := false 14 | for _, conversation := range conversations { 15 | if conversation.S("name") == "general" { 16 | found = true 17 | break 18 | } 19 | } 20 | assert.True(t, found) 21 | } 22 | -------------------------------------------------------------------------------- /etc/worker-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -m 3 | 4 | freshclam -d & 5 | /worker -conf /etc/g-worker.conf -loglevel=debug & 6 | 7 | pids=`jobs -p` 8 | 9 | exitcode=0 10 | 11 | function terminate() { 12 | trap "" CHLD 13 | 14 | for pid in $pids; do 15 | if ! kill -0 $pid 2>/dev/null; then 16 | wait $pid 17 | exitcode=$? 18 | fi 19 | done 20 | 21 | kill $pids 2>/dev/null 22 | } 23 | 24 | trap terminate CHLD 25 | wait 26 | 27 | exit $exitcode 28 | -------------------------------------------------------------------------------- /web/error_test.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "net/http/httptest" 5 | "testing" 6 | ) 7 | 8 | func TestWriteError(t *testing.T) { 9 | errors := []*Error{ErrBadRequest, ErrMissingPartRequest, ErrAuth, ErrCredentials, ErrNotAcceptable, 10 | ErrUnsupportedMediaType, ErrCSRF, ErrForbidden, ErrInternalServer} 11 | for _, e := range errors { 12 | w := httptest.NewRecorder() 13 | WriteError(w, e) 14 | if w.Code != e.Status { 15 | t.Fatalf("Wrong status code %d, expected %d", w.Code, e.Status) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /bot/noclamav.go: -------------------------------------------------------------------------------- 1 | // +build !clamav 2 | 3 | package bot 4 | 5 | import "github.com/Sirupsen/logrus" 6 | 7 | type clamEngine struct { 8 | } 9 | 10 | // newClamEngine creates an dummy engine 11 | func newClamEngine() (*clamEngine, error) { 12 | return &clamEngine{}, nil 13 | } 14 | 15 | // scan without ClamAV returns empty result 16 | func (ce *clamEngine) scan(filename string, b []byte) (string, error) { 17 | logrus.Debug("ClamAV is not configured to run") 18 | return "", nil 19 | } 20 | 21 | func (ce *clamEngine) close() { 22 | } 23 | -------------------------------------------------------------------------------- /.hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | fmtcount=`git ls-files | grep '.go$' | xargs gofmt -l 2>&1 | wc -l` 4 | if [ $fmtcount -gt 0 ]; then 5 | echo "Some files aren't formatted, please run 'go fmt ./...' to format your source code before committing" 6 | exit 1 7 | fi 8 | 9 | vetcount=`go vet ./... 2>&1 | wc -l` 10 | if [ $vetcount -gt 0 ]; then 11 | echo "Some files aren't passing vet heuristics, please run 'go vet ./...' to see the errors it flags and correct your source code before committing" 12 | exit 1 13 | fi 14 | exit 0 15 | -------------------------------------------------------------------------------- /client/src/components/Details/HeaderSection.js: -------------------------------------------------------------------------------- 1 | import './HeaderSection.less'; 2 | import React from 'react'; 3 | 4 | export function HeaderSection({ headers }) { 5 | return ( 6 |
7 | { 8 | headers.map(({ label, value }) => ( 9 |
10 |
{label}
11 |
{value}
12 |
13 | )) 14 | } 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import Details from './components/Details/Details'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(
, document.getElementById('details-container')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: http://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /util/object_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestObject_Get(t *testing.T) { 10 | r := Object(map[string]interface{}{"a": 1, "b": "2", "c": map[string]interface{}{"x": "11", "y": map[string]interface{}{"z": "111"}}}) 11 | assert.Equal(t, 1, r.Get("a")) 12 | assert.Equal(t, "2", r.Get("b")) 13 | assert.Nil(t, r.Get("xxx")) 14 | assert.Equal(t, "11", r.Get("c.x")) 15 | assert.Equal(t, "111", r.Get("c.y.z")) 16 | r1 := r.O("c.y") 17 | assert.Equal(t, "111", r1.Get("z")) 18 | } 19 | -------------------------------------------------------------------------------- /web/banned_test.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsBanned(t *testing.T) { 8 | bannedSamples := []string{"2.144.0.0:0", "2.147.100.100:0", "2.147.255.255:0", "5.112.0.0:0", "5.127.255.255:0"} 9 | cleanSamples := []string{"1.1.1.1:0", "73.231.0.156:0", "104.197.111.48:0"} 10 | 11 | for _, ip := range bannedSamples { 12 | if !isBanned(ip) { 13 | t.Errorf("IP %s was not banned\n", ip) 14 | } 15 | } 16 | for _, ip := range cleanSamples { 17 | if isBanned(ip) { 18 | t.Errorf("IP %s was banned\n", ip) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /web/context.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/demisto/alfred/bot" 7 | "github.com/demisto/alfred/queue" 8 | "github.com/demisto/alfred/repo" 9 | ) 10 | 11 | // AppContext holds the web context for the handlers 12 | type AppContext struct { 13 | r *repo.MySQL 14 | q queue.Queue 15 | b *bot.Bot 16 | } 17 | 18 | // NewContext creates a new context 19 | func NewContext(r *repo.MySQL, q queue.Queue, b *bot.Bot) *AppContext { 20 | return &AppContext{r: r, q: q, b: b} 21 | } 22 | 23 | type session struct { 24 | User string `json:"user"` 25 | UserID string `json:"userId"` 26 | When time.Time `json:"when"` 27 | } 28 | -------------------------------------------------------------------------------- /tools/dumper/dumper.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/boltdb/bolt" 9 | ) 10 | 11 | func main() { 12 | file := flag.String("file", "alfred.db", "the database file to dump") 13 | flag.Parse() 14 | db, err := bolt.Open(*file, 0600, &bolt.Options{Timeout: 1 * time.Second}) 15 | if err != nil { 16 | panic(err) 17 | } 18 | err = db.View(func(tx *bolt.Tx) error { 19 | return tx.ForEach(func(name []byte, b *bolt.Bucket) error { 20 | fmt.Printf("%s:\n", string(name)) 21 | return b.ForEach(func(k []byte, v []byte) error { 22 | fmt.Printf("%s:\n", string(v)) 23 | return nil 24 | }) 25 | }) 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /client/src/utils/api.js: -------------------------------------------------------------------------------- 1 | import { API_RESPONSE_STATUS } from './constants'; 2 | 3 | export async function get(url) { 4 | try { 5 | const response = await fetch(url, { 6 | headers: { 'Accept': 'application/json' } 7 | }); 8 | 9 | const data = await response.json(); 10 | 11 | // convert response status to success/fail 12 | const status = (response.status >= 200 && response.status < 400) ? API_RESPONSE_STATUS.success 13 | : API_RESPONSE_STATUS.error; 14 | 15 | return { 16 | status, 17 | data 18 | } 19 | } catch (err) { 20 | return { 21 | status: API_RESPONSE_STATUS.error, 22 | data: err 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tools/tokencrypt/encryptor.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | 7 | "github.com/demisto/alfred/conf" 8 | "github.com/demisto/alfred/repo" 9 | ) 10 | 11 | var ( 12 | confFile = flag.String("conf", "conf.json", "Path to configuration file in JSON format") 13 | ) 14 | 15 | func check(err error) { 16 | if err != nil { 17 | panic(err) 18 | } 19 | } 20 | 21 | func main() { 22 | flag.Parse() 23 | err := conf.Load(*confFile, false) 24 | check(err) 25 | r, err := repo.NewMySQL() 26 | check(err) 27 | teams, err := r.Teams() 28 | check(err) 29 | for i := range teams { 30 | log.Printf("Working on team %s\n", teams[i].Name) 31 | users, err := r.TeamMembers(teams[i].ID) 32 | check(err) 33 | for j := range users { 34 | log.Printf("Updating user %s\n", users[j].Name) 35 | err = r.SetUser(&users[j]) 36 | check(err) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /slack/conversations.go: -------------------------------------------------------------------------------- 1 | package slack 2 | 3 | import "github.com/demisto/alfred/util" 4 | 5 | // Conversations retrieval by type 6 | // Handle cursors as well 7 | func (s *Client) Conversations(t string) (channels []util.Object, err error) { 8 | args := map[string]string{"exclude_archived": "true", "limit": "1000"} 9 | if t != "" { 10 | args["types"] = t 11 | } 12 | channels = make([]util.Object, 0) 13 | for { 14 | res, err := s.Do("GET", "conversations.list", args) 15 | if err != nil { 16 | return nil, err 17 | } 18 | if c, ok := res["channels"]; ok { 19 | for _, cc := range c.([]interface{}) { 20 | channels = append(channels, util.Object(cc.(map[string]interface{}))) 21 | } 22 | } 23 | if res.S("response_metadata.next_cursor") == "" { 24 | break 25 | } else { 26 | args["cursor"] = res.S("response_metadata.next_cursor") 27 | } 28 | } 29 | return 30 | } 31 | -------------------------------------------------------------------------------- /client/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/en/webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | const assetFilename = JSON.stringify(path.basename(filename)); 11 | 12 | if (filename.match(/\.svg$/)) { 13 | return `module.exports = { 14 | __esModule: true, 15 | default: ${assetFilename}, 16 | ReactComponent: (props) => ({ 17 | $$typeof: Symbol.for('react.element'), 18 | type: 'svg', 19 | ref: null, 20 | key: null, 21 | props: Object.assign({}, props, { 22 | children: ${assetFilename} 23 | }) 24 | }), 25 | };`; 26 | } 27 | 28 | return `module.exports = ${assetFilename};`; 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /client/src/components/UIComponents/Table.js: -------------------------------------------------------------------------------- 1 | import './Table.less'; 2 | import React from 'react'; 3 | import { fieldToTitle } from '../../utils/utils'; 4 | 5 | export default function({ title, headers, data, keys, style }) { 6 | if (!data || data.length === 0) { 7 | return null; 8 | } 9 | 10 | const readyHeaders = headers || keys.map(fieldToTitle); 11 | return ( 12 |
13 |

{title}

14 | 15 | 16 | { 17 | readyHeaders.map(header => ()) 18 | } 19 | 20 | 21 | { 22 | data.map(((item, i) => ( 23 | 24 | {keys.map(key => ())} 25 | 26 | ))) 27 | } 28 | 29 |
{header}
{item[key]}
30 |
31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /queue/db_test.go: -------------------------------------------------------------------------------- 1 | // +build integration 2 | 3 | package queue 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/demisto/alfred/conf" 9 | "github.com/demisto/alfred/domain" 10 | "github.com/demisto/alfred/repo" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func getTestDB(t *testing.T) *repo.MySQL { 15 | conf.Load("", true) 16 | conf.Options.DB.ConnectString, conf.Options.DB.Username, conf.Options.DB.Password = "tcp/demistot?parseTime=true", "demisto", "demisto1999" 17 | db, err := repo.NewMySQL() 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | db.db.Exec("DELETE FROM queue") 22 | db.db.Exec("DELETE FROM teams") 23 | return db 24 | } 25 | 26 | func TestDbQueue_PushWork(t *testing.T) { 27 | r := getTestDB(t) 28 | q, err := New(r) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | err = r.SetTeam(&domain.Team{ID: "kuku", Name: "kuku"}) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | assert.NoError(t, q.PushWork(&domain.WorkRequest{Text: "kuku", Type: "message"})) 37 | 38 | defer q.Close() 39 | } 40 | -------------------------------------------------------------------------------- /client/src/components/Details/ReputationHeader.js: -------------------------------------------------------------------------------- 1 | import './ReputationHeader.less' 2 | import { REPUTATION_RESULT } from '../../utils/constants'; 3 | import classNames from 'classnames'; 4 | import React from 'react'; 5 | 6 | export function ReputationHeader({ indicatorName, isPrivate, result }) { 7 | let headerMessage = `Could not determine the ${indicatorName} reputation.`; 8 | let headerClass = 'unknown'; 9 | 10 | if (isPrivate) { 11 | headerMessage = `${indicatorName} is a private (internal).`; 12 | headerClass = 'clean'; 13 | } else if (result === REPUTATION_RESULT.clean) { 14 | headerMessage = `${indicatorName} is found to be clean.`; 15 | headerClass = 'clean'; 16 | } else if (result === REPUTATION_RESULT.dirty) { 17 | headerMessage = `${indicatorName} is found to be malicious.`; 18 | headerClass = 'dirty'; 19 | } 20 | 21 | return ( 22 |
23 |

24 | {headerMessage} 25 |

26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /client/public/js/skip-link-focus-fix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * File skip-link-focus-fix.js. 3 | * 4 | * Helps with accessibility for keyboard only users. 5 | * 6 | * Learn more: https://git.io/vWdr2 7 | */ 8 | ( function() { 9 | var isWebkit = navigator.userAgent.toLowerCase().indexOf( 'webkit' ) > -1, 10 | isOpera = navigator.userAgent.toLowerCase().indexOf( 'opera' ) > -1, 11 | isIe = navigator.userAgent.toLowerCase().indexOf( 'msie' ) > -1; 12 | 13 | if ( ( isWebkit || isOpera || isIe ) && document.getElementById && window.addEventListener ) { 14 | window.addEventListener( 'hashchange', function() { 15 | var id = location.hash.substring( 1 ), 16 | element; 17 | 18 | if ( ! ( /^[A-z0-9_-]+$/.test( id ) ) ) { 19 | return; 20 | } 21 | 22 | element = document.getElementById( id ); 23 | 24 | if ( element ) { 25 | if ( ! ( /^(?:a|select|input|button|textarea)$/i.test( element.tagName ) ) ) { 26 | element.tabIndex = -1; 27 | } 28 | 29 | element.focus(); 30 | } 31 | }, false ); 32 | } 33 | })(); 34 | -------------------------------------------------------------------------------- /client/src/utils/utils.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import { TABLE_DATE_FORMAT } from './constants'; 3 | 4 | const MD5Mask = 1; 5 | const URLMask = 2; 6 | const IPMask = 4; 7 | const FILEMask = 8; 8 | 9 | export function parseType(type) { 10 | return { 11 | isMD5: !!(type & MD5Mask), 12 | isURL: !!(type & URLMask), 13 | isIP: !!(type & IPMask), 14 | isFile: !!(type & FILEMask), 15 | }; 16 | } 17 | 18 | export function keysToString(obj, defaultVal = 'Unknown') { 19 | if (!obj) { 20 | return defaultVal; 21 | } 22 | 23 | return Object.keys(obj).join(', ') || defaultVal; 24 | } 25 | 26 | export function dateToString(date) { 27 | return moment(date).format( 28 | TABLE_DATE_FORMAT); 29 | } 30 | 31 | // "field_name" => "Field nNme" 32 | export function fieldToTitle(str) { 33 | return str.replace(/_/g, ' ').replace(/(?: |\b)(\w)/g, key => key.toUpperCase()); 34 | 35 | } 36 | 37 | export function compareDate(fieldName) { 38 | return function compare(a, b) { 39 | const dateA = new Date(a[fieldName]); 40 | const dateB = new Date(b[fieldName]); 41 | return dateA - dateB; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /queue/queue.go: -------------------------------------------------------------------------------- 1 | // Package queue abstracts the various external (or internal) message queues we are using for notifications 2 | package queue 3 | 4 | import ( 5 | "errors" 6 | "time" 7 | 8 | "github.com/demisto/alfred/domain" 9 | "github.com/demisto/alfred/repo" 10 | ) 11 | 12 | var ( 13 | // ErrTimeout is returned if receive encounters a timeout 14 | ErrTimeout = errors.New("timeout occurred") 15 | // ErrClosed is returned if you try to access a closed queue 16 | ErrClosed = errors.New("queue is already closed") 17 | ) 18 | 19 | // Queue abstracts the external / internal queues 20 | type Queue interface { 21 | PushConf(team string) error 22 | PopConf(timeout time.Duration) (string, error) 23 | PushWork(work *domain.WorkRequest) error 24 | PopWork(timeout time.Duration) (*domain.WorkRequest, error) 25 | PushWorkReply(replyQueue string, reply *domain.WorkReply) error 26 | PopWorkReply(replyQueue string, timeout time.Duration) (*domain.WorkReply, error) 27 | Close() error 28 | } 29 | 30 | // New queue is returned depending on environment 31 | func New(r *repo.MySQL) (Queue, error) { 32 | return NewDBQueue(r), nil 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Demisto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /domain/configuration_test.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import "testing" 4 | 5 | // TestRandomEvents tests the generation of random events 6 | func TestIsActive(t *testing.T) { 7 | var c Configuration 8 | if c.IsActive() { 9 | t.Error("IsActive is true for empty configuration") 10 | } 11 | 12 | c.Channels = []string{"kuku"} 13 | if !c.IsActive() { 14 | t.Error("IsActive is false but we have a channel") 15 | } 16 | 17 | c.Channels = []string{} 18 | c.Groups = []string{"kuku"} 19 | if !c.IsActive() { 20 | t.Error("IsActive is false but we have a group") 21 | } 22 | 23 | c.Groups = []string{} 24 | c.IM = true 25 | if !c.IsActive() { 26 | t.Error("IsActive is false but we have an IM") 27 | } 28 | } 29 | 30 | func TestIsInterestedIn(t *testing.T) { 31 | var c Configuration 32 | if c.IsInterestedIn("Cx", "") || c.IsInterestedIn("Gx", "") || c.IsInterestedIn("Dx", "") { 33 | t.Error("Configuration is empty but still interested") 34 | } 35 | 36 | c.Channels = []string{"Cx"} 37 | c.Groups = []string{"Gx"} 38 | c.IM = true 39 | 40 | if !c.IsInterestedIn("Cx", "") || !c.IsInterestedIn("Gx", "") || !c.IsInterestedIn("Dx", "") { 41 | t.Error("Configuration is not interested but it should") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tools/crypt/crypt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "github.com/Sirupsen/logrus" 8 | "github.com/demisto/alfred/conf" 9 | "github.com/demisto/alfred/util" 10 | ) 11 | 12 | var ( 13 | confFile = flag.String("conf", "conf.json", "Path to configuration file in JSON format") 14 | logLevel = flag.String("loglevel", "info", "Specify the log level for output (debug/info/warn/error/fatal/panic) - default is info") 15 | logFile = flag.String("logfile", "", "The log file location") 16 | action = flag.String("action", "decrypt", "Action to perform on the data - encrypt/decrypt") 17 | data = flag.String("data", "", "The data to perform the action on") 18 | ) 19 | 20 | func check(err error) { 21 | if err != nil { 22 | panic(err) 23 | } 24 | } 25 | 26 | func main() { 27 | flag.Parse() 28 | err := conf.Load(*confFile, false) 29 | if err != nil { 30 | logrus.Fatal(err) 31 | } 32 | if *data == "" { 33 | logrus.Fatal("Please specify the data") 34 | } 35 | if *action != "decrypt" && *action != "encrypt" { 36 | logrus.Fatal("Invalid action specified") 37 | } 38 | if *action == "decrypt" { 39 | clear, err := util.Decrypt(*data, conf.Options.Security.DBKey) 40 | check(err) 41 | fmt.Println(clear) 42 | } else { 43 | cipher, err := util.Encrypt(*data, conf.Options.Security.DBKey) 44 | check(err) 45 | fmt.Println(cipher) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /etc/Dockerfile.single: -------------------------------------------------------------------------------- 1 | FROM debian:jessie 2 | MAINTAINER Slavik Markovich 3 | 4 | ENV CLAMAV_VERSION 0.99 5 | 6 | RUN echo "deb http://http.debian.net/debian/ jessie main contrib non-free" > /etc/apt/sources.list && \ 7 | echo "deb http://http.debian.net/debian/ jessie-updates main contrib non-free" >> /etc/apt/sources.list && \ 8 | echo "deb http://security.debian.org/ jessie/updates main contrib non-free" >> /etc/apt/sources.list && \ 9 | apt-get update && \ 10 | DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ 11 | clamav-daemon=${CLAMAV_VERSION}* \ 12 | clamav-freshclam=${CLAMAV_VERSION}* \ 13 | libclamunrar6 \ 14 | wget && \ 15 | apt-get clean && \ 16 | rm -rf /var/lib/apt/lists/* 17 | 18 | RUN wget -O /var/lib/clamav/main.cvd http://database.clamav.net/main.cvd && \ 19 | wget -O /var/lib/clamav/daily.cvd http://database.clamav.net/daily.cvd && \ 20 | wget -O /var/lib/clamav/bytecode.cvd http://database.clamav.net/bytecode.cvd && \ 21 | chown clamav:clamav /var/lib/clamav/*.cvd 22 | 23 | RUN mkdir /var/run/clamav && \ 24 | chown clamav:clamav /var/run/clamav && \ 25 | chmod 750 /var/run/clamav 26 | 27 | RUN sed -i 's/^Foreground .*$/Foreground true/g' /etc/clamav/freshclam.conf 28 | 29 | VOLUME ["/var/lib/clamav"] 30 | 31 | COPY run.sh / 32 | COPY g-single.conf /etc/g-worker.conf 33 | COPY ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 34 | COPY worker / 35 | COPY build-date / 36 | 37 | CMD ["/run.sh"] 38 | -------------------------------------------------------------------------------- /etc/Dockerfile.worker: -------------------------------------------------------------------------------- 1 | FROM debian:jessie 2 | MAINTAINER Slavik Markovich 3 | 4 | ENV CLAMAV_VERSION 0.99 5 | 6 | RUN echo "deb http://http.debian.net/debian/ jessie main contrib non-free" > /etc/apt/sources.list && \ 7 | echo "deb http://http.debian.net/debian/ jessie-updates main contrib non-free" >> /etc/apt/sources.list && \ 8 | echo "deb http://security.debian.org/ jessie/updates main contrib non-free" >> /etc/apt/sources.list && \ 9 | apt-get update && \ 10 | DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ 11 | clamav-daemon=${CLAMAV_VERSION}* \ 12 | clamav-freshclam=${CLAMAV_VERSION}* \ 13 | libclamunrar7 \ 14 | wget && \ 15 | apt-get clean && \ 16 | rm -rf /var/lib/apt/lists/* 17 | 18 | RUN wget -O /var/lib/clamav/main.cvd http://database.clamav.net/main.cvd && \ 19 | wget -O /var/lib/clamav/daily.cvd http://database.clamav.net/daily.cvd && \ 20 | wget -O /var/lib/clamav/bytecode.cvd http://database.clamav.net/bytecode.cvd && \ 21 | chown clamav:clamav /var/lib/clamav/*.cvd 22 | 23 | RUN mkdir /var/run/clamav && \ 24 | chown clamav:clamav /var/run/clamav && \ 25 | chmod 750 /var/run/clamav 26 | 27 | RUN sed -i 's/^Foreground .*$/Foreground true/g' /etc/clamav/freshclam.conf 28 | 29 | VOLUME ["/var/lib/clamav"] 30 | 31 | COPY run.sh / 32 | COPY g-worker.conf /etc/g-worker.conf 33 | COPY ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 34 | COPY worker / 35 | COPY build-date / 36 | 37 | CMD ["/run.sh"] 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | tmp 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | *.test 25 | *.prof 26 | alfred 27 | alfred.db 28 | tools/pscleaner/pscleaner 29 | 30 | *.sw* 31 | *~ 32 | 33 | node_modules/ 34 | bower_components/ 35 | static/site/js/ 36 | static/site/css/ 37 | static/site/vendor/ 38 | static/site/*.html 39 | .DS_Store 40 | 41 | # Web static embedded files 42 | static.go 43 | 44 | # Sensitive files 45 | Makefile 46 | etc/*.conf 47 | etc/*rsa* 48 | etc/*.pem 49 | etc/*.yaml 50 | etc/*mysql* 51 | etc/backup_script.sh 52 | etc/dbot_backup* 53 | etc/command-dbot-prod.sh 54 | etc/copy-* 55 | etc/dbot-init-prod.script 56 | af_test.go 57 | 58 | _analytics.jade 59 | _gtmid.json 60 | _ze.jade 61 | .idea 62 | 63 | # Logs 64 | *.log 65 | 66 | tags 67 | 68 | # Client 69 | 70 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 71 | /client/node_modules 72 | /client/.pnp 73 | .pnp.js 74 | package-lock.json 75 | 76 | # testing 77 | /client/coverage 78 | 79 | # production 80 | /client/build 81 | 82 | # misc 83 | .DS_Store 84 | .env.local 85 | .env.development.local 86 | .env.test.local 87 | .env.production.local 88 | 89 | npm-debug.log* 90 | yarn-debug.log* 91 | yarn-error.log* 92 | 93 | -------------------------------------------------------------------------------- /etc/Dockerfile.build: -------------------------------------------------------------------------------- 1 | FROM golang:latest 2 | 3 | # First, add npm as well 4 | # verify gpg and sha256: http://nodejs.org/dist/v0.10.30/SHASUMS256.txt.asc 5 | # gpg: aka "Timothy J Fontaine (Work) " 6 | # gpg: aka "Julien Gilli " 7 | RUN set -ex \ 8 | && for key in \ 9 | 7937DFD2AB06298B2293C3187D33FF9D0246406D \ 10 | 114F43EE0176B71C7BC219DD50A3051F888C628D \ 11 | ; do \ 12 | gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \ 13 | done 14 | 15 | ENV NODE_VERSION 0.12.7 16 | ENV NPM_VERSION 2.13.3 17 | 18 | RUN set -x \ 19 | && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.gz" \ 20 | && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \ 21 | && gpg --verify SHASUMS256.txt.asc \ 22 | && grep " node-v$NODE_VERSION-linux-x64.tar.gz\$" SHASUMS256.txt.asc | sha256sum -c - \ 23 | && tar -xzf "node-v$NODE_VERSION-linux-x64.tar.gz" -C /usr/local --strip-components=1 \ 24 | && rm "node-v$NODE_VERSION-linux-x64.tar.gz" SHASUMS256.txt.asc \ 25 | && npm install -g npm@"$NPM_VERSION" \ 26 | && npm cache clear 27 | 28 | # Add ClamAV lib 29 | RUN apt-get update && apt-get install -y libclamav-dev --no-install-recommends 30 | 31 | COPY build-date / 32 | COPY Makefile / 33 | COPY _analytics.jade / 34 | COPY _ze.jade / 35 | COPY _gtmid.json / 36 | WORKDIR / 37 | RUN make setup 38 | RUN make buildgo 39 | RUN make buildgoav 40 | 41 | CMD tar -chf - -C /go/src/github.com/demisto/alfred alfred -C /etc/ssl/certs ca-certificates.crt -C /go/src/github.com/demisto/alfred worker -C / build-date 42 | -------------------------------------------------------------------------------- /client/scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI, in coverage mode, or explicitly running all tests 42 | if ( 43 | !process.env.CI && 44 | argv.indexOf('--coverage') === -1 && 45 | argv.indexOf('--watchAll') === -1 46 | ) { 47 | // https://github.com/facebook/create-react-app/issues/5210 48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 49 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 50 | } 51 | 52 | 53 | jest.run(argv); 54 | -------------------------------------------------------------------------------- /util/encryption_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "testing" 4 | 5 | func TestEncrypt(t *testing.T) { 6 | plain := ` 7 | the quick brown fox jumped over the white fence 8 | the quick brown fox jumped over the white fence 9 | the quick brown fox jumped over the white fence 10 | the quick brown fox jumped over the white fence 11 | the quick brown fox jumped over the white fence 12 | the quick brown fox jumped over the white fence 13 | the quick brown fox jumped over the white fence 14 | the quick brown fox jumped over the white fence 15 | the quick brown fox jumped over the white fence 16 | the quick brown fox jumped over the white fence 17 | ` 18 | pass := `12345678901234567890123456789012` 19 | // Test with 32 byte key 20 | ciph, err := Encrypt(plain, pass) 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | plain1, err := Decrypt(ciph, pass) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | if plain != plain1 { 29 | t.Fatalf("Expected '%v' but got '%v'", plain, plain1) 30 | } 31 | } 32 | 33 | func TestJson(t *testing.T) { 34 | type plainType struct { 35 | t1 string 36 | } 37 | plain := plainType{"t1"} 38 | pass := `12345678901234567890123456789012` 39 | // Test with 32 byte key 40 | ciph, err := EncryptJSON(plain, pass) 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | err = DecryptJSON(ciph, pass, &plain) 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | if plain.t1 != "t1" { 49 | t.Fatalf("Expected '%v' but got '%v'", "t1", plain.t1) 50 | } 51 | } 52 | 53 | func TestSecureRandomString(t *testing.T) { 54 | str := SecureRandomString(50, true) 55 | if len(str) != 50 { 56 | t.Fatalf("Expected %v length but got %v", 50, len(str)) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tools/pscleaner/pscleaner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | 7 | "github.com/Sirupsen/logrus" 8 | "github.com/demisto/alfred/conf" 9 | "golang.org/x/oauth2" 10 | "golang.org/x/oauth2/google" 11 | "google.golang.org/api/pubsub/v1" 12 | ) 13 | 14 | var ( 15 | confFile = flag.String("conf", "conf.json", "Path to configuration file in JSON format") 16 | logLevel = flag.String("loglevel", "info", "Specify the log level for output (debug/info/warn/error/fatal/panic) - default is info") 17 | logFile = flag.String("logfile", "", "The log file location") 18 | ) 19 | 20 | func check(err error) { 21 | if err != nil { 22 | panic(err) 23 | } 24 | } 25 | 26 | func main() { 27 | flag.Parse() 28 | err := conf.Load(*confFile, true) 29 | if err != nil { 30 | logrus.Fatal(err) 31 | } 32 | jsonCreds, err := json.Marshal(conf.Options.G.Credentials) 33 | check(err) 34 | config, err := google.JWTConfigFromJSON(jsonCreds, pubsub.PubsubScope) 35 | check(err) 36 | client := config.Client(oauth2.NoContext) 37 | svc, err := pubsub.New(client) 38 | check(err) 39 | list, err := svc.Projects.Subscriptions.List("projects/" + conf.Options.G.Project).Do() 40 | check(err) 41 | for i := range list.Subscriptions { 42 | logrus.Infof("Deleting subscription - %s", list.Subscriptions[i].Name) 43 | _, err := svc.Projects.Subscriptions.Delete(list.Subscriptions[i].Name).Do() 44 | check(err) 45 | } 46 | topics, err := svc.Projects.Topics.List("projects/" + conf.Options.G.Project).Do() 47 | check(err) 48 | for i := range topics.Topics { 49 | logrus.Infof("Deleting topic - %s", topics.Topics[i].Name) 50 | _, err := svc.Projects.Topics.Delete(topics.Topics[i].Name).Do() 51 | check(err) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /client/public/fonts/themovation-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by Fontastic.me 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | Alfred client 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | -------------------------------------------------------------------------------- /domain/stats.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import "time" 4 | 5 | // Statistics holds message and detection statistics for a team 6 | type Statistics struct { 7 | Team string `json:"team"` 8 | Timestamp time.Time `json:"ts" db:"ts"` 9 | Messages int64 `json:"messages"` 10 | FilesClean int64 `json:"files_clean" db:"files_clean"` 11 | FilesDirty int64 `json:"files_dirty" db:"files_dirty"` 12 | FilesUnknown int64 `json:"files_unknown" db:"files_unknown"` 13 | URLsClean int64 `json:"urls_clean" db:"urls_clean"` 14 | URLsDirty int64 `json:"urls_dirty" db:"urls_dirty"` 15 | URLsUnknown int64 `json:"urls_unknown" db:"urls_unknown"` 16 | HashesClean int64 `json:"hashes_clean" db:"hashes_clean"` 17 | HashesDirty int64 `json:"hashes_dirty" db:"hashes_dirty"` 18 | HashesUnknown int64 `json:"hashes_unknown" db:"hashes_unknown"` 19 | IPsClean int64 `json:"ips_clean" db:"ips_clean"` 20 | IPsDirty int64 `json:"ips_dirty" db:"ips_dirty"` 21 | IPsUnknown int64 `json:"ips_unknown" db:"ips_unknown"` 22 | } 23 | 24 | // Reset all the counters 25 | func (s *Statistics) Reset() { 26 | s.Messages = 0 27 | s.FilesClean = 0 28 | s.FilesDirty = 0 29 | s.FilesUnknown = 0 30 | s.URLsClean = 0 31 | s.URLsDirty = 0 32 | s.URLsUnknown = 0 33 | s.HashesClean = 0 34 | s.HashesDirty = 0 35 | s.HashesUnknown = 0 36 | s.IPsClean = 0 37 | s.IPsDirty = 0 38 | s.IPsUnknown = 0 39 | } 40 | 41 | // HasSomething that is not 0 in the statistics 42 | func (s *Statistics) HasSomething() bool { 43 | return s.Messages != 0 || 44 | s.FilesClean != 0 || 45 | s.FilesDirty != 0 || 46 | s.FilesUnknown != 0 || 47 | s.URLsClean != 0 || 48 | s.URLsDirty != 0 || 49 | s.URLsUnknown != 0 || 50 | s.HashesClean != 0 || 51 | s.HashesDirty != 0 || 52 | s.HashesUnknown != 0 || 53 | s.IPsClean != 0 || 54 | s.IPsDirty != 0 || 55 | s.IPsUnknown != 0 56 | } 57 | -------------------------------------------------------------------------------- /util/util_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestToIntf(t *testing.T) { 9 | if reflect.TypeOf(ToIntf([]int{1, 2, 3})) != reflect.TypeOf([]interface{}{}) { 10 | t.Fatal("Did not convert slice to intf slice") 11 | } 12 | } 13 | 14 | func TestIn(t *testing.T) { 15 | s := []string{"foo", "bar", "kuku", "kiki"} 16 | for _, v := range s { 17 | if !In(s, v) { 18 | t.Error("Should be in") 19 | } 20 | } 21 | if In(s, "foobar") { 22 | t.Error("Should not be in") 23 | } 24 | } 25 | 26 | func TestIndex(t *testing.T) { 27 | s := []string{"foo", "bar", "kuku", "kiki"} 28 | for i, v := range s { 29 | if Index(s, v) != i { 30 | t.Error("Should be indexed") 31 | } 32 | } 33 | if Index(s, "foobar") != -1 { 34 | t.Error("Should not be in") 35 | } 36 | } 37 | 38 | func TestToLower(t *testing.T) { 39 | s := []string{"MyString12Str"} 40 | res := ToLower(s) 41 | if res[0] != "mystring12str" { 42 | t.Error(res) 43 | } 44 | 45 | } 46 | 47 | func TestRandStr(t *testing.T) { 48 | s1 := RandStr(32) 49 | s2 := RandStr(32) 50 | if len(s1) != 32 { 51 | t.Errorf("Rand str len not enforced s1 %d", len(s1)) 52 | } 53 | if len(s2) != 32 { 54 | t.Errorf("Rand str len not enforced s2 %d", len(s2)) 55 | } 56 | if s1 == s2 { 57 | t.Error("Random string is not random") 58 | } 59 | 60 | } 61 | 62 | func TestCannoncialize(t *testing.T) { 63 | url1 := "http://first/test.com" 64 | url2 := "https://second/test.com" 65 | res := Canonicalize(url1, url2) 66 | if len(res) != 2 { 67 | t.Errorf("length problem: %d", len(res)) 68 | } 69 | if res[0] != "http://first" || res[1] != "https://second" { 70 | t.Error(res) 71 | } 72 | } 73 | 74 | func TestSubstr(t *testing.T) { 75 | s := "僤凘墈 葎萻萶 銈 磑禠" 76 | if Substr(s, 13, 14) != "" { 77 | t.Error("From is bigger than length so should have returned empty") 78 | } 79 | if Substr(s, 11, 15) != "禠" { 80 | t.Error("Did not return last char") 81 | } 82 | if Substr(s, 10, 10) != "" { 83 | t.Error("Should return empty if from and to are equal") 84 | } 85 | if s1 := Substr(s, 0, 2); s1 != "僤凘" { 86 | t.Error("Substr is wrong - " + s1) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /client/src/components/Details/Details.less: -------------------------------------------------------------------------------- 1 | .details-page { 2 | text-align: center; 3 | padding-top: 20px; 4 | 5 | .loading-wrapper { 6 | margin-top: 40px; 7 | 8 | .sk-cube-grid { 9 | width: 100px; 10 | height: 100px; 11 | margin: 100px auto; 12 | } 13 | 14 | .sk-cube-grid .sk-cube { 15 | width: 33%; 16 | height: 33%; 17 | background-color: #333; 18 | float: left; 19 | -webkit-animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out; 20 | animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out; 21 | } 22 | .sk-cube-grid .sk-cube1 { 23 | -webkit-animation-delay: 0.2s; 24 | animation-delay: 0.2s; } 25 | .sk-cube-grid .sk-cube2 { 26 | -webkit-animation-delay: 0.3s; 27 | animation-delay: 0.3s; } 28 | .sk-cube-grid .sk-cube3 { 29 | -webkit-animation-delay: 0.4s; 30 | animation-delay: 0.4s; } 31 | .sk-cube-grid .sk-cube4 { 32 | -webkit-animation-delay: 0.1s; 33 | animation-delay: 0.1s; } 34 | .sk-cube-grid .sk-cube5 { 35 | -webkit-animation-delay: 0.2s; 36 | animation-delay: 0.2s; } 37 | .sk-cube-grid .sk-cube6 { 38 | -webkit-animation-delay: 0.3s; 39 | animation-delay: 0.3s; } 40 | .sk-cube-grid .sk-cube7 { 41 | -webkit-animation-delay: 0s; 42 | animation-delay: 0s; } 43 | .sk-cube-grid .sk-cube8 { 44 | -webkit-animation-delay: 0.1s; 45 | animation-delay: 0.1s; } 46 | .sk-cube-grid .sk-cube9 { 47 | -webkit-animation-delay: 0.2s; 48 | animation-delay: 0.2s; } 49 | 50 | @-webkit-keyframes sk-cubeGridScaleDelay { 51 | 0%, 70%, 100% { 52 | -webkit-transform: scale3D(1, 1, 1); 53 | transform: scale3D(1, 1, 1); 54 | } 35% { 55 | -webkit-transform: scale3D(0, 0, 1); 56 | transform: scale3D(0, 0, 1); 57 | } 58 | } 59 | 60 | @keyframes sk-cubeGridScaleDelay { 61 | 0%, 70%, 100% { 62 | -webkit-transform: scale3D(1, 1, 1); 63 | transform: scale3D(1, 1, 1); 64 | } 35% { 65 | -webkit-transform: scale3D(0, 0, 1); 66 | transform: scale3D(0, 0, 1); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /domain/configuration.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/Sirupsen/logrus" 7 | "github.com/demisto/alfred/util" 8 | ) 9 | 10 | // Configuration holds the user configuration 11 | type Configuration struct { 12 | Team string `json:"team"` 13 | Channels []string `json:"channels"` 14 | Groups []string `json:"groups"` 15 | IM bool `json:"im"` 16 | Regexp string `json:"regexp"` 17 | All bool `json:"all"` 18 | VerboseChannels []string `json:"verbose_channels"` 19 | VerboseGroups []string `json:"verbose_groups"` 20 | VerboseIM bool `json:"verbose_im"` 21 | } 22 | 23 | // IsActive returns true if there is at least one active part for the user 24 | func (c *Configuration) IsActive() bool { 25 | return c.All || len(c.Channels) > 0 || len(c.Groups) > 0 || c.IM || 26 | len(c.VerboseChannels) > 0 || len(c.VerboseGroups) > 0 || c.VerboseIM 27 | } 28 | 29 | // IsInterestedIn the given channel 30 | func (c *Configuration) IsInterestedIn(channel, channelName string) bool { 31 | if len(channel) == 0 { 32 | return false 33 | } 34 | if c.All { 35 | return true 36 | } 37 | found := false 38 | switch channel[0] { 39 | case 'C': 40 | found = util.In(c.Channels, channel) || util.In(c.VerboseChannels, channel) 41 | case 'G': 42 | found = util.In(c.Groups, channel) || util.In(c.VerboseGroups, channel) 43 | case 'D': 44 | return c.IM || c.VerboseIM 45 | } 46 | if !found && c.Regexp != "" && channelName != "" { 47 | re, err := regexp.Compile(c.Regexp) 48 | if err != nil { 49 | logrus.Warnf("Found invalid regexp in configuration - %v\n", err) 50 | } else { 51 | logrus.Debugf("Matching %s\n", c.Regexp) 52 | return re.MatchString(channelName) 53 | } 54 | } 55 | return found 56 | } 57 | 58 | // IsVerbose checks if the channel is verbose 59 | func (c *Configuration) IsVerbose(channel string) bool { 60 | if len(channel) == 0 { 61 | return false 62 | } 63 | found := false 64 | switch channel[0] { 65 | case 'C': 66 | found = util.In(c.VerboseChannels, channel) 67 | case 'G': 68 | found = util.In(c.VerboseGroups, channel) 69 | case 'D': 70 | found = c.VerboseIM 71 | } 72 | return found 73 | } 74 | -------------------------------------------------------------------------------- /slack/slack.go: -------------------------------------------------------------------------------- 1 | package slack 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | 11 | "github.com/Sirupsen/logrus" 12 | "github.com/demisto/alfred/util" 13 | ) 14 | 15 | // client to the Slack API. 16 | type Client struct { 17 | Token string // The token to use for requests. Required. 18 | } 19 | 20 | // OK returns true if response is ok 21 | func OK(r util.Object) bool { 22 | return r.B("ok") 23 | } 24 | 25 | // Error returns an error of the response 26 | func Error(r util.Object) string { 27 | return r.S("error") 28 | } 29 | 30 | // Warning returns warning if the response contains warnings 31 | func Warning(r util.Object) string { 32 | return r.S("warning") 33 | } 34 | 35 | // Do the given API request 36 | // Returns the response if the status code is between 200 and 299 37 | func (s *Client) Do(method, path string, body interface{}) (util.Object, error) { 38 | var bodyReader io.Reader 39 | if method == "GET" { 40 | if body != nil { 41 | if bmap, ok := body.(map[string]string); ok { 42 | urlValues := url.Values{} 43 | for k, v := range bmap { 44 | urlValues.Set(k, v) 45 | } 46 | path += "?" + urlValues.Encode() 47 | } 48 | } 49 | } else { 50 | if body != nil { 51 | b, err := json.Marshal(body) 52 | if err != nil { 53 | return nil, err 54 | } 55 | bodyReader = bytes.NewReader(b) 56 | } 57 | } 58 | req, err := http.NewRequest(method, "https://slack.com/api/"+path, bodyReader) 59 | if err != nil { 60 | return nil, err 61 | } 62 | if method != "GET" { 63 | req.Header.Set("Content-Type", "application/json; charset=utf-8") 64 | } 65 | req.Header.Set("Accept", "application/json") 66 | if s.Token != "" { 67 | req.Header.Set("Authorization", "Bearer "+s.Token) 68 | } 69 | resp, err := http.DefaultClient.Do(req) 70 | if err != nil { 71 | return nil, err 72 | } 73 | defer resp.Body.Close() 74 | if resp.StatusCode < 200 || resp.StatusCode > 299 { 75 | return nil, errors.New("unexpected status code: [" + resp.Status + "]") 76 | } 77 | res := util.Object{} 78 | if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { 79 | return nil, err 80 | } 81 | if !OK(res) { 82 | return nil, errors.New("Slack error: " + Error(res)) 83 | } 84 | if Warning(res) != "" { 85 | logrus.Warnf("Slack API warning %s", Warning(res)) 86 | } 87 | return res, nil 88 | } 89 | -------------------------------------------------------------------------------- /util/logger.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "runtime" 8 | "strings" 9 | 10 | "github.com/demisto/alfred/conf" 11 | 12 | "github.com/Sirupsen/logrus" 13 | "gopkg.in/natefinch/lumberjack.v2" 14 | ) 15 | 16 | var maxLineSize int 17 | var output *lumberjack.Logger 18 | 19 | type captainHook struct { 20 | } 21 | 22 | func (*captainHook) Levels() []logrus.Level { 23 | return logrus.AllLevels 24 | } 25 | 26 | func (*captainHook) Fire(entry *logrus.Entry) error { 27 | skip := 6 28 | ok := true 29 | var file string 30 | var line int 31 | for ok { 32 | _, file, line, ok = runtime.Caller(skip) 33 | if strings.Contains(file, "logrus") { 34 | skip++ 35 | } else { 36 | entry.Data["source"] = fmt.Sprintf("%s:%d", file, line) 37 | return nil 38 | } 39 | } 40 | return nil 41 | } 42 | 43 | type simpleFormatter struct { 44 | } 45 | 46 | func (*simpleFormatter) Format(entry *logrus.Entry) ([]byte, error) { 47 | buff := new(bytes.Buffer) 48 | buff.Grow(512) 49 | buff.WriteString(entry.Time.Format("2006-01-02 15:04:05.9999 ")) 50 | msg := entry.Message 51 | if len(msg) > maxLineSize { 52 | msg = fmt.Sprintf("%s...\nNOTE, too much data to log, message was truncated.", msg[:maxLineSize]) 53 | } 54 | fmt.Fprintf(buff, "%s %s ", entry.Level, msg) 55 | for name, field := range entry.Data { 56 | fmt.Fprintf(buff, "(%s: %v)", name, field) 57 | } 58 | buff.WriteString(" \n") 59 | if runtime.GOOS == "windows" { 60 | buff.WriteString("\r") 61 | } 62 | return buff.Bytes(), nil 63 | } 64 | 65 | // InitLog - init the log for server 66 | func InitLog(fileloc string, logLevel string, stdout bool) { 67 | // limit the size of the message to log 68 | maxLineSize = 100000 69 | 70 | level, err := logrus.ParseLevel(logLevel) 71 | if err != nil { 72 | fmt.Printf("Invalid log level value provided; %s, using Info", logLevel) 73 | level = logrus.InfoLevel 74 | } 75 | logrus.SetLevel(level) 76 | logrus.AddHook(new(captainHook)) 77 | logrus.SetFormatter(new(simpleFormatter)) 78 | 79 | if stdout { 80 | logrus.SetOutput(os.Stderr) 81 | } else { 82 | SetOutput(fileloc) 83 | } 84 | 85 | conf.LogWriter = logrus.StandardLogger().Writer() 86 | } 87 | 88 | //SetOutput ... 89 | func SetOutput(fileloc string) { 90 | if output == nil { 91 | output = &lumberjack.Logger{} 92 | logrus.SetOutput(output) 93 | } 94 | output.Filename = fileloc 95 | output.MaxSize = 10 96 | output.MaxBackups = 3 97 | output.MaxAge = 0 98 | } 99 | -------------------------------------------------------------------------------- /circle-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Simple script to set up Circle environment and run the tests 4 | 5 | BUILD_DIR=$HOME/go 6 | GO_VERSION=go1.5 7 | TIMEOUT="-timeout 30s" 8 | 9 | # Executes the given statement, and exits if the command returns a non-zero code. 10 | function exit_if_fail { 11 | command=$@ 12 | echo "Executing '$command'" 13 | $command 14 | rc=$? 15 | if [ $rc -ne 0 ]; then 16 | echo "'$command' returned $rc." 17 | exit $rc 18 | fi 19 | } 20 | 21 | # Check that go fmt has been run. 22 | function check_go_fmt { 23 | fmtcount=`git ls-files | grep '.go$' | xargs gofmt -l 2>&1 | wc -l` 24 | if [ $fmtcount -gt 0 ]; then 25 | echo "run 'go fmt ./...' to format your source code." 26 | exit 1 27 | fi 28 | } 29 | 30 | # Check that go vet passes. 31 | function check_go_vet { 32 | vetcount=`go vet ./... 2>&1 | wc -l` 33 | if [ $vetcount -gt 0 ]; then 34 | echo "run 'go vet ./...' to see the errors it flags and correct your source code." 35 | exit 1 36 | fi 37 | } 38 | 39 | source $HOME/.gvm/scripts/gvm 40 | exit_if_fail gvm use $GO_VERSION 41 | 42 | # Set up the build directory, and then GOPATH. 43 | exit_if_fail mkdir $BUILD_DIR 44 | export GOPATH=$BUILD_DIR 45 | exit_if_fail mkdir -p $GOPATH/src/github.com/demisto 46 | 47 | # Dump some test config to the log. 48 | echo "Configuration" 49 | echo "========================================" 50 | echo "\$HOME: $HOME" 51 | echo "\$GOPATH: $GOPATH" 52 | echo "\$GO_VERSION: $GO_VERSION" 53 | 54 | # Move the checked-out source to a better location. 55 | exit_if_fail mv $HOME/alfred $GOPATH/src/github.com/demisto 56 | 57 | # Install the code. 58 | exit_if_fail cd $GOPATH/src/github.com/demisto/alfred 59 | exit_if_fail go get -t -d -v ./... 60 | 61 | # Touch missing private files 62 | exit_if_fail touch $GOPATH/src/github.com/demisto/alfred/static/master/jade/_ze.jade 63 | exit_if_fail touch $GOPATH/src/github.com/demisto/alfred/static/master/jade/_analytics.jade 64 | 65 | # Build all the web stuff 66 | exit_if_fail cd $GOPATH/src/github.com/demisto/alfred/static/master 67 | exit_if_fail npm install 68 | exit_if_fail bower install 69 | exit_if_fail gulp build 70 | 71 | # Embed the static files inside the executable 72 | exit_if_fail go get github.com/slavikm/esc 73 | exit_if_fail cd $GOPATH/src/github.com/demisto/alfred 74 | exit_if_fail $GOPATH/bin/esc -o web/static.go -pkg web -prefix static/site/ static/site/ 75 | check_go_fmt 76 | check_go_vet 77 | exit_if_fail go build -v ./... 78 | 79 | # Finally, test 80 | 81 | go test $TIMEOUT -v ./... 82 | exit $? 83 | -------------------------------------------------------------------------------- /util/object.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | // Object generic map to allow JSON nice interactivity 10 | type Object map[string]interface{} 11 | 12 | // Get a recursive path 13 | func (o Object) Get(path string) interface{} { 14 | parts := strings.Split(path, ".") 15 | curr := map[string]interface{}(o) 16 | for i, p := range parts { 17 | if tmp, ok := curr[p]; ok { 18 | if i == len(parts)-1 { 19 | return tmp 20 | } 21 | curr, ok = tmp.(map[string]interface{}) 22 | if !ok { 23 | return nil 24 | } 25 | } else { 26 | return nil 27 | } 28 | } 29 | return nil 30 | } 31 | 32 | // O returns a path as an Object 33 | func (o Object) O(path string) Object { 34 | if d := o.Get(path); d != nil { 35 | if dr, ok := d.(map[string]interface{}); ok { 36 | return Object(dr) 37 | } 38 | } 39 | return Object(map[string]interface{}{}) 40 | } 41 | 42 | // S returns given path as string 43 | func (o Object) S(path string) string { 44 | if d := o.Get(path); d != nil { 45 | if ds, ok := d.(string); ok { 46 | return ds 47 | } 48 | } 49 | return "" 50 | } 51 | 52 | // B returns given path as bool 53 | func (o Object) B(path string) bool { 54 | if d := o.Get(path); d != nil { 55 | if db, ok := d.(bool); ok { 56 | return db 57 | } 58 | } 59 | return false 60 | } 61 | 62 | // I returns given path as int 63 | func (o Object) I(path string) int { 64 | if d := o.Get(path); d != nil { 65 | if di, ok := d.(int); ok { 66 | return di 67 | } 68 | if di, ok := d.(float64); ok { 69 | return int(di) 70 | } 71 | } 72 | return 0 73 | } 74 | 75 | // A returns given path as []interface{} 76 | func (o Object) A(path string) []interface{} { 77 | if d := o.Get(path); d != nil { 78 | if da, ok := d.([]interface{}); ok { 79 | return da 80 | } 81 | } 82 | return nil 83 | } 84 | 85 | // AStr returns given path as []string 86 | func (o Object) AStr(path string) []string { 87 | if d := o.Get(path); d != nil { 88 | if da, ok := d.([]interface{}); ok { 89 | dastr := make([]string, len(da)) 90 | for i, elm := range da { 91 | dastr[i] = fmt.Sprintf("%v", elm) 92 | } 93 | return dastr 94 | } 95 | } 96 | return nil 97 | } 98 | 99 | // Stringify the map to JSON string 100 | func (o Object) Stringify() string { 101 | return ToJSONString(o) 102 | } 103 | 104 | // NewObject by parsing the given byte data 105 | func NewObject(b []byte) (Object, error) { 106 | o := make(map[string]interface{}) 107 | err := json.Unmarshal(b, &o) 108 | return Object(o), err 109 | } 110 | -------------------------------------------------------------------------------- /client/public/js/navigation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * File navigation.js. 3 | * 4 | * Handles toggling the navigation menu for small screens and enables tab 5 | * support for dropdown menus. 6 | */ 7 | ( function() { 8 | var container, button, menu, links, subMenus, i, len; 9 | 10 | container = document.getElementById( 'site-navigation' ); 11 | if ( ! container ) { 12 | return; 13 | } 14 | 15 | button = container.getElementsByTagName( 'button' )[0]; 16 | if ( 'undefined' === typeof button ) { 17 | return; 18 | } 19 | 20 | menu = container.getElementsByTagName( 'ul' )[0]; 21 | 22 | // Hide menu toggle button if menu is empty and return early. 23 | if ( 'undefined' === typeof menu ) { 24 | button.style.display = 'none'; 25 | return; 26 | } 27 | 28 | menu.setAttribute( 'aria-expanded', 'false' ); 29 | if ( -1 === menu.className.indexOf( 'nav-menu' ) ) { 30 | menu.className += ' nav-menu'; 31 | } 32 | 33 | button.onclick = function() { 34 | if ( -1 !== container.className.indexOf( 'toggled' ) ) { 35 | container.className = container.className.replace( ' toggled', '' ); 36 | button.setAttribute( 'aria-expanded', 'false' ); 37 | menu.setAttribute( 'aria-expanded', 'false' ); 38 | } else { 39 | container.className += ' toggled'; 40 | button.setAttribute( 'aria-expanded', 'true' ); 41 | menu.setAttribute( 'aria-expanded', 'true' ); 42 | } 43 | }; 44 | 45 | // Get all the link elements within the menu. 46 | links = menu.getElementsByTagName( 'a' ); 47 | subMenus = menu.getElementsByTagName( 'ul' ); 48 | 49 | // Set menu items with submenus to aria-haspopup="true". 50 | for ( i = 0, len = subMenus.length; i < len; i++ ) { 51 | subMenus[i].parentNode.setAttribute( 'aria-haspopup', 'true' ); 52 | } 53 | 54 | // Each time a menu link is focused or blurred, toggle focus. 55 | for ( i = 0, len = links.length; i < len; i++ ) { 56 | links[i].addEventListener( 'focus', toggleFocus, true ); 57 | links[i].addEventListener( 'blur', toggleFocus, true ); 58 | } 59 | 60 | /** 61 | * Sets or removes .focus class on an element. 62 | */ 63 | function toggleFocus() { 64 | var self = this; 65 | 66 | // Move up through the ancestors of the current link until we hit .nav-menu. 67 | while ( -1 === self.className.indexOf( 'nav-menu' ) ) { 68 | 69 | // On li elements toggle the class .focus. 70 | if ( 'li' === self.tagName.toLowerCase() ) { 71 | if ( -1 !== self.className.indexOf( 'focus' ) ) { 72 | self.className = self.className.replace( ' focus', '' ); 73 | } else { 74 | self.className += ' focus'; 75 | } 76 | } 77 | 78 | self = self.parentElement; 79 | } 80 | } 81 | } )(); 82 | -------------------------------------------------------------------------------- /web/error.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | ) 7 | 8 | // Errors 9 | 10 | // Errors is a list of errors 11 | type Errors struct { 12 | Errors []*Error `json:"errors"` 13 | } 14 | 15 | // Error holds the info about a web error 16 | type Error struct { 17 | ID string `json:"id"` 18 | Status int `json:"status"` 19 | Title string `json:"title"` 20 | Detail string `json:"detail"` 21 | } 22 | 23 | // WriteError writes an error to the reply 24 | func WriteError(w http.ResponseWriter, err *Error) { 25 | w.Header().Set("Content-Type", "application/json") 26 | w.WriteHeader(err.Status) 27 | json.NewEncoder(w).Encode(Errors{[]*Error{err}}) 28 | } 29 | 30 | var ( 31 | // ErrBadRequest is a generic bad request 32 | ErrBadRequest = &Error{"bad_request", 400, "Bad request", "Request body is not well-formed. It must be JSON."} 33 | // ErrBadCaptcha is a captcha error 34 | ErrBadCaptcha = &Error{"bad_captcha", 400, "Bad CAPTCHA", "The provided CAPTCHA response does not match."} 35 | // ErrMissingPartRequest returns 400 if the request is missing some parts 36 | ErrMissingPartRequest = &Error{"missing_request", 400, "Bad request", "Request body is missing mandatory parts."} 37 | // ErrBadContentRequest if the request content is wrong 38 | ErrBadContentRequest = &Error{"bad_content", 400, "Bad content", "Request contains bad content"} 39 | // ErrAuth if not authenticated 40 | ErrAuth = &Error{"unauthorized", 401, "Unauthorized", "The request requires authorization"} 41 | // ErrCredentials if there are missing / wrong credentials 42 | ErrCredentials = &Error{"invalid_credentials", 401, "Invalid credentials", "Invalid username or password"} 43 | // ErrNotFound if file is not found 44 | ErrNotFound = &Error{"not_found", 404, "Not found", "The page you requested is not found"} 45 | // ErrNotAcceptable wrong accept header 46 | ErrNotAcceptable = &Error{"not_acceptable", 406, "Not Acceptable", "Accept header must be set to 'application/json'."} 47 | // ErrUnsupportedMediaType wrong media type 48 | ErrUnsupportedMediaType = &Error{"unsupported_media_type", 415, "Unsupported Media Type", "Content-Type header must be set to: 'application/json'."} 49 | // ErrCSRF missing CSRF cookie or parameter 50 | ErrCSRF = &Error{"forbidden", 403, "Forbidden", "Issue with CSRF code"} 51 | // ErrForbidden if request is forbidden to the user 52 | ErrForbidden = &Error{"forbidden", 403, "Forbidden", "Forbidden"} 53 | // ErrInternalServer if things go wrong on our side 54 | ErrInternalServer = &Error{"internal_server_error", 500, "Internal Server Error", "Something went wrong."} 55 | // ErrCouldNotFindTeam ... 56 | ErrCouldNotFindTeam = &Error{"could_find_team", 400, "Could not find slack team", "Could not find slack team"} 57 | ) 58 | -------------------------------------------------------------------------------- /client/config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(inputPath, needsSlash) { 15 | const hasSlash = inputPath.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return inputPath.substr(0, inputPath.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${inputPath}/`; 20 | } else { 21 | return inputPath; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right 45 | 46 |
47 |
48 |
49 |
50 |
51 |
52 | 88 |
89 |
90 |
91 |
92 |
93 |
94 | 95 |
96 |
97 |
98 |
99 |
100 |
101 | 116 |
117 |
118 |
119 |
120 | 121 |
122 | 123 |
124 |
125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /web/router.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "net" 7 | "net/http" 8 | "time" 9 | 10 | log "github.com/Sirupsen/logrus" 11 | "github.com/demisto/alfred/conf" 12 | "github.com/demisto/alfred/domain" 13 | "github.com/demisto/alfred/util" 14 | "github.com/julienschmidt/httprouter" 15 | "github.com/justinas/alice" 16 | ) 17 | 18 | // Main handlers 19 | var public string 20 | 21 | type requestContextKey string 22 | 23 | const ( 24 | contextUser = requestContextKey("user") 25 | contextBody = requestContextKey("body") 26 | contextSession = requestContextKey("session") 27 | contextParams = requestContextKey("params") 28 | ) 29 | 30 | func setRequestContext(r *http.Request, key requestContextKey, val interface{}) *http.Request { 31 | return r.WithContext(context.WithValue(r.Context(), key, val)) 32 | } 33 | 34 | func getRequestBody(r *http.Request) interface{} { 35 | return r.Context().Value(contextBody) 36 | } 37 | 38 | func getRequestUser(r *http.Request) *domain.User { 39 | v := r.Context().Value(contextUser) 40 | if v == nil { 41 | return nil 42 | } 43 | return v.(*domain.User) 44 | } 45 | 46 | func getRequestParams(r *http.Request) httprouter.Params { 47 | v := r.Context().Value(contextParams) 48 | if v == nil { 49 | return nil 50 | } 51 | return v.(httprouter.Params) 52 | } 53 | 54 | func getRequestSession(r *http.Request) *session { 55 | v := r.Context().Value(contextSession) 56 | if v == nil { 57 | return nil 58 | } 59 | return v.(*session) 60 | } 61 | 62 | func pageHandler(file string) func(w http.ResponseWriter, r *http.Request) { 63 | m := func(w http.ResponseWriter, r *http.Request) { 64 | log.Debugf("Looking for file %s\n", file) 65 | f, err := FS(conf.IsDev()).Open(file) 66 | if err != nil { 67 | log.Warnf("Could not find file %s - %v", file, err) 68 | WriteError(w, ErrNotFound) 69 | return 70 | } 71 | stat, err := f.Stat() 72 | if err != nil { 73 | log.Warnf("Could not stat file %s - %v", file, err) 74 | WriteError(w, ErrNotFound) 75 | return 76 | } 77 | http.ServeContent(w, r, file, stat.ModTime(), f) 78 | } 79 | return m 80 | } 81 | 82 | // Router 83 | 84 | // Router handles the web requests routing 85 | type Router struct { 86 | *httprouter.Router 87 | } 88 | 89 | // Get handles GET requests 90 | func (r *Router) Get(path string, handler http.Handler) { 91 | r.GET(path, wrapHandler(handler)) 92 | } 93 | 94 | // Post handles POST requests 95 | func (r *Router) Post(path string, handler http.Handler) { 96 | r.POST(path, wrapHandler(handler)) 97 | } 98 | 99 | // Put handles PUt requests 100 | func (r *Router) Put(path string, handler http.Handler) { 101 | r.PUT(path, wrapHandler(handler)) 102 | } 103 | 104 | // Delete handles DELETE requests 105 | func (r *Router) Delete(path string, handler http.Handler) { 106 | r.DELETE(path, wrapHandler(handler)) 107 | } 108 | 109 | // New creates a new router 110 | func New(appC *AppContext) *Router { 111 | r := &Router{httprouter.New()} 112 | staticHandlers := alice.New(loggingHandler, csrfHandler, recoverHandler) 113 | commonHandlers := staticHandlers.Append(acceptHandler) 114 | authHandlers := commonHandlers.Append(appC.authHandler) 115 | eventsHandler := alice.New(loggingHandler, recoverHandler) 116 | // Security 117 | r.Get("/oauth", staticHandlers.ThenFunc(appC.initiateOAuth)) 118 | r.Get("/auth", staticHandlers.ThenFunc(appC.loginOAuth)) 119 | r.Get("/logout", staticHandlers.ThenFunc(appC.logout)) 120 | r.Get("/user", authHandlers.ThenFunc(appC.currUser)) 121 | r.Get("/info", authHandlers.ThenFunc(appC.info)) 122 | r.Post("/match", authHandlers.Append(contentTypeHandler, bodyHandler(regexpMatch{})).ThenFunc(appC.match)) 123 | r.Post("/save", authHandlers.Append(contentTypeHandler, bodyHandler(domain.Configuration{})).ThenFunc(appC.save)) 124 | r.Get("/work", commonHandlers.ThenFunc(appC.work)) 125 | r.Post("/join", commonHandlers.Append(contentTypeHandler, bodyHandler(join{})).ThenFunc(appC.joinSlack)) 126 | r.Get("/messages", commonHandlers.ThenFunc(appC.totalMessages)) 127 | r.Post("/events", eventsHandler.Append(contentTypeHandler, bodyHandler(util.Object{})).ThenFunc(appC.events)) 128 | // Static 129 | r.Get("/", staticHandlers.ThenFunc(pageHandler("/index.html"))) 130 | r.Get("/conf", staticHandlers.ThenFunc(pageHandler("/conf.html"))) 131 | r.Get("/details", staticHandlers.ThenFunc(pageHandler("/details.html"))) 132 | r.Get("/faq", staticHandlers.ThenFunc(pageHandler("/faq.html"))) 133 | r.Get("/slackuser", staticHandlers.ThenFunc(pageHandler("/slackuser.html"))) 134 | r.Get("/privacy", staticHandlers.ThenFunc(pageHandler("/privacy.html"))) 135 | r.Get("/terms", staticHandlers.ThenFunc(pageHandler("/terms.html"))) 136 | r.Get("/banned", staticHandlers.ThenFunc(pageHandler("/banned.html"))) 137 | r.ServeFiles("/static/*filepath", Dir(conf.IsDev(), "/static/")) 138 | r.ServeFiles("/css/*filepath", Dir(conf.IsDev(), "/css/")) 139 | r.ServeFiles("/fonts/*filepath", Dir(conf.IsDev(), "/fonts/")) 140 | r.ServeFiles("/img/*filepath", Dir(conf.IsDev(), "/img/")) 141 | r.ServeFiles("/js/*filepath", Dir(conf.IsDev(), "/js/")) 142 | r.NotFound = staticHandlers.ThenFunc(pageHandler("/404.html")) 143 | return r 144 | } 145 | 146 | // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted 147 | // connections. It's used by ListenAndServe and ListenAndServeTLS so 148 | // dead TCP connections (e.g. closing laptop mid-download) eventually 149 | // go away. 150 | type tcpKeepAliveListener struct { 151 | *net.TCPListener 152 | } 153 | 154 | func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { 155 | tc, err := ln.AcceptTCP() 156 | if err != nil { 157 | return 158 | } 159 | if err = tc.SetKeepAlive(true); err != nil { 160 | return 161 | } 162 | if err = tc.SetKeepAlivePeriod(3 * time.Minute); err != nil { 163 | return 164 | } 165 | return tc, nil 166 | } 167 | 168 | func redirectToHTTPS(w http.ResponseWriter, r *http.Request) { 169 | http.Redirect(w, r, conf.Options.ExternalAddress+r.RequestURI, http.StatusMovedPermanently) 170 | } 171 | 172 | // Serve the routes based on configuration 173 | func (r *Router) Serve() { 174 | var err error 175 | if conf.Options.SSL.Cert != "" { 176 | // First, listen on the HTTP address with redirect 177 | go func() { 178 | err := http.ListenAndServe(conf.Options.HTTPAddress, http.HandlerFunc(redirectToHTTPS)) 179 | if err != nil { 180 | log.Fatal(err) 181 | } 182 | }() 183 | addr := conf.Options.Address 184 | if addr == "" { 185 | addr = ":https" 186 | } 187 | server := &http.Server{Addr: conf.Options.Address, Handler: r} 188 | config := &tls.Config{NextProtos: []string{"http/1.1"}} 189 | config.Certificates = make([]tls.Certificate, 1) 190 | config.Certificates[0], err = tls.X509KeyPair([]byte(conf.Options.SSL.Cert), []byte(conf.Options.SSL.Key)) 191 | if err != nil { 192 | log.Fatal(err) 193 | } 194 | ln, err := net.Listen("tcp", addr) 195 | if err != nil { 196 | log.Fatal(err) 197 | } 198 | tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config) 199 | err = server.Serve(tlsListener) 200 | } else { 201 | err = http.ListenAndServe(conf.Options.Address, r) 202 | } 203 | if err != nil { 204 | log.Fatal(err) 205 | } 206 | } 207 | 208 | func wrapHandler(h http.Handler) httprouter.Handle { 209 | return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 210 | setRequestContext(r, contextParams, ps) 211 | h.ServeHTTP(w, r) 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /web/middleware.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "compress/gzip" 5 | "encoding/json" 6 | "net/http" 7 | "reflect" 8 | "strings" 9 | "time" 10 | 11 | log "github.com/Sirupsen/logrus" 12 | "github.com/demisto/alfred/conf" 13 | "github.com/demisto/alfred/domain" 14 | "github.com/demisto/alfred/util" 15 | ) 16 | 17 | func recoverHandler(next http.Handler) http.Handler { 18 | fn := func(w http.ResponseWriter, r *http.Request) { 19 | defer func() { 20 | if err := recover(); err != nil { 21 | log.WithField("error", err).Warn("Recovered from error") 22 | WriteError(w, ErrInternalServer) 23 | } 24 | }() 25 | 26 | next.ServeHTTP(w, r) 27 | } 28 | 29 | return http.HandlerFunc(fn) 30 | } 31 | 32 | type loggingResponseWriter struct { 33 | http.ResponseWriter 34 | status int 35 | } 36 | 37 | func (l *loggingResponseWriter) WriteHeader(status int) { 38 | l.status = status 39 | l.ResponseWriter.WriteHeader(status) 40 | } 41 | 42 | func loggingHandler(next http.Handler) http.Handler { 43 | fn := func(w http.ResponseWriter, r *http.Request) { 44 | lw := &loggingResponseWriter{w, 200} 45 | t1 := time.Now() 46 | next.ServeHTTP(lw, r) 47 | t2 := time.Now() 48 | log.Infof("[%s] %q %v %v\n", r.Method, r.URL.String(), lw.status, t2.Sub(t1)) 49 | } 50 | 51 | return http.HandlerFunc(fn) 52 | } 53 | 54 | func acceptHandler(next http.Handler) http.Handler { 55 | fn := func(w http.ResponseWriter, r *http.Request) { 56 | if !strings.Contains(r.Header.Get("Accept"), "application/json") { 57 | log.Warn("Request without accept header received") 58 | WriteError(w, ErrNotAcceptable) 59 | return 60 | } 61 | 62 | next.ServeHTTP(w, r) 63 | } 64 | 65 | return http.HandlerFunc(fn) 66 | } 67 | 68 | func contentTypeHandler(next http.Handler) http.Handler { 69 | fn := func(w http.ResponseWriter, r *http.Request) { 70 | if !strings.Contains(r.Header.Get("Content-Type"), "application/json") { 71 | log.Warn("Request without proper content type received") 72 | WriteError(w, ErrUnsupportedMediaType) 73 | return 74 | } 75 | 76 | next.ServeHTTP(w, r) 77 | } 78 | 79 | return http.HandlerFunc(fn) 80 | } 81 | 82 | const ( 83 | encodingGzip = "gzip" 84 | 85 | headerAcceptEncoding = "Accept-Encoding" 86 | headerContentEncoding = "Content-Encoding" 87 | headerContentLength = "Content-Length" 88 | headerContentType = "Content-Type" 89 | headerVary = "Vary" 90 | 91 | bestCompression = gzip.BestCompression 92 | bestSpeed = gzip.BestSpeed 93 | defaultCompression = gzip.DefaultCompression 94 | noCompression = gzip.NoCompression 95 | ) 96 | 97 | type gzipWriter struct { 98 | http.ResponseWriter 99 | gzwriter *gzip.Writer 100 | } 101 | 102 | func newGzipWriter(writer http.ResponseWriter, gzwriter *gzip.Writer) *gzipWriter { 103 | return &gzipWriter{writer, gzwriter} 104 | } 105 | 106 | func (g *gzipWriter) Write(data []byte) (int, error) { 107 | return g.gzwriter.Write(data) 108 | } 109 | 110 | func doGzip(level int) func(http.Handler) http.Handler { 111 | m := func(next http.Handler) http.Handler { 112 | fn := func(w http.ResponseWriter, r *http.Request) { 113 | if !strings.Contains(r.Header.Get(headerAcceptEncoding), encodingGzip) { 114 | next.ServeHTTP(w, r) 115 | return 116 | } 117 | gz, err := gzip.NewWriterLevel(w, level) 118 | if err != nil { 119 | next.ServeHTTP(w, r) 120 | return 121 | } 122 | defer gz.Close() 123 | headers := w.Header() 124 | headers.Set(headerContentEncoding, encodingGzip) 125 | headers.Set(headerVary, headerAcceptEncoding) 126 | gzwriter := newGzipWriter(w, gz) 127 | next.ServeHTTP(gzwriter, r) 128 | w.Header().Del(headerContentLength) 129 | } 130 | return http.HandlerFunc(fn) 131 | } 132 | return m 133 | } 134 | 135 | func bodyHandler(v interface{}) func(http.Handler) http.Handler { 136 | t := reflect.TypeOf(v) 137 | 138 | m := func(next http.Handler) http.Handler { 139 | fn := func(w http.ResponseWriter, r *http.Request) { 140 | val := reflect.New(t).Interface() 141 | err := json.NewDecoder(r.Body).Decode(val) 142 | 143 | if err != nil { 144 | log.WithFields(log.Fields{"body": r.Body, "err": err}).Warn("Error handling body") 145 | WriteError(w, ErrBadRequest) 146 | return 147 | } 148 | if next != nil { 149 | r = setRequestContext(r, contextBody, val) 150 | next.ServeHTTP(w, r) 151 | } 152 | } 153 | 154 | return http.HandlerFunc(fn) 155 | } 156 | 157 | return m 158 | } 159 | 160 | const ( 161 | // xsrfCookie is the name of the XSRF cookie 162 | xsrfCookie = `XSRF` 163 | // xsrfHeader is the name of the expected header 164 | xsrfHeader = `X-XSRF-TOKEN` 165 | // noXsrfAllowed is the error message 166 | noXSRFAllowed = `No XSRF Allowed` 167 | ) 168 | 169 | // Handle CSRF protection 170 | func csrfHandler(next http.Handler) http.Handler { 171 | fn := func(w http.ResponseWriter, r *http.Request) { 172 | csrf, err := r.Cookie(xsrfCookie) 173 | csrfHeader := r.Header.Get(xsrfHeader) 174 | ok := false 175 | secure := conf.Options.SSL.Key != "" 176 | pass := conf.Options.Security.SessionKey 177 | // If it is an idempotent method, set the cookie 178 | if r.Method == "GET" || r.Method == "HEAD" { 179 | if err != nil { 180 | val, cErr := util.Encrypt(noXSRFAllowed+time.Now().String(), pass) 181 | if cErr == nil { 182 | http.SetCookie(w, &http.Cookie{Name: xsrfCookie, Value: val, Path: "/", Expires: time.Now().Add(365 * 24 * time.Hour), MaxAge: 365 * 24 * 60 * 60, Secure: secure, HttpOnly: false}) 183 | } else { 184 | log.WithField("error", cErr).Error("Unable to generate CSRF") 185 | } 186 | } 187 | ok = true 188 | } else if err == nil && csrf.Value == csrfHeader { 189 | val, cErr := util.Decrypt(csrfHeader, pass) 190 | if cErr == nil && strings.HasPrefix(val, noXSRFAllowed) { 191 | ok = true 192 | } 193 | } 194 | if ok { 195 | next.ServeHTTP(w, r) 196 | } else { 197 | WriteError(w, ErrCSRF) 198 | } 199 | } 200 | return http.HandlerFunc(fn) 201 | } 202 | 203 | const ( 204 | sessionCookie = `SES` 205 | ) 206 | 207 | func (ac *AppContext) authHandler(next http.Handler) http.Handler { 208 | fn := func(w http.ResponseWriter, r *http.Request) { 209 | cookie, err := r.Cookie(sessionCookie) 210 | // No session, bye bye 211 | if err != nil { 212 | log.Info("Access to authenticated service without session") 213 | WriteError(w, ErrAuth) 214 | return 215 | } 216 | var sess session 217 | err = util.DecryptJSON(cookie.Value, conf.Options.Security.SessionKey, &sess) 218 | if err != nil { 219 | log.WithFields(log.Fields{"cookie": cookie.Value, "error": err}).Warn("Unable to decrypt encrypted session") 220 | WriteError(w, ErrAuth) 221 | return 222 | } 223 | // If the session is no longer valid 224 | if time.Since(sess.When) > time.Duration(conf.Options.Security.Timeout)*time.Minute { 225 | log.Debug("Session timeout") 226 | WriteError(w, ErrAuth) 227 | return 228 | } 229 | setRequestContext(r, contextSession, &sess) 230 | log.Debugf("User %v in request", sess.User) 231 | u, err := ac.r.User(sess.UserID) 232 | if err != nil { 233 | log.WithFields(log.Fields{"username": sess.User, "id": sess.UserID, "error": err}).Warn("Unable to load user from repository") 234 | panic(err) 235 | } 236 | if u.Status != domain.UserStatusActive { 237 | log.Debugf("User %s (%s) tried to login but revoked the token", u.ID, u.Name) 238 | WriteError(w, ErrAuth) 239 | return 240 | } 241 | setRequestContext(r, contextUser, u) 242 | // Set the new cookie for the user with the new timeout 243 | sess.When = time.Now() 244 | secure := conf.Options.SSL.Key != "" 245 | val, _ := util.EncryptJSON(&sess, conf.Options.Security.SessionKey) 246 | http.SetCookie(w, &http.Cookie{ 247 | Name: sessionCookie, 248 | Value: val, 249 | Path: "/", 250 | Expires: time.Now().Add(time.Duration(conf.Options.Security.Timeout) * time.Minute), 251 | MaxAge: conf.Options.Security.Timeout * 60, 252 | Secure: secure, 253 | HttpOnly: true}) 254 | next.ServeHTTP(w, r) 255 | } 256 | return http.HandlerFunc(fn) 257 | } 258 | --------------------------------------------------------------------------------