diff --git a/.drone.yml b/.drone.yml index bec436627a..1d73a49aa7 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,16 +1,9 @@ --- kind: pipeline name: default - -# Disable default clone -clone: - disable: true +type: docker steps: - # This clone step doesn't use "root" user - - name: clone - image: plugins/git:next - # Restore cache of downloaded dependencies - name: restore-cache image: drillster/drone-volume-cache @@ -27,8 +20,7 @@ steps: - name: run-tests image: thehiveproject/drone-scala-node commands: - - . ~/.nvm/nvm.sh - - sbt -Duser.home=$PWD test stage + - sbt -Duser.home=$PWD test:compile test # Build packages - name: build-packages @@ -37,9 +29,23 @@ steps: pgp_key: {from_secret: pgp_key} commands: - | + V=$(sbt -no-colors --error "print thehive/version" | tail -1) + if ( echo $V | grep -qi snapshot) + then + exit 1 + fi . ~/.nvm/nvm.sh [ -n "$PLUGIN_PGP_KEY" ] && gpg --batch --import - <<< $PLUGIN_PGP_KEY sbt -Duser.home=$PWD docker:stage debian:packageBin rpm:packageBin universal:packageBin + if ( echo $V | grep -qi rc ) + then + echo $( echo $V | sed -re 's/([0-9]+.[0-9]+.[0-9]+)-RC([0-9]+)-([0-9]+)/\1-RC\2,\1-RC\2-\3/' ) > .tags + else + echo $( echo $V | sed -re 's/([0-9]+).([0-9]+).([0-9]+)-([0-9]+)/\1,\1.\2,\1.\2.\3,\1.\2.\3-\4,latest/' ) > .tags + fi + echo $V > thehive-version.txt + mv target/rpm/RPMS/noarch/thehive*.rpm target/ + mv target/universal/thehive*.zip target/ when: event: [tag] @@ -48,156 +54,89 @@ steps: image: drillster/drone-volume-cache settings: rebuild: true + backend: "filesystem" mount: - .sbt - .ivy2 + - .cache - ui/node_modules - ui/bower_components volumes: [{name: cache, path: /cache}] - - name: publish-package - image: thehiveproject/drone-bintray + # Send packages using scp + - name: send packages + image: appleboy/drone-scp settings: - user: {from_secret: bintray_user} - key: {from_secret: bintray_key} - subject: thehive-project - package: thehive - commands: - - | - export PLUGIN_USER - export PLUGIN_KEY - export PLUGIN_SUBJECT - export PLUGIN_PACKAGE - export PLUGIN_VERSION=$(cut -d\" -f2 version.sbt) - echo "Publishing package version $PLUGIN_VERSION" - - if echo $PLUGIN_VERSION | grep -qvi -E \ - -e '^[0-9]+\.[0-9]+\.[0-9]+$' \ - -e '^[0-9]+\.[0-9]+\.[0-9]+-[0-9]+$' \ - -e '^[0-9]+\.[0-9]+\.[0-9]+-RC[0-9]+$'; then - echo The version $PLUGIN_VERSION has invalid format - exit 1 - fi - - CHANNEL=stable - if $(echo $PLUGIN_VERSION | grep -qi rc) - then - CHANNEL=beta - V=$(echo $PLUGIN_VERSION | sed -e 's/-\([rR][cC]\)/-0.1\1/') - DEB_FILE=target/thehive_$${V}_all.deb - RPM_FILE=target/rpm/RPMS/noarch/thehive-$${V}.noarch.rpm - else - DEB_FILE=target/thehive_$${PLUGIN_VERSION}_all.deb - RPM_FILE=target/rpm/RPMS/noarch/thehive-$${PLUGIN_VERSION}.noarch.rpm - fi - - ZIP_FILE=target/universal/thehive-$${PLUGIN_VERSION}.zip - - upload \ - --file $DEB_FILE \ - --repo debian-beta \ - --extra-param deb_distribution=any \ - --extra-param deb_component=main \ - --extra-param deb_architecture=all - - [ $CHANNEL = stable ] && upload \ - --file $DEB_FILE \ - --repo debian-stable \ - --extra-param deb_distribution=any \ - --extra-param deb_component=main \ - --extra-param deb_architecture=all - - upload \ - --file $RPM_FILE \ - --repo rpm-beta - - [ $CHANNEL = stable ] && upload \ - --file $RPM_FILE \ - --repo rpm-stable - - upload \ - --file $ZIP_FILE \ - --repo binary - - LATEST_VERSION=latest - [ $CHANNEL = beta ] && LATEST_VERSION=latest-beta - - removeVersion \ - --repo binary \ - --version $LATEST_VERSION + host: {from_secret: package_host} + username: {from_secret: package_user} + key: {from_secret: package_key} + target: {from_secret: incoming_path} + source: + - target/thehive*.deb + - target/thehive*.rpm + - target/thehive*.zip + strip_components: 1 + when: + event: [tag] - upload \ - --file $ZIP_FILE \ - --repo binary \ - --version $LATEST_VERSION \ - --dest-file thehive-$${LATEST_VERSION}.zip + # Publish packages + - name: publish packages + image: appleboy/drone-ssh + settings: + host: {from_secret: package_host} + user: {from_secret: package_user} + key: {from_secret: package_key} + publish_script: {from_secret: publish_script} + commands: + - PLUGIN_SCRIPT="bash $PLUGIN_PUBLISH_SCRIPT thehive $(cat thehive-version.txt)" /bin/drone-ssh when: event: [tag] - # Publish docker image + # Publish docker image on Docker Hub - name: docker image: plugins/docker settings: context: target/docker/stage dockerfile: target/docker/stage/Dockerfile repo: thehiveproject/thehive - auto_tag: true username: {from_secret: docker_username} password: {from_secret: docker_password} when: event: [tag] - # Deploy binaries in integration environment - - name: copy binaries in integration environment - image: appleboy/drone-scp - settings: - host: {from_secret: deploy_beta_host} - username: {from_secret: deploy_username} - key: {from_secret: deploy_key} - target: ./thehive-builds/${DRONE_BUILD_NUMBER} - source: target/universal/stage - strip_components: 3 - when: - branch: [develop] - event: {exclude: [pull_request]} - - - name: deploy binaries in integration environment - image: appleboy/drone-ssh - settings: - host: {from_secret: deploy_beta_host} - username: {from_secret: deploy_username} - key: {from_secret: deploy_key} - script: - - ./start thehive ${DRONE_BUILD_NUMBER} - when: - branch: [develop] - event: {exclude: [pull_request]} - - # Deploy binaries in staging environment - - name: copy binaries in staging environment - image: appleboy/drone-scp + # Publish docker image on Harbor + - name: harbor + image: plugins/docker settings: - host: {from_secret: deploy_stable_host} - username: {from_secret: deploy_username} - key: {from_secret: deploy_key} - target: ./thehive-builds/${DRONE_BUILD_NUMBER} - source: target/universal/stage - strip_components: 3 + context: target/docker/stage + dockerfile: target/docker/stage/Dockerfile + registry: {from_secret: harbor_registry} + repo: {from_secret: harbor_repo} + username: {from_secret: harbor_username} + password: {from_secret: harbor_password} when: - branch: [master] - event: {exclude: [pull_request]} + event: [tag] - - name: deploy binaries in staging environment - image: appleboy/drone-ssh + - name: send message + image: thehiveproject/drone_keybase settings: - host: {from_secret: deploy_stable_host} - username: {from_secret: deploy_username} - key: {from_secret: deploy_key} - script: - - ./start thehive ${DRONE_BUILD_NUMBER} + username: {from_secret: keybase_username} + paperkey: {from_secret: keybase_paperkey} + channel: {from_secret: keybase_channel} + commands: + - | + keybase oneshot -u "$PLUGIN_USERNAME" --paperkey "$PLUGIN_PAPERKEY" + URL="$DRONE_SYSTEM_PROTO://$DRONE_SYSTEM_HOST/$DRONE_REPO/$DRONE_BUILD_NUMBER" + if [ $DRONE_BUILD_STATUS = "success" ] + then + keybase chat send "$PLUGIN_CHANNEL" ":white_check_mark: $DRONE_REPO: build succeeded $URL" + else + keybase chat send "$PLUGIN_CHANNEL" ":x: $DRONE_REPO: build failed $URL" + fi when: - branch: [master] - event: {exclude: [pull_request]} + status: + - success + - failure volumes: - name: cache diff --git a/CHANGELOG.md b/CHANGELOG.md index 31f7833080..8825d1ec63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Change Log +## [3.5.0-RC1](https://github.com/TheHive-Project/TheHive/milestone/44) (2020-08-12) + +**Implemented enhancements:** + +- Support of ElasticSearch 7 [\#1377](https://github.com/TheHive-Project/TheHive/issues/1377) +- [Enhancement] MISP sync [\#1398](https://github.com/TheHive-Project/TheHive/issues/1398) + +**Closed issues:** + +- OAuth2 not working : Authentication failure [\#946](https://github.com/TheHive-Project/TheHive/issues/946) + +**Fixed bugs:** + +- [Bug] OAuth2/OpenIDC Authentication failure [\#1291](https://github.com/TheHive-Project/TheHive/issues/1291) +- [Feature Request] OAuth support for Basic authentication to authorization server's tokenUrl [\#1294](https://github.com/TheHive-Project/TheHive/issues/1294) +- [Bug] Can't auth with SSO/OAuth with FusionAuth [\#1342](https://github.com/TheHive-Project/TheHive/issues/1342) +- [Bug] TheHive is stalled while importing Alerts with a large number of observables [\#1416](https://github.com/TheHive-Project/TheHive/issues/1416) + ## [3.4.2](https://github.com/TheHive-Project/TheHive/milestone/57) (2020-04-25) **Implemented enhancements:** diff --git a/build.sbt b/build.sbt index 82efa88501..992f41566f 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,6 @@ import Common._ import Dependencies._ +import org.thp.ghcl.Milestone lazy val thehiveBackend = (project in file("thehive-backend")) .enablePlugins(PlayScala) @@ -17,6 +18,7 @@ lazy val thehiveBackend = (project in file("thehive-backend")) Library.zip4j, Library.reflections, Library.akkaCluster, + Library.akkaClusterTyped, Library.akkaClusterTools ), play.sbt.routes.RoutesKeys.routesImport -= "controllers.Assets.Asset" @@ -105,6 +107,8 @@ rpmReleaseFile := { rpmFile } +milestoneFilter := ((milestone: Milestone) ⇒ milestone.title.head < '4') + bintrayOrganization := Some("thehive-project") // Front-end // diff --git a/contrib/.gitignore b/contrib/.gitignore deleted file mode 100644 index 71d918b0bb..0000000000 --- a/contrib/.gitignore +++ /dev/null @@ -1 +0,0 @@ -report-templates/*.zip diff --git a/contrib/report-templates/Abuse_Finder_1.0/long.html b/contrib/report-templates/Abuse_Finder_1.0/long.html deleted file mode 100644 index ee902b1b50..0000000000 --- a/contrib/report-templates/Abuse_Finder_1.0/long.html +++ /dev/null @@ -1,33 +0,0 @@ -
-
- Abuse Finder Information for {{artifact.data}} -
-
-
-
Names:
-
-
- {{name}} -
-
-
-
-
Abuse addresses:
-
-
- {{abuse}} -
-
-
-
-
- - -
-
- {{(artifact.data || artifact.attachment.name) | fang}} -
-
- {{content.errorMessage}} -
-
diff --git a/contrib/report-templates/DNSDB_DomainName_1_1/long.html b/contrib/report-templates/DNSDB_DomainName_1_1/long.html deleted file mode 100644 index cb1eb66e8e..0000000000 --- a/contrib/report-templates/DNSDB_DomainName_1_1/long.html +++ /dev/null @@ -1,46 +0,0 @@ -
-
- {{(artifact.data || artifact.attachment.name) | fang}} -
-
- {{content.errorMessage}} -
-
- -
-
- {{(artifact.data || artifact.attachment.name) | fang}} - View All ({{::content.records.length}}) -
-
-

- DNSDB Domain Name History Report (RRSET) -

- - - - - - - - - - - - - - - - - - - - - - - -
#bailiwickcountrdatarrnametime firsttime last
{{$index+1}}{{row.bailiwick}}{{row.count}} -
{{rdata}}
-
{{row.rrname}}{{(row.zone_time_first || row.time_first) | shortDate}}{{(row.zone_time_last || row.time_last) | shortDate}}
-
-
diff --git a/contrib/report-templates/DNSDB_DomainName_1_1/short.html b/contrib/report-templates/DNSDB_DomainName_1_1/short.html deleted file mode 100644 index f0ae84c374..0000000000 --- a/contrib/report-templates/DNSDB_DomainName_1_1/short.html +++ /dev/null @@ -1 +0,0 @@ -DNSDB Domain Name: {{content.records}} records diff --git a/contrib/report-templates/DNSDB_IPHistory_1_0/long.html b/contrib/report-templates/DNSDB_IPHistory_1_0/long.html deleted file mode 100644 index 5cb5ced89b..0000000000 --- a/contrib/report-templates/DNSDB_IPHistory_1_0/long.html +++ /dev/null @@ -1,43 +0,0 @@ -
-
- {{(artifact.data || artifact.attachment.name) | fang}} -
-
- {{content.errorMessage}} -
-
- -
-
- {{(artifact.data || artifact.attachment.name) | fang}} - View All ({{::content.records.length}}) -
-
-

- DNSDB IP History Report - (Rdata IP) -

- - - - - - - - - - - - - - - - - - - - - -
#countrdatarrnamerrtypetime firsttime last
{{$index + 1}}{{row.count}}{{row.rdata}} class="wrap"{{row.rrname}}{{row.rrtype}}{{(row.zone_time_first || row.time_first) | shortDate}}{{(row.zone_time_last || row.time_last) | shortDate}}
-
-
diff --git a/contrib/report-templates/DNSDB_IPHistory_1_0/short.html b/contrib/report-templates/DNSDB_IPHistory_1_0/short.html deleted file mode 100644 index 4402a33419..0000000000 --- a/contrib/report-templates/DNSDB_IPHistory_1_0/short.html +++ /dev/null @@ -1 +0,0 @@ -DNSDB IP History: {{content.records}} records diff --git a/contrib/report-templates/DNSDB_NameHistory_1_0/long.html b/contrib/report-templates/DNSDB_NameHistory_1_0/long.html deleted file mode 100644 index d7031c9a69..0000000000 --- a/contrib/report-templates/DNSDB_NameHistory_1_0/long.html +++ /dev/null @@ -1,42 +0,0 @@ -
-
- {{(artifact.data || artifact.attachment.name) | fang}} -
-
- {{content.errorMessage}} -
-
- -
-
- {{(artifact.data || artifact.attachment.name) | fang}} - View All ({{::content.records.length}}) -
-
-

- DNSDB Name History Report(Rdata Name) -

- - - - - - - - - - - - - - - - - - - - - -
#countrdatarrnamerrtypetime firsttime last
{{$index + 1}}{{row.count}}{{row.rdata}}{{row.rrname}}{{row.rrtype}}{{row.time_first | shortDate}}{{row.time_last | shortDate}}
-
-
diff --git a/contrib/report-templates/DNSDB_NameHistory_1_0/short.html b/contrib/report-templates/DNSDB_NameHistory_1_0/short.html deleted file mode 100644 index 3271f99b00..0000000000 --- a/contrib/report-templates/DNSDB_NameHistory_1_0/short.html +++ /dev/null @@ -1 +0,0 @@ -DNSDB Name History: {{content.records}} records diff --git a/contrib/report-templates/DomainTools_ReverseIP_1_0/long.html b/contrib/report-templates/DomainTools_ReverseIP_1_0/long.html deleted file mode 100644 index 89c1bc2164..0000000000 --- a/contrib/report-templates/DomainTools_ReverseIP_1_0/long.html +++ /dev/null @@ -1,30 +0,0 @@ -
-
- {{artifact.data | fang}} -
-
-
-
Domains count
-
{{content.ip_addresses.domain_count}}
-
-
-
Domain Names
-
-
    -
  • - {{dn}} -
  • -
-
-
-
-
- -
-
- {{artifact.data | fang}} -
-
- {{content.errorMessage}} -
-
diff --git a/contrib/report-templates/DomainTools_ReverseIP_1_0/short.html b/contrib/report-templates/DomainTools_ReverseIP_1_0/short.html deleted file mode 100644 index 8b91e7d98e..0000000000 --- a/contrib/report-templates/DomainTools_ReverseIP_1_0/short.html +++ /dev/null @@ -1 +0,0 @@ -{{content.ip.address}}: {{content.ip.domain_count}} domains found diff --git a/contrib/report-templates/DomainTools_ReverseNameServer_1_0/long.html b/contrib/report-templates/DomainTools_ReverseNameServer_1_0/long.html deleted file mode 100644 index 470227e411..0000000000 --- a/contrib/report-templates/DomainTools_ReverseNameServer_1_0/long.html +++ /dev/null @@ -1,43 +0,0 @@ -
-
- {{artifact.data | fang}} -
-
- {{content.errorMessage}} -
-
-
-
- {{artifact.data | fang}} -
-
-
-
Name Server
-
{{content.name_server.hostname}}
-
-
-
Primary domains
-
- {{content.name_server.primary}}
- Show all -
-
-
-
Secondary domains
-
- {{content.name_server.secondary}}
- Show all -
-
- -
-
Primary Domains List
-
{{content.primary_domains | json}}
-
-
-
Secondary Domains List
-
{{content.secondary_domains | json}}
-
- -
-
diff --git a/contrib/report-templates/DomainTools_ReverseNameServer_1_0/short.html b/contrib/report-templates/DomainTools_ReverseNameServer_1_0/short.html deleted file mode 100644 index 2f4c374ea0..0000000000 --- a/contrib/report-templates/DomainTools_ReverseNameServer_1_0/short.html +++ /dev/null @@ -1 +0,0 @@ -Hostname: {{content.name_server}}, {{content.domain_count}} domains diff --git a/contrib/report-templates/DomainTools_ReverseWhois_1_0/long.html b/contrib/report-templates/DomainTools_ReverseWhois_1_0/long.html deleted file mode 100644 index b2f8fe07bb..0000000000 --- a/contrib/report-templates/DomainTools_ReverseWhois_1_0/long.html +++ /dev/null @@ -1,34 +0,0 @@ -
-
- {{artifact.data | fang}} -
-
-
-
Current Domains
-
{{content.domain_count.current}}
-
-
-
Historic Domains
-
{{content.domain_count.historic}}
-
-
-
Domains
-
-
    -
  • - {{dn}} -
  • -
-
-
-
-
- -
-
- {{artifact.data | fang}} -
-
- {{content.errorMessage}} -
-
diff --git a/contrib/report-templates/DomainTools_ReverseWhois_1_0/short.html b/contrib/report-templates/DomainTools_ReverseWhois_1_0/short.html deleted file mode 100644 index 015b9f0fa0..0000000000 --- a/contrib/report-templates/DomainTools_ReverseWhois_1_0/short.html +++ /dev/null @@ -1,3 +0,0 @@ - - Domains found: curr:{{content.domain_count.current}}/hist:{{content.domain_count.historic}} - diff --git a/contrib/report-templates/DomainTools_WhoisHistory_1_0/long.html b/contrib/report-templates/DomainTools_WhoisHistory_1_0/long.html deleted file mode 100644 index 3d235f6000..0000000000 --- a/contrib/report-templates/DomainTools_WhoisHistory_1_0/long.html +++ /dev/null @@ -1,48 +0,0 @@ -
-
- {{artifact.data | fang}} -
-
- {{content.errorMessage}} -
-
- -
-
- {{artifact.data| fang}} - View All ({{::content.history.length}}) -
-
-
No records found
- - - - - - - - - - - - - - - - - - - - - -
#dateserver namesregistrantregistrarexpirationstatuses
{{$index + 1}}{{row.date}} -
    -
  • {{ns}}
  • -
-
{{row.whois.registrant}}{{row.whois.registration.registrar}}{{row.whois.registration.expires}} -
    -
  • {{st}}
  • -
-
-
-
diff --git a/contrib/report-templates/DomainTools_WhoisHistory_1_0/short.html b/contrib/report-templates/DomainTools_WhoisHistory_1_0/short.html deleted file mode 100644 index 3e748cbb2c..0000000000 --- a/contrib/report-templates/DomainTools_WhoisHistory_1_0/short.html +++ /dev/null @@ -1,2 +0,0 @@ -REGISTRANT: {{content.registrant}} -REGISTRAR: {{content.registrar}} diff --git a/contrib/report-templates/DomainTools_WhoisLookup_1_0/long.html b/contrib/report-templates/DomainTools_WhoisLookup_1_0/long.html deleted file mode 100644 index 2cdab86513..0000000000 --- a/contrib/report-templates/DomainTools_WhoisLookup_1_0/long.html +++ /dev/null @@ -1,21 +0,0 @@ -
-
- {{artifact.data | fang}} -
-
- {{content.errorMessage}} -
-
- -
-
- {{artifact.data | fang}} -
-
-
-
Date of record
-
{{content.whois.date}}
-
-
{{content.whois.record}}
-
-
diff --git a/contrib/report-templates/DomainTools_WhoisLookup_1_0/short.html b/contrib/report-templates/DomainTools_WhoisLookup_1_0/short.html deleted file mode 100644 index 3e748cbb2c..0000000000 --- a/contrib/report-templates/DomainTools_WhoisLookup_1_0/short.html +++ /dev/null @@ -1,2 +0,0 @@ -REGISTRANT: {{content.registrant}} -REGISTRAR: {{content.registrar}} diff --git a/contrib/report-templates/DomainTools_WhoisLookup_IP_1_0/long.html b/contrib/report-templates/DomainTools_WhoisLookup_IP_1_0/long.html deleted file mode 100644 index 2cdab86513..0000000000 --- a/contrib/report-templates/DomainTools_WhoisLookup_IP_1_0/long.html +++ /dev/null @@ -1,21 +0,0 @@ -
-
- {{artifact.data | fang}} -
-
- {{content.errorMessage}} -
-
- -
-
- {{artifact.data | fang}} -
-
-
-
Date of record
-
{{content.whois.date}}
-
-
{{content.whois.record}}
-
-
diff --git a/contrib/report-templates/DomainTools_WhoisLookup_IP_1_0/short.html b/contrib/report-templates/DomainTools_WhoisLookup_IP_1_0/short.html deleted file mode 100644 index f95ed23abc..0000000000 --- a/contrib/report-templates/DomainTools_WhoisLookup_IP_1_0/short.html +++ /dev/null @@ -1,2 +0,0 @@ -DT:Whois:REGISTRANT= {{content.registrant}} -DT:Whois:REGISTRAR= {{content.registrar}} diff --git a/contrib/report-templates/File_Info_1_0/long.html b/contrib/report-templates/File_Info_1_0/long.html deleted file mode 100644 index 7d72d879d9..0000000000 --- a/contrib/report-templates/File_Info_1_0/long.html +++ /dev/null @@ -1,391 +0,0 @@ -
- - - -
-
- File Identification -
-
-
-
MD5
-
{{content.Identification['MD5']}}
-
-
-
SHA1
-
{{content.Identification['SHA1']}}
-
-
-
SHA256
-
{{content.Identification['SHA256']}}
-
-
-
impash
-
{{content.Identification['impash']|| "-"}} -
-
-
-
ssdeep
-
{{content.Identification['ssdeep']|| "-"}} -
-
-
-
pehash
-
{{content.Identification['pehash']|| "-"}} -
-
-
-
Operating System
-
{{content.Identification['OperatingSystem']}} -
-
-
-
PE Type
-
{{content.Identification['PEType']}}
-
-
-
Magic literal
-
{{content.Magic}}
-
-
-
MimeType
-
{{content.Mimetype}}
-
-
-
- - -
-
- File Metadata (Exiftool) -
-
-
-
{{k}}
-
{{v}}
-
- -
-
- - -
-
- PE Basic Information -
-
-
-
{{I.Info}}
-
{{I.Value}}
-
-
-
Compilation Timestamp
-
{{content.PE.BasicInformation.CompilationTimestamp}}
-
-
-
File Size
-
{{content.PE.BasicInformation.FileSize}}
-
-
-
Entry Point (EP)
-
{{content.PE.BasicInformation.EntryPoint}}
-
-
-
Target Machine
-
{{content.PE.BasicInformation.TargetMachine}}
-
-
-
- - -
-
- PE Sections -
-
- - - - - - - - - - - - - - - - -
SectionSizeOfRawDataEntroy
- {{section.entryname}} - {{section.SizeOfRawData}}{{section.Entropy}}
-
-
- MD5 -
-
- {{section.MD5}} -
-
-
-
- SHA1 -
-
- {{section.SHA1}} -
-
-
-
- SHA256 -
-
- {{section.SHA256}} -
-
-
-
-
- - - -
-
- PE Import Address Table -
-
-
-
- - - - {{entry.entryname}} -
- -
-
- {{entry.symbols.length}} - items -
-
-
- {{sym}} -
-
-
-
-
-
- - -
-
- Olevba Report -
-
-

Summary

-
-
Olevba version:
-
v{{content.MSOffice.olevba.Version}}
-
- -
-
Olevba detection :
-
{{content.MSOffice.olevba.vba}}
-
- -
-
Olevba scanner :
-
- - - - Not suspicious - Suspicious VBA -   - - Base64 strings -   - - Hex strings - - - - - Not suspicious - - - -
-
- -
-

Detailed Information

- -
-
-
-

OLE stream: - {{stream['OLE stream']}}

-
-
-
Information
-
-
VBA filename:
-
{{stream['VBA filename']}}
-
-
-
Filename:
-
{{stream['Filename']}}
-
- -
-
Olevba analysis
-
- - - - - - - - - - - - - - - -
TypeKeywordDescription
{{result.type}}{{result.keyword}}{{result.description}}
- -
-
- -
- - Show code - Hide code - -
-
- -
-                                      {{stream['VBA code']}}
-                                    
- - - -
-
- -
-
-
-
- -
-
-
- Analysis failure -
-
- {{content.MSOffice.olevba.Error}} -
-
-
-
- -
- - - - -
-
- PDFiD Report -
-
-

Summary

-
-
PDFiD version:
-
v{{content.PDF.pdfid[0].pdfid.version}}
-
- -
-
Suspicious:
-
{{content.PDF.pdfid[0].suspicious}}
-
- -
-
PDFiD detection :
-
- - - - /RichMedia -   - - - - /OpenAction -   - - - - /JavaScript -   - - - - /Launch -   - - - - /ObjStm -   - - -
-
-
- -
-
- - - -
-
- {{(artifact.data || artifact.attachment.name) | fang}} -
-
- {{content.errorMessage}} -
-
diff --git a/contrib/report-templates/File_Info_1_0/short.html b/contrib/report-templates/File_Info_1_0/short.html deleted file mode 100644 index f8d67bb184..0000000000 --- a/contrib/report-templates/File_Info_1_0/short.html +++ /dev/null @@ -1,7 +0,0 @@ - - {{content.filetype}} - - - - {{content.filetype}}: Suspicious - diff --git a/contrib/report-templates/Fortiguard_URLCategory_1_0/long.html b/contrib/report-templates/Fortiguard_URLCategory_1_0/long.html deleted file mode 100644 index 251350036f..0000000000 --- a/contrib/report-templates/Fortiguard_URLCategory_1_0/long.html +++ /dev/null @@ -1,26 +0,0 @@ -
-
- URL Categories of - {{artifact.data}} -
-
-
-
Fortinet URL Category:
-
{{content.category}}  - - View Full Report - - Request Recategorization -
-
-
-
- -
-
- {{(artifact.data || artifact.attachment.name) | fang}} -
-
- {{content.errorMessage}} -
-
diff --git a/contrib/report-templates/Fortiguard_URLCategory_1_0/short.html b/contrib/report-templates/Fortiguard_URLCategory_1_0/short.html deleted file mode 100644 index e39dbcf7f7..0000000000 --- a/contrib/report-templates/Fortiguard_URLCategory_1_0/short.html +++ /dev/null @@ -1,4 +0,0 @@ - - URLCat: - {{content.category}}  - diff --git a/contrib/report-templates/HippoMore_1_0/long.html b/contrib/report-templates/HippoMore_1_0/long.html deleted file mode 100644 index d0f1417e4d..0000000000 --- a/contrib/report-templates/HippoMore_1_0/long.html +++ /dev/null @@ -1,37 +0,0 @@ -
-
- Detailed Information -
-
-
- No records found -
- - - - - - - - - - - - - - - -
SourceFirst seen by sourceLast seen by sourceCategoryDetails
{{source.source}}{{source.first_seen || '-'}}{{source.last_seen || '-'}}{{source.category || '-'}} -
  • First added in DB: {{source.firstAppearance }}
  • -
  • Last added in DB: {{source.lastAppearance }}
  • -
    -
    - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/HippoMore_1_0/short.html b/contrib/report-templates/HippoMore_1_0/short.html deleted file mode 100644 index 5812622426..0000000000 --- a/contrib/report-templates/HippoMore_1_0/short.html +++ /dev/null @@ -1 +0,0 @@ -HippoMore: {{content[artifact.data]}} record(s) diff --git a/contrib/report-templates/Hipposcore_1_0/long.html b/contrib/report-templates/Hipposcore_1_0/long.html deleted file mode 100644 index 3fd59af53e..0000000000 --- a/contrib/report-templates/Hipposcore_1_0/long.html +++ /dev/null @@ -1,30 +0,0 @@ -
    -
    - Detailed Information -
    -
    -
    -
    Hippocamp Score:
    -
    - - {{score}} - - - {{score}} - - - {{score}} - -
    -
    -
    -
    - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/Hipposcore_1_0/short.html b/contrib/report-templates/Hipposcore_1_0/short.html deleted file mode 100644 index 34ef106075..0000000000 --- a/contrib/report-templates/Hipposcore_1_0/short.html +++ /dev/null @@ -1,4 +0,0 @@ - - HippoScore: {{score}} - diff --git a/contrib/report-templates/MaxMind_GeoIP_2_0/long.html b/contrib/report-templates/MaxMind_GeoIP_2_0/long.html deleted file mode 100644 index 5f98f12d29..0000000000 --- a/contrib/report-templates/MaxMind_GeoIP_2_0/long.html +++ /dev/null @@ -1,20 +0,0 @@ -
    -
    - Geolocation of {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    -
    Is anonymous proxy
    -
    Is satellite provider
    - - {{[content.continent.name, content.country.name, content.subdivisions.name, content.city.name].join(' / ')}} -
    -
    - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/MaxMind_GeoIP_2_0/short.html b/contrib/report-templates/MaxMind_GeoIP_2_0/short.html deleted file mode 100644 index 0c79843709..0000000000 --- a/contrib/report-templates/MaxMind_GeoIP_2_0/short.html +++ /dev/null @@ -1 +0,0 @@ -IP location: {{content.country}} / {{content.continent}} diff --git a/contrib/report-templates/Msg_Parser_1_0/long.html b/contrib/report-templates/Msg_Parser_1_0/long.html deleted file mode 100644 index 1291294fcd..0000000000 --- a/contrib/report-templates/Msg_Parser_1_0/long.html +++ /dev/null @@ -1,76 +0,0 @@ -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    - - -
    -
    - Email message details -
    -
    -
    Is anonymous proxy
    -
    Is satellite provider
    - -
    -
    From
    -
    {{content.displayFrom}} ({{content.sender}})
    -
    -
    -
    To
    -
    {{content.displayTo}} ({{content.receivers}})
    -
    -
    -
    Subject
    -
    {{content.subject || '-'}}
    -
    -
    -
    Topic
    -
    {{content.topic || '-'}}
    -
    -
    -
    Bcc
    -
    {{content.bcc || '-'}}
    -
    -
    -
    Attachments
    -
    -
    This message file includes - -
    - - - - - - - - - - - - - - - -
    FilenameMime TypeExtension
    {{a.filename}}{{a.mime}}{{a.extension}}
    -
    -
    -
    -
    Headers
    -
    -
    {{content.headers}}
    -
    -
    -
    -
    Body
    -
    -
    {{content.body}}
    -
    -
    -
    -
    diff --git a/contrib/report-templates/Msg_Parser_1_0/short.html b/contrib/report-templates/Msg_Parser_1_0/short.html deleted file mode 100644 index 60802dd732..0000000000 --- a/contrib/report-templates/Msg_Parser_1_0/short.html +++ /dev/null @@ -1,3 +0,0 @@ - - MsgParser: - diff --git a/contrib/report-templates/OTXQuery_1_0/long.html b/contrib/report-templates/OTXQuery_1_0/long.html deleted file mode 100644 index f1c39b6739..0000000000 --- a/contrib/report-templates/OTXQuery_1_0/long.html +++ /dev/null @@ -1,165 +0,0 @@ -
    -
    - {{artifact.data | fang}} -
    -
    - {{content.errorMessage}} -
    -
    - - -
    -
    - OTX Report -
    -
    - -
    -
    ERROR:
    -
    {{content.errortext}} 
    -
    - -
    -
    Related Pulses Found:
    -
    {{content.pulse_count}}
    -
    - -
    -
    Related Pulses:
    -
    -
    - Name: {{::pulse.name}}
    - Author: {{::pulse.author.username}}
    - Modified: {{::pulse.modified_text}} @ {{::pulse.modified}}
    - Subscribers: {{::pulse.subscriber_count}}
    - Subscribed: {{::pulse.is_subscribing}}
    - Industries: {{::pulse.industries}}
    - Indicators: {{::pulse.indicator_count}} -
    -
    -
    -
    - -
    -
    Submit Pulse:
    -
    - Create a Pulse  -
    -
    - -
    -
    Malware Samples:
    -
    -
    - {{::sample.sample}} -
    -
    -
    - -
    -
    Malware:
    -
    {{content.malware}} 
    -
    - -
    -
    SHA1:
    -
    {{content.sha1}} 
    -
    - -
    -
    SHA256:
    -
    {{content.sha256}} 
    -
    - -
    -
    MD5:
    -
    {{content.md5}} 
    -
    - -
    -
    Page Type:
    -
    {{content.page_type}} 
    -
    - -
    -
    File Class:
    -
    {{content.file_class}} 
    -
    - -
    -
    File Type:
    -
    {{content.file_type}} 
    -
    - -
    -
    File Size:
    -
    {{content.filesize}} 
    -
    - -
    -
    SSDEEP:
    -
    {{content.ssdeep}} 
    -
    - -
    -
    Related URLs:
    -
    -
    - URL: {{::url.url}}
    - Date: {{::url.date}}
    - HTTP Code: {{::url.httpcode}}
    - IP: {{::url.result.urlworker.ip}} -
    -
    -
    -
    - -
    -
    Passive DNS:
    -
    -
    - Hostname: {{::dns.hostname}}
    - IP: {{::dns.address}}
    - First/Last seen: {{::dns.first}} / {{::dns.last}}
    - Locale: {{::dns.flag_title}} -
    -
    -
    -
    - -
    -
    Whois:
    -
    - Whois Query  -
    -
    - -
    -
    Alexa:
    -
    - Alexa Report  -
    -
    - -
    -
    City:
    -
    {{content.city}} 
    -
    - -
    -
    Country Code:
    -
    {{content.country_code}} 
    -
    - -
    -
    Country:
    -
    {{content.country_name}} 
    -
    - -
    -
    ASN:
    -
    {{content.asn}} 
    -
    - -
    -
    diff --git a/contrib/report-templates/OTXQuery_1_0/short.html b/contrib/report-templates/OTXQuery_1_0/short.html deleted file mode 100644 index 43a71a067f..0000000000 --- a/contrib/report-templates/OTXQuery_1_0/short.html +++ /dev/null @@ -1,4 +0,0 @@ - - OTX: - Pulses({{content.pulse_count}})  - diff --git a/contrib/report-templates/PassiveTotal_Enrichment_1_0/long.html b/contrib/report-templates/PassiveTotal_Enrichment_1_0/long.html deleted file mode 100644 index 694882d8de..0000000000 --- a/contrib/report-templates/PassiveTotal_Enrichment_1_0/long.html +++ /dev/null @@ -1,48 +0,0 @@ -
    - - - -
    -
    - PassiveTotal Enrichment Info -
    -
    -
    - No records found -
    -
    -
    -
    {{key}}
    -
    -
    -
    - {{tag}} -
    -
    - {{subd}} -
    -
    - {{value || None}} -
    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PassiveTotal_Malware_1_0/long.html b/contrib/report-templates/PassiveTotal_Malware_1_0/long.html deleted file mode 100644 index f95a957ac1..0000000000 --- a/contrib/report-templates/PassiveTotal_Malware_1_0/long.html +++ /dev/null @@ -1,45 +0,0 @@ -
    - - -
    -
    - PassiveTotal OSINT -
    -
    -
    - No records found -
    -
    -
    -
    -
    -
    {{key}}:
    -
    -
    -
    - {{value || None}} -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PassiveTotal_Malware_1_0/short.html b/contrib/report-templates/PassiveTotal_Malware_1_0/short.html deleted file mode 100644 index 733f0f9b6e..0000000000 --- a/contrib/report-templates/PassiveTotal_Malware_1_0/short.html +++ /dev/null @@ -1,7 +0,0 @@ - - PT:MALWARE=False - - - - PT:MALWARE=True - diff --git a/contrib/report-templates/PassiveTotal_Osint_1_0/long.html b/contrib/report-templates/PassiveTotal_Osint_1_0/long.html deleted file mode 100644 index 8715acf54c..0000000000 --- a/contrib/report-templates/PassiveTotal_Osint_1_0/long.html +++ /dev/null @@ -1,51 +0,0 @@ -
    - - -
    -
    - PassiveTotal OSINT -
    -
    -
    - No records found -
    -
    -
    -
    -
    -
    {{key}}:
    -
    -
    -
    - {{v}} -
    -
    - {{v}} -
    -
    - {{value || None}} -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PassiveTotal_Osint_1_0/short.html b/contrib/report-templates/PassiveTotal_Osint_1_0/short.html deleted file mode 100644 index 0c7db25dbd..0000000000 --- a/contrib/report-templates/PassiveTotal_Osint_1_0/short.html +++ /dev/null @@ -1,7 +0,0 @@ - - PT:OSINT=False - - - - PT:OSINT=True - diff --git a/contrib/report-templates/PassiveTotal_Passive_Dns_1_0/long.html b/contrib/report-templates/PassiveTotal_Passive_Dns_1_0/long.html deleted file mode 100644 index d9b74792cd..0000000000 --- a/contrib/report-templates/PassiveTotal_Passive_Dns_1_0/long.html +++ /dev/null @@ -1,80 +0,0 @@ - -
    - - - -
    -
    - PassiveTotal PassiveDNS Report -
    -
    -
    - No records found -
    -
    -
    - Summary Information -
    -
    -
    -
    Value:
    -
    {{content.queryValue}}
    -
    -
    -
    Total Records:
    -
    {{content.totalRecords}}
    -
    -
    -
    First seen:
    -
    {{content.firstSeen}}
    -
    -
    -
    Last seen:
    -
    {{content.lastSeen}}
    -
    -
    -
    -
    -
    - Records -
    -
    - - - - - - - - - - - - - -
    SourceResolveFirst seenLast seen
    -
    - {{s}} -
    -
    {{c.resolve || 'None'}}{{c.firstSeen || 'None'}}{{c.lastSeen || 'None'}}
    -
    -
    - -
    -
    - -
    - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PassiveTotal_Passive_Dns_1_0/short.html b/contrib/report-templates/PassiveTotal_Passive_Dns_1_0/short.html deleted file mode 100644 index 9de1bbc44c..0000000000 --- a/contrib/report-templates/PassiveTotal_Passive_Dns_1_0/short.html +++ /dev/null @@ -1,7 +0,0 @@ - - PT:PassiveDNS= {{content.total}} record - - - - PT:PassiveDNS= {{content.total}} record(s) - diff --git a/contrib/report-templates/PassiveTotal_Ssl_Certificate_Details_1_0/long.html b/contrib/report-templates/PassiveTotal_Ssl_Certificate_Details_1_0/long.html deleted file mode 100644 index 33a4d38445..0000000000 --- a/contrib/report-templates/PassiveTotal_Ssl_Certificate_Details_1_0/long.html +++ /dev/null @@ -1,34 +0,0 @@ -
    - - -
    -
    - PassiveTotal SSL Certificate Information -
    -
    -
    - No records found -
    -
    -
    -
    {{k}}:
    -
    {{v}}
    -
    -
    -
    -
    -
    - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PassiveTotal_Ssl_Certificate_Details_1_0/short.html b/contrib/report-templates/PassiveTotal_Ssl_Certificate_Details_1_0/short.html deleted file mode 100644 index 6f79d3155b..0000000000 --- a/contrib/report-templates/PassiveTotal_Ssl_Certificate_Details_1_0/short.html +++ /dev/null @@ -1,7 +0,0 @@ - - PT:SSL=False - - - - PT:SSL=True - diff --git a/contrib/report-templates/PassiveTotal_Ssl_Certificate_History_1_0/long.html b/contrib/report-templates/PassiveTotal_Ssl_Certificate_History_1_0/long.html deleted file mode 100644 index 0ef8fe306b..0000000000 --- a/contrib/report-templates/PassiveTotal_Ssl_Certificate_History_1_0/long.html +++ /dev/null @@ -1,41 +0,0 @@ - -
    - - -
    -
    - Detailed Information -
    -
    -
    - No records found -
    - - - - - - - - - - - -
    SHA1First seenLast seen
    {{c.sha1}}{{c.firstSeen || 'None'}}{{c.lastSeen || 'None'}}
    -
    -
    -
    - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PassiveTotal_Ssl_Certificate_History_1_0/short.html b/contrib/report-templates/PassiveTotal_Ssl_Certificate_History_1_0/short.html deleted file mode 100644 index 128ecbc3b0..0000000000 --- a/contrib/report-templates/PassiveTotal_Ssl_Certificate_History_1_0/short.html +++ /dev/null @@ -1,3 +0,0 @@ - - PT:SSLCertHistory= {{content.total}} record(s) - diff --git a/contrib/report-templates/PassiveTotal_Unique_Resolutions_1_0/long.html b/contrib/report-templates/PassiveTotal_Unique_Resolutions_1_0/long.html deleted file mode 100644 index d7b0b64f5e..0000000000 --- a/contrib/report-templates/PassiveTotal_Unique_Resolutions_1_0/long.html +++ /dev/null @@ -1,60 +0,0 @@ -
    - -
    -
    - PassiveTotal unique resolution -
    -
    -
    - No records found -
    -
    -
    -
    Query Type:
    -
    {{content.queryType}}
    -
    -
    -
    Query:
    -
    {{content.queryValue}}
    -
    -
    -
    - - -    - {{content.queryValue}} :
    -
    -
    - {{content.total}} result(s) - -
    -
    - {{content.results[0]}} -
    -
    -
    - - {{r}} -
    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PassiveTotal_Unique_Resolutions_1_0/short.html b/contrib/report-templates/PassiveTotal_Unique_Resolutions_1_0/short.html deleted file mode 100644 index 54e803b3ae..0000000000 --- a/contrib/report-templates/PassiveTotal_Unique_Resolutions_1_0/short.html +++ /dev/null @@ -1,3 +0,0 @@ - - PT:UniqueResolution= {{content.total}} record(s) - diff --git a/contrib/report-templates/PassiveTotal_Whois_Details_1.0/long.html b/contrib/report-templates/PassiveTotal_Whois_Details_1.0/long.html deleted file mode 100644 index 4d3b96114d..0000000000 --- a/contrib/report-templates/PassiveTotal_Whois_Details_1.0/long.html +++ /dev/null @@ -1,118 +0,0 @@ -
    - - -
    -
    - PassiveTotal Whois Summary -
    -
    -
    - No records found -
    -
    -
    -
    Domain:
    -
    {{content.domain|| "-"}}
    -
    -
    -
    Contact email:
    -
    {{content.contactEmail || "-"}}
    -
    -
    -
    Nameservers:
    -
    {{ns}}
    -
    -
    -
    Whois server:
    -
    {{content.whoisServer || "-"}}
    -
    -
    -
    Registered date:
    -
    {{content.registered}}
    -
    -
    -
    Registry updated at:
    -
    {{content.registryUpdatedAt}}
    -
    -
    -
    Last load at:
    -
    {{content.lastLoadedAt}}
    -
    -
    -
    Expires at:
    -
    {{content.expiresAt}}
    -
    -
    -
    Registrar:
    -
    {{content.registrar}}
    -
    -
    -
    -
    - -
    -
    - PassiveTotal Whois Admin Info -
    -
    -
    - No records found -
    -
    -
    -
    {{key}}
    -
    {{value}}
    -
    -
    -
    -
    - -
    -
    - PassiveTotal Whois Tech Info -
    -
    -
    - No records found -
    -
    -
    -
    {{key}}
    -
    {{value}}
    -
    -
    -
    -
    - -
    -
    - PassiveTotal Whois Registrant Info -
    -
    -
    - No records found -
    -
    -
    -
    {{key}}
    -
    {{value}}
    -
    -
    -
    -
    -
    - - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PassiveTotal_Whois_Details_1.0/short.html b/contrib/report-templates/PassiveTotal_Whois_Details_1.0/short.html deleted file mode 100644 index 7353be9a41..0000000000 --- a/contrib/report-templates/PassiveTotal_Whois_Details_1.0/short.html +++ /dev/null @@ -1,2 +0,0 @@ -PT:Whois:REGISTRANT= {{content.registrant}} -PT:Whois:REGISTRAR= {{content.registrar}} diff --git a/contrib/report-templates/PhishTank_CheckURL_1_0/long.html b/contrib/report-templates/PhishTank_CheckURL_1_0/long.html deleted file mode 100644 index c10bf30991..0000000000 --- a/contrib/report-templates/PhishTank_CheckURL_1_0/long.html +++ /dev/null @@ -1,44 +0,0 @@ -
    -
    - PhishTank Report for {{artifact.data}} -
    -
    -
    -
    ERROR:
    -
    {{content.errortext}} 
    -
    -
    -
    In database:
    -
    {{content.in_database}} 
    -
    -
    -
    Verified:
    -
    {{content.verified}} 
    -
    -
    -
    Verified at:
    -
    {{content.verified_at}} 
    -
    -
    -
    Phish Detail Page:
    -
    - {{content.phish_detail_page}} -
    -
    -
    -
    Submit to PhishTank:
    -
    - Click here to submit this site to PhishTank -
    -
    -
    -
    - -
    -
    - {{artifact.data | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PhishTank_CheckURL_1_0/short.html b/contrib/report-templates/PhishTank_CheckURL_1_0/short.html deleted file mode 100644 index b90f191fcb..0000000000 --- a/contrib/report-templates/PhishTank_CheckURL_1_0/short.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - PhishTank: - - {{millis | amDurationFormat : 'milliseconds'}} - - - - PhishTank: {{content.in_database}}  - - - - diff --git a/contrib/report-templates/PhishingInitiative_Lookup_1_0/long.html b/contrib/report-templates/PhishingInitiative_Lookup_1_0/long.html deleted file mode 100644 index 1f573461e5..0000000000 --- a/contrib/report-templates/PhishingInitiative_Lookup_1_0/long.html +++ /dev/null @@ -1,21 +0,0 @@ -
    -
    - PhishingInitiative Report for {{artifact.data | fang}} -
    -
    -
    -
    Status:
    -
    - {{content.tag_label}} -
    -
    -
    -
    -
    -
    - {{artifact.data | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PhishingInitiative_Lookup_1_0/short.html b/contrib/report-templates/PhishingInitiative_Lookup_1_0/short.html deleted file mode 100644 index d200285563..0000000000 --- a/contrib/report-templates/PhishingInitiative_Lookup_1_0/short.html +++ /dev/null @@ -1,3 +0,0 @@ - - PhishingInitiative: {{content.status}} - diff --git a/contrib/report-templates/VirusTotal_GetReport_2_0/long.html b/contrib/report-templates/VirusTotal_GetReport_2_0/long.html deleted file mode 100644 index fdcb8194fe..0000000000 --- a/contrib/report-templates/VirusTotal_GetReport_2_0/long.html +++ /dev/null @@ -1,203 +0,0 @@ -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    - -
    -
    -
    -
    - Summary -
    -
    -
    -
    Score
    -
    {{content.positives || 0}}/{{content.total}}
    -
    -
    -
    Last analysis date
    -
    {{content.scan_date}}
    -
    -
    -
    Autonomous System
    -
    {{content.as_owner}}
    -
    -
    -
    Categories
    -
    {{content.categories.join(', ')}}
    -
    -
    -
    Sub domains
    -
    {{content.subdomains.join(', ')}}
    -
    -
    -
    Resolutions
    -
    -
    - This domain has been seen to resolve to the following IP addresses. -
    -
    - The following domains resolved to the given IP address. -
    -
    - {{::resolution.last_resolved | amDateFormat:'DD-MM-YYYY'}}: - {{(resolution.ip_address | fang) || (resolution.hostname | fang)}} -
    -
    -
    -
    -
    Virus Total
    -
    - - - - View Full Report - - - - - View Full Report - - - - - View Full Report - -
    -
    -
    -
    - -
    -
    - Latest detected URLs - - View All ({{::content.detected_urls.length}}) - -
    -
    -

    Latest URLs hosted in this IP address - detected by at least one URL scanner or malicious URL dataset. -

    - - - - - - - - - - - -
    ScoreScan DateURL
    - {{::url.positives}}/{{::url.total}} - {{url.scan_date}}{{url.url | fang}}
    -
    -
    - -
    -
    - Latest detected files that were downloaded from this IP address - - View All ({{::content.detected_downloaded_samples.length}}) - -
    -
    -

    Latest files that are - detected by at least one antivirus solution and were downloaded by VirusTotal from the IP address provided. -

    - - - - - - - - - - - -
    ScoreDateSHA256
    - {{hash.positives}}/{{hash.total}} - {{hash.date}}{{hash.sha256}}
    -
    -
    - -
    -
    - Latest detected files that embed this IP address in their strings - - View All ({{::content.detected_referrer_samples.length}}) - -
    -
    -

    Latest files that are - detected by at least one antivirus solution and embed URL pattern strings with the IP address provided. -

    - - - - - - - - - -
    ScoreSHA256
    - {{hash.positives}}/{{hash.total}} - {{hash.sha256}}
    -
    -
    - -
    -
    - Scans -
    -
    - - - - - - - - - - - - - - - - - - -
    ScannerDetectedResultDetailsUpdateVersion
    - {{scanner}} - - - {{result.result}} - - - View details - {{result.update}}{{result.version}}
    -
    -
    - -
    -
    -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.verbose_msg}} -
    -
    -
    -
    diff --git a/contrib/report-templates/VirusTotal_GetReport_2_0/short.html b/contrib/report-templates/VirusTotal_GetReport_2_0/short.html deleted file mode 100644 index a713b763d4..0000000000 --- a/contrib/report-templates/VirusTotal_GetReport_2_0/short.html +++ /dev/null @@ -1,8 +0,0 @@ - - VT: - {{content.positives}}/{{content.total}} - Scans({{content.scans}}) - Resolutions({{content.resolutions}}) - Url detections({{content.detected_urls}}) - files({{content.detected_downloaded_samples}}) - diff --git a/contrib/report-templates/VirusTotal_Scan_2_0/long.html b/contrib/report-templates/VirusTotal_Scan_2_0/long.html deleted file mode 100644 index 8bdaee6579..0000000000 --- a/contrib/report-templates/VirusTotal_Scan_2_0/long.html +++ /dev/null @@ -1,78 +0,0 @@ -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    - - -
    -
    -
    -
    Summary
    -
    -
    -
    Score
    -
    {{content.positives}}/{{content.total}}
    -
    -
    -
    Last analysis date
    -
    {{content.scan_date}}
    -
    -
    -
    Virus Total
    -
    - - - - View Full Report - -
    -
    -
    -
    - -
    -
    Scans
    -
    - - - - - - - - - - - - - - - - - - -
    ScannerDetectedResultDetailsUpdateVersion
    {{scanner}} - - {{result.result}} - View details - {{result.update}}{{result.version}}
    -
    -
    - - -
    -
    -
    -
    - {{(artifact.data || artifact.attachment.name)| fang}} -
    -
    - {{content.verbose_msg}} -
    -
    -
    -
    diff --git a/contrib/report-templates/VirusTotal_Scan_2_0/short.html b/contrib/report-templates/VirusTotal_Scan_2_0/short.html deleted file mode 100644 index 7da455c08f..0000000000 --- a/contrib/report-templates/VirusTotal_Scan_2_0/short.html +++ /dev/null @@ -1,3 +0,0 @@ - - VT: {{content.positives}}/{{content.total}} - diff --git a/debian.sbt b/debian.sbt index 2f08116b02..7a40c44ea7 100644 --- a/debian.sbt +++ b/debian.sbt @@ -1,12 +1,13 @@ -import Common.{stableVersion, snapshotVersion, betaVersion} +import Common.{betaVersion, snapshotVersion, stableVersion, versionUsage} -linuxPackageMappings in Debian += packageMapping(file("LICENSE") -> "/usr/share/doc/thehive/copyright").withPerms("644") +linuxPackageMappings in Debian += packageMapping(file("LICENSE") → "/usr/share/doc/thehive/copyright").withPerms("644") version in Debian := { version.value match { - case stableVersion(_, _) => version.value - case betaVersion(v1, v2) => v1 + "-0.1RC" + v2 - case snapshotVersion(_, _) => version.value + "-SNAPSHOT" - case _ => sys.error("Invalid version: " + version.value) + case stableVersion(_, _) ⇒ version.value + case betaVersion(v1, v2, v3) ⇒ v1 + "-0." + v3 + "RC" + v2 + case snapshotVersion(stableVersion(v1, v2)) ⇒ v1 + "-" + v2 + "-SNAPSHOT" + case snapshotVersion(betaVersion(v1, v2, v3)) ⇒ v1 + "-0." + v3 + "RC" + v2 + "-SNAPSHOT" + case _ ⇒ versionUsage(version.value) } } debianPackageRecommends := Seq("elasticsearch") @@ -16,4 +17,4 @@ maintainerScripts in Debian := maintainerScriptsFromDirectory( Seq(DebianConstants.Postinst, DebianConstants.Prerm, DebianConstants.Postrm) ) linuxEtcDefaultTemplate in Debian := (baseDirectory.value / "package" / "etc_default_thehive").asURL -linuxMakeStartScript in Debian := None \ No newline at end of file +linuxMakeStartScript in Debian := None diff --git a/docker.sbt b/docker.sbt index e96488b4cf..4619e82c53 100644 --- a/docker.sbt +++ b/docker.sbt @@ -1,12 +1,13 @@ -import Common.{betaVersion, snapshotVersion, stableVersion} +import Common.{betaVersion, snapshotVersion, stableVersion, versionUsage} import com.typesafe.sbt.packager.docker.{Cmd, ExecCmd} version in Docker := { version.value match { - case stableVersion(_, _) ⇒ version.value - case betaVersion(v1, v2) ⇒ v1 + "-0.1RC" + v2 - case snapshotVersion(_, _) ⇒ version.value + "-SNAPSHOT" - case _ ⇒ sys.error("Invalid version: " + version.value) + case stableVersion(_, _) ⇒ version.value + case betaVersion(v1, v2, v3) ⇒ v1 + "-0." + v3 + "RC" + v2 + case snapshotVersion(stableVersion(v1, v2)) ⇒ v1 + "-" + v2 + "-SNAPSHOT" + case snapshotVersion(betaVersion(v1, v2, v3)) ⇒ v1 + "-0." + v3 + "RC" + v2 + "-SNAPSHOT" + case _ ⇒ versionUsage(version.value) } } defaultLinuxInstallLocation in Docker := "/opt/thehive" diff --git a/project/Common.scala b/project/Common.scala index a382ef5ee9..afa8373da3 100644 --- a/project/Common.scala +++ b/project/Common.scala @@ -15,15 +15,15 @@ object Common { scalaVersion := Dependencies.scalaVersion, scalacOptions ++= Seq( "-deprecation", // Emit warning and location for usages of deprecated APIs. - "-feature", // Emit warning and location for usages of features that should be imported explicitly. - "-unchecked", // Enable additional warnings where generated code depends on assumptions. + "-feature", // Emit warning and location for usages of features that should be imported explicitly. + "-unchecked", // Enable additional warnings where generated code depends on assumptions. //"-Xfatal-warnings", // Fail the compilation if there are any warnings. - "-Xlint", // Enable recommended additional warnings. - "-Ywarn-adapted-args", // Warn if an argument list is modified to match the receiver. - "-Ywarn-dead-code", // Warn when dead code is identified. - "-Ywarn-inaccessible", // Warn about inaccessible types in method signatures. + "-Xlint", // Enable recommended additional warnings. + "-Ywarn-adapted-args", // Warn if an argument list is modified to match the receiver. + "-Ywarn-dead-code", // Warn when dead code is identified. + "-Ywarn-inaccessible", // Warn about inaccessible types in method signatures. "-Ywarn-nullary-override", // Warn when non-nullary overrides nullary, e.g. def foo() over def foo. - "-Ywarn-numeric-widen" // Warn when numerics are widened. + "-Ywarn-numeric-widen" // Warn when numerics are widened. ), scalacOptions in Test ~= { options ⇒ options filterNot (_ == "-Ywarn-dead-code") // Allow dead code in tests (to support using mockito). @@ -31,21 +31,28 @@ object Common { parallelExecution in Test := false, fork in Test := true, javaOptions += "-Xmx1G", - // Redirect logs from ElasticSearch (which uses log4j2) to slf4j libraryDependencies += "org.apache.logging.log4j" % "log4j-to-slf4j" % "2.9.1", excludeDependencies += "org.apache.logging.log4j" % "log4j-core" ) val stableVersion: Regex = "(\\d+\\.\\d+\\.\\d+)-(\\d+)".r - val betaVersion: Regex = "(\\d+\\.\\d+\\.\\d+)-[Rr][Cc](\\d+)".r + val betaVersion: Regex = "(\\d+\\.\\d+\\.\\d+)-[Rr][Cc](\\d+)-(\\d+)".r + object snapshotVersion { - def unapplySeq(version: String): Option[List[String]] = { - if (version.endsWith("-SNAPSHOT")) { - val v = version.dropRight(9) - stableVersion.unapplySeq(v) orElse betaVersion.unapplySeq(v) - } + + def unapply(version: String): Option[String] = + if (version.endsWith("-SNAPSHOT")) Some(version.dropRight(9)) else None - } } -} \ No newline at end of file + + def versionUsage(version: String): Nothing = + sys.error( + s"Invalid version: $version\n" + + "The accepted formats for version are:\n" + + " - 1.2.3-4\n" + + " - 1.2.3-RC4-5\n" + + " - 1.2.3-4-SNAPSHOT\n" + + " - 1.2.3-RC4-5-SNAPSHOT" + ) +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 7be462cf0c..6e341910f8 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -1,27 +1,27 @@ import sbt._ object Dependencies { - val scalaVersion = "2.12.6" + val scalaVersion = "2.12.11" object Library { object Play { - val version = play.core.PlayVersion.current - val ws = "com.typesafe.play" %% "play-ws" % version - val ahc = "com.typesafe.play" %% "play-ahc-ws" % version - val cache = "com.typesafe.play" %% "play-ehcache" % version - val test = "com.typesafe.play" %% "play-test" % version - val specs2 = "com.typesafe.play" %% "play-specs2" % version - val filters = "com.typesafe.play" %% "filters-helpers" % version - val guice = "com.typesafe.play" %% "play-guice" % version + val ws = "com.typesafe.play" %% "play-ws" % play.core.PlayVersion.current + val ahc = "com.typesafe.play" %% "play-ahc-ws" % play.core.PlayVersion.current + val cache = "com.typesafe.play" %% "play-ehcache" % play.core.PlayVersion.current + val test = "com.typesafe.play" %% "play-test" % play.core.PlayVersion.current + val specs2 = "com.typesafe.play" %% "play-specs2" % play.core.PlayVersion.current + val filters = "com.typesafe.play" %% "filters-helpers" % play.core.PlayVersion.current + val guice = "com.typesafe.play" %% "play-guice" % play.core.PlayVersion.current } - val scalaGuice = "net.codingwell" %% "scala-guice" % "4.2.3" + val scalaGuice = "net.codingwell" %% "scala-guice" % "4.2.6" - val reflections = "org.reflections" % "reflections" % "0.9.11" - val zip4j = "net.lingala.zip4j" % "zip4j" % "1.3.2" - val elastic4play = "org.thehive-project" %% "elastic4play" % "1.11.5" - val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % "2.5.21" - val akkaClusterTools = "com.typesafe.akka" %% "akka-cluster-tools" % "2.5.21" + val reflections = "org.reflections" % "reflections" % "0.9.11" + val zip4j = "net.lingala.zip4j" % "zip4j" % "2.6.0" + val elastic4play = "org.thehive-project" %% "elastic4play" % "1.12.1" + val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % play.core.PlayVersion.akkaVersion + val akkaClusterTyped = "com.typesafe.akka" %% "akka-cluster-typed" % play.core.PlayVersion.akkaVersion + val akkaClusterTools = "com.typesafe.akka" %% "akka-cluster-tools" % play.core.PlayVersion.akkaVersion } } diff --git a/project/plugins.sbt b/project/plugins.sbt index 8069c5c167..efb8586d60 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,7 @@ // Comment to get more information during initialization logLevel := Level.Info -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.23") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.2") addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.1") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.0.0") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.0") addSbtPlugin("org.thehive-project" % "sbt-github-changelog" % "0.3.0") diff --git a/rpm.sbt b/rpm.sbt index d65451081f..fa852a9bd6 100644 --- a/rpm.sbt +++ b/rpm.sbt @@ -1,21 +1,24 @@ -import Common.{stableVersion, snapshotVersion, betaVersion} +import Common.{betaVersion, snapshotVersion, stableVersion, versionUsage} version in Rpm := { version.value match { - case stableVersion(v1, v2) => v1 - case betaVersion(v1, v2) => v1 - case snapshotVersion(v1, v2) => v1 - case _ => sys.error("Invalid version: " + version.value) + case stableVersion(v1, _) ⇒ v1 + case betaVersion(v1, _, _) ⇒ v1 + case snapshotVersion(stableVersion(v1, _)) ⇒ v1 + case snapshotVersion(betaVersion(v1, _, _)) ⇒ v1 + case _ ⇒ versionUsage(version.value) } } rpmRelease := { version.value match { - case stableVersion(_, v2) => v2 - case betaVersion(v1, v2) => "0.1RC" + v2 - case snapshotVersion(v1, v2) => v2 + "-SNAPSHOT" - case _ => sys.error("Invalid version: " + version.value) + case stableVersion(_, v2) ⇒ v2 + case betaVersion(_, v2, v3) ⇒ "0." + v3 + "RC" + v2 + case snapshotVersion(stableVersion(_, v2)) ⇒ v2 + "-SNAPSHOT" + case snapshotVersion(betaVersion(_, v2, v3)) ⇒ "0." + v3 + "RC" + v2 + "-SNAPSHOT" + case _ ⇒ versionUsage(version.value) } } + rpmVendor := organizationName.value rpmUrl := organizationHomepage.value.map(_.toString) rpmLicense := Some("AGPL") @@ -35,12 +38,14 @@ linuxPackageMappings in Rpm := configWithNoReplace((linuxPackageMappings in Rpm) packageBin in Rpm := { import scala.sys.process._ val rpmFile = (packageBin in Rpm).value - Process("rpm" :: - "--define" :: "_gpg_name TheHive Project" :: - "--define" :: "_signature gpg" :: - "--define" :: "__gpg_check_password_cmd /bin/true" :: - "--define" :: "__gpg_sign_cmd %{__gpg} gpg --batch --no-verbose --no-armor --use-agent --no-secmem-warning -u \"%{_gpg_name}\" -sbo %{__signature_filename} %{__plaintext_filename}" :: - "--addsign" :: rpmFile.toString :: - Nil).!! + Process( + "rpm" :: + "--define" :: "_gpg_name TheHive Project" :: + "--define" :: "_signature gpg" :: + "--define" :: "__gpg_check_password_cmd /bin/true" :: + "--define" :: "__gpg_sign_cmd %{__gpg} gpg --batch --no-verbose --no-armor --use-agent --no-secmem-warning -u \"%{_gpg_name}\" -sbo %{__signature_filename} %{__plaintext_filename}" :: + "--addsign" :: rpmFile.toString :: + Nil + ).!! rpmFile } diff --git a/thehive-backend/app/connectors/Connectors.scala b/thehive-backend/app/connectors/Connectors.scala index fe3d0982d6..3668976de1 100644 --- a/thehive-backend/app/connectors/Connectors.scala +++ b/thehive-backend/app/connectors/Connectors.scala @@ -1,13 +1,12 @@ package connectors import scala.collection.immutable - import play.api.libs.json.{JsObject, Json} import play.api.mvc._ import play.api.routing.sird.UrlContext import play.api.routing.{Router, SimpleRouter} - import com.google.inject.AbstractModule +import scala.reflect.runtime.{universe => ru} import javax.inject.{Inject, Singleton} import models.HealthStatus import net.codingwell.scalaguice.{ScalaModule, ScalaMultibinder} @@ -35,7 +34,7 @@ class ConnectorRouter @Inject()(connectors: immutable.Set[Connector], actionBuil abstract class ConnectorModule extends AbstractModule with ScalaModule { - def registerController[C <: Connector](implicit evidence: Manifest[C]): Unit = { + def registerController[C <: Connector: ru.TypeTag]: Unit = { val connectorBindings = ScalaMultibinder.newSetBinder[Connector](binder) connectorBindings.addBinding.to[C] () diff --git a/thehive-backend/app/controllers/ArtifactCtrl.scala b/thehive-backend/app/controllers/ArtifactCtrl.scala index 3bde2e0543..646c49c1e0 100644 --- a/thehive-backend/app/controllers/ArtifactCtrl.scala +++ b/thehive-backend/app/controllers/ArtifactCtrl.scala @@ -3,25 +3,24 @@ package controllers import java.io.FilterInputStream import java.nio.file.{Files, Path} -import scala.collection.JavaConverters._ -import scala.concurrent.{ExecutionContext, Future} - +import javax.inject.{Inject, Singleton} +import models.Roles +import net.lingala.zip4j.ZipFile import play.api.http.Status import play.api.libs.json.{JsArray, JsValue} import play.api.mvc._ import play.api.{Configuration, Logger} -import javax.inject.{Inject, Singleton} -import models.Roles -import net.lingala.zip4j.core.ZipFile +import scala.collection.JavaConverters._ +import scala.concurrent.{ExecutionContext, Future} +//import net.lingala.zip4j.core.ZipFile import net.lingala.zip4j.model.FileHeader -import services.ArtifactSrv - import org.elastic4play.controllers._ import org.elastic4play.models.JsonFormat.baseModelEntityWrites import org.elastic4play.services.JsonFormat.{aggReads, queryReads} import org.elastic4play.services._ import org.elastic4play.{BadRequestError, InternalError, InvalidFormatAttributeError, Timed} +import services.ArtifactSrv @Singleton class ArtifactCtrl @Inject()( @@ -70,7 +69,7 @@ class ArtifactCtrl @Inject()( } } - private def getFieldsFromZipFile(caseId: String, fields: Fields, filepath: Path)(implicit authContext: AuthContext): Seq[Fields] = { + private def getFieldsFromZipFile(fields: Fields, filepath: Path)(implicit authContext: AuthContext): Seq[Fields] = { val zipFile = new ZipFile(filepath.toFile) val files: Seq[FileHeader] = zipFile.getFileHeaders.asScala.asInstanceOf[Seq[FileHeader]] @@ -79,7 +78,7 @@ class ArtifactCtrl @Inject()( .getString("zipPassword") .filterNot(_.isEmpty) .getOrElse(configuration.get[String]("datastore.attachment.password")) - zipFile.setPassword(pw) + zipFile.setPassword(pw.toCharArray) } /*val multiFields = */ @@ -125,7 +124,7 @@ class ArtifactCtrl @Inject()( .get("attachment") .map { case FileInputValue(_, filepath, _) if fields.getBoolean("isZip").getOrElse(false) ⇒ - Future.successful(getFieldsFromZipFile(caseId, fields, filepath)) + Future.successful(getFieldsFromZipFile(fields, filepath)) case _: FileInputValue ⇒ Future.successful(Seq(fields)) case JsonInputValue(JsArray(attachments)) ⇒ Future.traverse(attachments)(attachment ⇒ getFieldsFromAttachment(fields, attachment)).map(_.flatten) @@ -155,7 +154,7 @@ class ArtifactCtrl @Inject()( } @Timed - def get(id: String): Action[Fields] = authenticated(Roles.read).async(fieldsBodyParser) { implicit request ⇒ + def get(id: String): Action[Fields] = authenticated(Roles.read).async(fieldsBodyParser) { _ ⇒ artifactSrv .get(id) .map(artifact ⇒ renderer.toOutput(OK, artifact)) diff --git a/thehive-backend/app/controllers/AttachmentCtrl.scala b/thehive-backend/app/controllers/AttachmentCtrl.scala index 46dce09a56..b66aebe746 100644 --- a/thehive-backend/app/controllers/AttachmentCtrl.scala +++ b/thehive-backend/app/controllers/AttachmentCtrl.scala @@ -1,23 +1,21 @@ package controllers import java.nio.file.Files -import javax.inject.{Inject, Singleton} - -import play.api.http.HttpEntity -import play.api.libs.Files.DefaultTemporaryFileCreator -import play.api.mvc._ -import play.api.{mvc, Configuration} import akka.stream.scaladsl.FileIO -import net.lingala.zip4j.core.ZipFile -import net.lingala.zip4j.model.ZipParameters -import net.lingala.zip4j.util.Zip4jConstants +import javax.inject.{Inject, Singleton} import models.Roles - +import net.lingala.zip4j.ZipFile +import net.lingala.zip4j.model.ZipParameters +import net.lingala.zip4j.model.enums.{CompressionLevel, EncryptionMethod} import org.elastic4play.Timed -import org.elastic4play.controllers.{Authenticated, Renderer} +import org.elastic4play.controllers.Authenticated import org.elastic4play.models.AttachmentAttributeFormat import org.elastic4play.services.AttachmentSrv +import play.api.http.HttpEntity +import play.api.libs.Files.DefaultTemporaryFileCreator +import play.api.mvc._ +import play.api.{Configuration, mvc} /** * Controller used to access stored attachments (plain or zipped) @@ -29,7 +27,6 @@ class AttachmentCtrl( attachmentSrv: AttachmentSrv, authenticated: Authenticated, components: ControllerComponents, - renderer: Renderer ) extends AbstractController(components) { @Inject() def this( @@ -38,9 +35,8 @@ class AttachmentCtrl( attachmentSrv: AttachmentSrv, authenticated: Authenticated, components: ControllerComponents, - renderer: Renderer ) = - this(configuration.get[String]("datastore.attachment.password"), tempFileCreator, attachmentSrv, authenticated, components, renderer) + this(configuration.get[String]("datastore.attachment.password"), tempFileCreator, attachmentSrv, authenticated, components) /** * Download an attachment, identified by its hash, in plain format @@ -48,7 +44,7 @@ class AttachmentCtrl( * open the document directly. It must be used only for safe file */ @Timed("controllers.AttachmentCtrl.download") - def download(hash: String, name: Option[String]): Action[AnyContent] = authenticated(Roles.read) { implicit request ⇒ + def download(hash: String, name: Option[String]): Action[AnyContent] = authenticated(Roles.read) { _ ⇒ if (hash.startsWith("{{")) // angularjs hack NoContent else if (!name.getOrElse("").intersect(AttachmentAttributeFormat.forbiddenChar).isEmpty) @@ -69,20 +65,21 @@ class AttachmentCtrl( * File name can be specified (zip extension is append) */ @Timed("controllers.AttachmentCtrl.downloadZip") - def downloadZip(hash: String, name: Option[String]): Action[AnyContent] = authenticated(Roles.read) { implicit request ⇒ + def downloadZip(hash: String, name: Option[String]): Action[AnyContent] = authenticated(Roles.read) { _ ⇒ if (!name.getOrElse("").intersect(AttachmentAttributeFormat.forbiddenChar).isEmpty) BadRequest("File name is invalid") else { val f = tempFileCreator.create("zip", hash).path Files.delete(f) val zipFile = new ZipFile(f.toFile) + zipFile.setPassword(password.toCharArray) val zipParams = new ZipParameters - zipParams.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_FASTEST) + zipParams.setCompressionLevel(CompressionLevel.FASTEST) zipParams.setEncryptFiles(true) - zipParams.setEncryptionMethod(Zip4jConstants.ENC_METHOD_STANDARD) - zipParams.setPassword(password) + zipParams.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD) +// zipParams.setsetPassword(password.toCharArray) zipParams.setFileNameInZip(name.getOrElse(hash)) - zipParams.setSourceExternalStream(true) +// zipParams.setSourceExternalStream(true) zipFile.addStream(attachmentSrv.stream(hash), zipParams) Result( diff --git a/thehive-backend/app/controllers/AuditCtrl.scala b/thehive-backend/app/controllers/AuditCtrl.scala index 128a96e277..49002b4803 100644 --- a/thehive-backend/app/controllers/AuditCtrl.scala +++ b/thehive-backend/app/controllers/AuditCtrl.scala @@ -30,7 +30,7 @@ class AuditCtrl @Inject()( * Return audit logs. For each item, include ancestor entities */ @Timed - def flow(rootId: Option[String], count: Option[Int]): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def flow(rootId: Option[String], count: Option[Int]): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ val (audits, total) = auditSrv(rootId.filterNot(_ == "any"), count.getOrElse(10)) renderer.toOutput(OK, audits, total) } diff --git a/thehive-backend/app/controllers/AuthenticationCtrl.scala b/thehive-backend/app/controllers/AuthenticationCtrl.scala index cae6774be5..42a127388b 100644 --- a/thehive-backend/app/controllers/AuthenticationCtrl.scala +++ b/thehive-backend/app/controllers/AuthenticationCtrl.scala @@ -1,12 +1,12 @@ package controllers import javax.inject.{Inject, Singleton} - import models.UserStatus -import org.elastic4play.controllers.{Authenticated, Fields, FieldsBodyParser, Renderer} +import org.elastic4play.controllers.{Authenticated, Fields, FieldsBodyParser} import org.elastic4play.database.DBIndex import org.elastic4play.services.AuthSrv -import org.elastic4play.{AuthorizationError, OAuth2Redirect, Timed} +import org.elastic4play.{AuthorizationError, Timed} +import play.api.Configuration import play.api.mvc._ import services.UserSrv @@ -14,11 +14,11 @@ import scala.concurrent.{ExecutionContext, Future} @Singleton class AuthenticationCtrl @Inject()( + configuration: Configuration, authSrv: AuthSrv, userSrv: UserSrv, authenticated: Authenticated, dbIndex: DBIndex, - renderer: Renderer, components: ControllerComponents, fieldsBodyParser: FieldsBodyParser, implicit val ec: ExecutionContext @@ -46,24 +46,23 @@ class AuthenticationCtrl @Inject()( dbIndex.getIndexStatus.flatMap { case false ⇒ Future.successful(Results.Status(520)) case _ ⇒ - (for { - authContext ← authSrv.authenticate() - user ← userSrv.get(authContext.userId) - } yield { - if (user.status() == UserStatus.Ok) - authenticated.setSessingUser(Ok, authContext) - else - throw AuthorizationError("Your account is locked") - }) recover { - // A bit of a hack with the status code, so that Angular doesn't reject the origin - case OAuth2Redirect(redirectUrl, qp) ⇒ Redirect(redirectUrl, qp, status = OK) - case e ⇒ throw e - } + authSrv + .authenticate() + .flatMap { + case Right(authContext) ⇒ + userSrv.get(authContext.userId).map { user ⇒ + if (user.status() == UserStatus.Ok) + authenticated.setSessingUser(Redirect(configuration.get[String]("play.http.context").stripSuffix("/") + "/index.html"), authContext) + else + throw AuthorizationError("Your account is locked") + } + case Left(result) ⇒ Future.successful(result) + } } } @Timed - def logout = Action { + def logout: Action[AnyContent] = Action { Ok.withNewSession } } diff --git a/thehive-backend/app/controllers/CaseCtrl.scala b/thehive-backend/app/controllers/CaseCtrl.scala index a3654677a0..1160f20686 100644 --- a/thehive-backend/app/controllers/CaseCtrl.scala +++ b/thehive-backend/app/controllers/CaseCtrl.scala @@ -126,7 +126,7 @@ class CaseCtrl @Inject()( } @Timed - def linkedCases(id: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def linkedCases(id: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ caseSrv .linkedCases(id) .runWith(Sink.seq) diff --git a/thehive-backend/app/controllers/CaseTemplateCtrl.scala b/thehive-backend/app/controllers/CaseTemplateCtrl.scala index 66292283c3..091b4b45b3 100644 --- a/thehive-backend/app/controllers/CaseTemplateCtrl.scala +++ b/thehive-backend/app/controllers/CaseTemplateCtrl.scala @@ -36,7 +36,7 @@ class CaseTemplateCtrl @Inject()( } @Timed - def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ caseTemplateSrv .get(id) .map(caze ⇒ renderer.toOutput(OK, caze)) diff --git a/thehive-backend/app/controllers/CustomFieldsCtrl.scala b/thehive-backend/app/controllers/CustomFieldsCtrl.scala index a5a7b38b35..4975e243a8 100644 --- a/thehive-backend/app/controllers/CustomFieldsCtrl.scala +++ b/thehive-backend/app/controllers/CustomFieldsCtrl.scala @@ -1,17 +1,15 @@ package controllers import scala.concurrent.{ExecutionContext, Future} - import play.api.http.Status import play.api.libs.json.{JsNumber, JsObject, Json} import play.api.mvc.{AbstractController, Action, AnyContent, ControllerComponents} - import akka.stream.Materializer import akka.stream.scaladsl.Sink -import com.sksamuel.elastic4s.http.ElasticDsl.{search, termsAggregation} +import com.sksamuel.elastic4s.ElasticDsl.{search, termsAgg} +import com.sksamuel.elastic4s.requests.searches.aggs.responses.bucket.Terms import javax.inject.{Inject, Singleton} import models.Roles - import org.elastic4play.NotFoundError import org.elastic4play.controllers.Authenticated import org.elastic4play.database.DBFind @@ -43,9 +41,9 @@ class CustomFieldsCtrl @Inject()( .flatMap { customFieldType ⇒ val filter = and("relations" in ("case", "alert", "caseTemplate"), contains(s"customFields.$customField.$customFieldType")) dbfind( - indexName ⇒ search(indexName).query(filter.query).aggregations(termsAggregation("t").field("relations")) + indexName ⇒ search(indexName).query(filter.query).aggregations(termsAgg("t","relations")) ).map { searchResponse ⇒ - val buckets = searchResponse.aggregations.terms("t").buckets + val buckets = searchResponse.aggregations.result[Terms]("t").buckets val total = buckets.map(_.docCount).sum val result = buckets.map(b ⇒ b.key → JsNumber(b.docCount)) :+ ("total" → JsNumber(total)) Ok(JsObject(result)) diff --git a/thehive-backend/app/controllers/DBListCtrl.scala b/thehive-backend/app/controllers/DBListCtrl.scala index d4c6a68df3..74496673d6 100644 --- a/thehive-backend/app/controllers/DBListCtrl.scala +++ b/thehive-backend/app/controllers/DBListCtrl.scala @@ -23,14 +23,14 @@ class DBListCtrl @Inject()( ) extends AbstractController(components) { @Timed("controllers.DBListCtrl.list") - def list: Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def list: Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ dblists.listAll.map { listNames ⇒ renderer.toOutput(OK, listNames) } } @Timed("controllers.DBListCtrl.listItems") - def listItems(listName: String): Action[AnyContent] = authenticated(Roles.read) { implicit request ⇒ + def listItems(listName: String): Action[AnyContent] = authenticated(Roles.read) { _ ⇒ val (src, _) = dblists(listName).getItems[JsValue] val items = src .map { case (id, value) ⇒ s""""$id":$value""" } diff --git a/thehive-backend/app/controllers/DashboardCtrl.scala b/thehive-backend/app/controllers/DashboardCtrl.scala index 901a0b7dc5..b4311da67a 100644 --- a/thehive-backend/app/controllers/DashboardCtrl.scala +++ b/thehive-backend/app/controllers/DashboardCtrl.scala @@ -21,7 +21,6 @@ import org.elastic4play.{AuthorizationError, BadRequestError, Timed} @Singleton class DashboardCtrl @Inject()( dashboardSrv: DashboardSrv, - auxSrv: AuxSrv, authenticated: Authenticated, renderer: Renderer, components: ControllerComponents, @@ -41,7 +40,7 @@ class DashboardCtrl @Inject()( } @Timed - def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ dashboardSrv.get(id).map { dashboard ⇒ renderer.toOutput(OK, dashboard) } diff --git a/thehive-backend/app/controllers/DescribeCtrl.scala b/thehive-backend/app/controllers/DescribeCtrl.scala index 24829e7b57..5e00dbc370 100644 --- a/thehive-backend/app/controllers/DescribeCtrl.scala +++ b/thehive-backend/app/controllers/DescribeCtrl.scala @@ -33,7 +33,7 @@ class DescribeCtrl @Inject()( Json.obj("label" → model.label, "path" → model.path, "attributes" → attributeDefinitions) } - def describe(modelName: String): Action[AnyContent] = authenticated(Roles.read) { implicit request ⇒ + def describe(modelName: String): Action[AnyContent] = authenticated(Roles.read) { _ ⇒ modelSrv(modelName) .map { model ⇒ renderer.toOutput(OK, modelToJson(model)) @@ -43,7 +43,7 @@ class DescribeCtrl @Inject()( private val allModels: Seq[String] = Seq("case", "case_artifact", "case_task", "case_task_log", "alert", "case_artifact_job", "audit", "action") - def describeAll: Action[AnyContent] = authenticated(Roles.read) { implicit request ⇒ + def describeAll: Action[AnyContent] = authenticated(Roles.read) { _ ⇒ val entityDefinitions = modelSrv .list .collect { diff --git a/thehive-backend/app/controllers/LogCtrl.scala b/thehive-backend/app/controllers/LogCtrl.scala index 5bb5398e67..ff4879dfd4 100644 --- a/thehive-backend/app/controllers/LogCtrl.scala +++ b/thehive-backend/app/controllers/LogCtrl.scala @@ -34,7 +34,7 @@ class LogCtrl @Inject()( } @Timed - def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ logSrv .get(id) .map(log ⇒ renderer.toOutput(OK, log)) diff --git a/thehive-backend/app/controllers/StatusCtrl.scala b/thehive-backend/app/controllers/StatusCtrl.scala index 936a029744..63b24b1281 100644 --- a/thehive-backend/app/controllers/StatusCtrl.scala +++ b/thehive-backend/app/controllers/StatusCtrl.scala @@ -1,25 +1,24 @@ package controllers import akka.actor.ActorSystem - -import scala.collection.immutable -import scala.concurrent.ExecutionContext -import scala.util.Try -import play.api.Configuration -import play.api.libs.json.Json.toJsFieldJsValueWrapper -import play.api.libs.json.{JsBoolean, JsObject, JsString, Json} -import play.api.mvc.{AbstractController, Action, AnyContent, ControllerComponents} -import com.sksamuel.elastic4s.http.ElasticDsl +import com.sksamuel.elastic4s.ElasticDsl import connectors.Connector import javax.inject.{Inject, Singleton} import models.HealthStatus -import org.elasticsearch.client.Node import org.elastic4play.Timed import org.elastic4play.database.DBIndex import org.elastic4play.services.AuthSrv import org.elastic4play.services.auth.MultiAuthSrv +import org.elasticsearch.client.Node +import play.api.Configuration +import play.api.libs.json.Json.toJsFieldJsValueWrapper +import play.api.libs.json.{JsBoolean, JsObject, JsString, Json} +import play.api.mvc.{AbstractController, Action, AnyContent, ControllerComponents} +import scala.collection.immutable +import scala.concurrent.ExecutionContext import scala.concurrent.duration.{DurationInt, FiniteDuration} +import scala.util.Try @Singleton class StatusCtrl @Inject()( diff --git a/thehive-backend/app/controllers/StreamCtrl.scala b/thehive-backend/app/controllers/StreamCtrl.scala index 7f3ca03a27..756236b735 100644 --- a/thehive-backend/app/controllers/StreamCtrl.scala +++ b/thehive-backend/app/controllers/StreamCtrl.scala @@ -1,30 +1,27 @@ package controllers +import akka.actor.{ActorIdentity, ActorSystem, Identify, Props} +import akka.cluster.pubsub.DistributedPubSub +import akka.cluster.pubsub.DistributedPubSubMediator.{Put, Send} +import akka.pattern.{AskTimeoutException, ask} +import akka.util.Timeout import javax.inject.{Inject, Singleton} - -import scala.collection.immutable -import scala.concurrent.{ExecutionContext, Future} -import scala.concurrent.duration.{DurationLong, FiniteDuration} -import scala.util.Random - +import models.Roles +import org.elastic4play.Timed +import org.elastic4play.controllers._ +import org.elastic4play.services.{MigrationSrv, UserSrv} import play.api.http.Status import play.api.libs.json.Json import play.api.libs.json.Json.toJsFieldJsValueWrapper import play.api.mvc._ import play.api.{Configuration, Logger} - -import akka.actor.{ActorIdentity, ActorSystem, Identify, Props} -import akka.cluster.pubsub.DistributedPubSub -import akka.cluster.pubsub.DistributedPubSubMediator.{Put, Send} -import akka.pattern.{ask, AskTimeoutException} -import akka.util.Timeout -import models.Roles import services.StreamActor import services.StreamActor.StreamMessages -import org.elastic4play.controllers._ -import org.elastic4play.services.{AuxSrv, EventSrv, MigrationSrv, UserSrv} -import org.elastic4play.Timed +import scala.collection.immutable +import scala.concurrent.duration.{DurationLong, FiniteDuration} +import scala.concurrent.{ExecutionContext, Future} +import scala.util.Random @Singleton class StreamCtrl( @@ -32,9 +29,7 @@ class StreamCtrl( refresh: FiniteDuration, authenticated: Authenticated, renderer: Renderer, - eventSrv: EventSrv, userSrv: UserSrv, - auxSrv: AuxSrv, migrationSrv: MigrationSrv, components: ControllerComponents, implicit val system: ActorSystem, @@ -46,9 +41,7 @@ class StreamCtrl( configuration: Configuration, authenticated: Authenticated, renderer: Renderer, - eventSrv: EventSrv, userSrv: UserSrv, - auxSrv: AuxSrv, migrationSrv: MigrationSrv, components: ControllerComponents, system: ActorSystem, @@ -59,9 +52,7 @@ class StreamCtrl( configuration.getMillis("stream.longpolling.refresh").millis, authenticated, renderer, - eventSrv, userSrv, - auxSrv, migrationSrv, components, system, @@ -125,7 +116,7 @@ class StreamCtrl( } @Timed("controllers.StreamCtrl.status") - def status = Action { implicit request ⇒ + def status: Action[AnyContent] = Action { implicit request ⇒ val status = authenticated.expirationStatus(request) match { case ExpirationWarning(duration) ⇒ Json.obj("remaining" → duration.toSeconds, "warning" → true) case ExpirationError ⇒ Json.obj("remaining" → 0, "warning" → true) diff --git a/thehive-backend/app/controllers/TaskCtrl.scala b/thehive-backend/app/controllers/TaskCtrl.scala index defb9d4143..730548103e 100644 --- a/thehive-backend/app/controllers/TaskCtrl.scala +++ b/thehive-backend/app/controllers/TaskCtrl.scala @@ -36,7 +36,7 @@ class TaskCtrl @Inject()( } @Timed - def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ taskSrv .get(id) .map(task ⇒ renderer.toOutput(OK, task)) diff --git a/thehive-backend/app/controllers/UserCtrl.scala b/thehive-backend/app/controllers/UserCtrl.scala index a785317ad1..2965a74bcb 100644 --- a/thehive-backend/app/controllers/UserCtrl.scala +++ b/thehive-backend/app/controllers/UserCtrl.scala @@ -41,7 +41,7 @@ class UserCtrl @Inject()( } @Timed - def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ userSrv .get(id) .map { user ⇒ diff --git a/thehive-backend/app/models/Alert.scala b/thehive-backend/app/models/Alert.scala index b9afab587f..f45c45db98 100644 --- a/thehive-backend/app/models/Alert.scala +++ b/thehive-backend/app/models/Alert.scala @@ -2,32 +2,18 @@ package models import java.util.Date -import scala.concurrent.Future -import scala.util.Try - -import play.api.Logger -import play.api.libs.json._ - -import javax.inject.{Inject, Singleton} +import javax.inject.Singleton import models.JsonFormat.alertStatusFormat -import services.AuditedModel - import org.elastic4play.controllers.JsonInputValue -import org.elastic4play.models.{ - Attribute, - AttributeDef, - BaseEntity, - EntityDef, - HiveEnumeration, - ModelDef, - MultiAttributeFormat, - OptionalAttributeFormat, - AttributeFormat ⇒ F, - AttributeOption ⇒ O -} -import org.elastic4play.services.DBLists +import org.elastic4play.models.{Attribute, AttributeDef, BaseEntity, EntityDef, HiveEnumeration, ModelDef, MultiAttributeFormat, OptionalAttributeFormat, AttributeFormat => F, AttributeOption => O} import org.elastic4play.utils.Hasher import org.elastic4play.{AttributeCheckingError, InvalidFormatAttributeError} +import play.api.Logger +import play.api.libs.json._ +import services.AuditedModel + +import scala.concurrent.Future +import scala.util.Try object AlertStatus extends Enumeration with HiveEnumeration { type Type = Value @@ -60,7 +46,7 @@ trait AlertAttributes { Attribute("alert", "ioc", OptionalAttributeFormat(F.booleanFmt), Nil, None, "") ) } - + val alertId: A[String] = attribute("_id", F.stringFmt, "Alert id", O.readonly) val tpe: A[String] = attribute("type", F.stringFmt, "Type of the alert", O.readonly) val source: A[String] = attribute("source", F.stringFmt, "Source of the alert", O.readonly) @@ -81,7 +67,7 @@ trait AlertAttributes { } @Singleton -class AlertModel @Inject()(dblists: DBLists) extends ModelDef[AlertModel, Alert]("alert", "Alert", "/alert") with AlertAttributes with AuditedModel { +class AlertModel extends ModelDef[AlertModel, Alert]("alert", "Alert", "/alert") with AlertAttributes with AuditedModel { private[AlertModel] lazy val logger = Logger(getClass) override val defaultSortBy: Seq[String] = Seq("-date") diff --git a/thehive-backend/app/models/Artifact.scala b/thehive-backend/app/models/Artifact.scala index 25264ee27d..beb747afaa 100644 --- a/thehive-backend/app/models/Artifact.scala +++ b/thehive-backend/app/models/Artifact.scala @@ -102,10 +102,9 @@ class ArtifactModel @Inject()( mm.addValue((attrs \ "data").asOpt[JsValue].getOrElse(JsNull)) mm.addValue((attrs \ "dataType").asOpt[JsValue].getOrElse(JsNull)) for { - IOResult(_, done) ← (attrs \ "attachment" \ "filepath") + IOResult(_, _) ← (attrs \ "attachment" \ "filepath") .asOpt[String] .fold(Future.successful(IOResult(0, Success(Done))))(file ⇒ mm.addFile(file)) - _ ← Future.fromTry(done) _ ← (attrs \ "attachment" \ "id") .asOpt[String] .fold(Future.successful(NotUsed: NotUsed)) { fileId ⇒ diff --git a/thehive-backend/app/models/Audit.scala b/thehive-backend/app/models/Audit.scala index 6cd670c8eb..5aa0f32b5a 100644 --- a/thehive-backend/app/models/Audit.scala +++ b/thehive-backend/app/models/Audit.scala @@ -48,7 +48,7 @@ class AuditModel(auditName: String, auditedModels: immutable.Set[AuditedModel], extends ModelDef[AuditModel, Audit](auditName, "Audit", "/audit") with AuditAttributes { - lazy val auxSrv = auxSrvProvider.get() + lazy val auxSrv: AuxSrv = auxSrvProvider.get() @Inject() def this( configuration: Configuration, @@ -134,5 +134,5 @@ class AuditModel(auditName: String, auditedModels: immutable.Set[AuditedModel], } class Audit(model: AuditModel, attributes: JsObject) extends EntityDef[AuditModel, Audit](model, attributes) with AuditAttributes { - def detailsAttributes = Nil + def detailsAttributes: Seq[Attribute[_]] = Nil } diff --git a/thehive-backend/app/models/CaseTemplate.scala b/thehive-backend/app/models/CaseTemplate.scala index dc3f607298..36d941f9ee 100644 --- a/thehive-backend/app/models/CaseTemplate.scala +++ b/thehive-backend/app/models/CaseTemplate.scala @@ -44,5 +44,5 @@ class CaseTemplateModel @Inject()(taskModel: TaskModel) class CaseTemplate(model: CaseTemplateModel, attributes: JsObject) extends EntityDef[CaseTemplateModel, CaseTemplate](model, attributes) with CaseTemplateAttributes { - def taskAttributes = Nil + def taskAttributes: Seq[Attribute[_]] = Nil } diff --git a/thehive-backend/app/models/Log.scala b/thehive-backend/app/models/Log.scala index 611ac378ac..b8e77d5d2e 100644 --- a/thehive-backend/app/models/Log.scala +++ b/thehive-backend/app/models/Log.scala @@ -35,7 +35,7 @@ class LogModel @Inject()(taskModel: TaskModel) extends ChildModelDef[LogModel, Log, TaskModel, Task](taskModel, "case_task_log", "Log", "/case/task/log") with LogAttributes with AuditedModel { - override val defaultSortBy = Seq("-startDate") - override val removeAttribute = Json.obj("status" → LogStatus.Deleted) + override val defaultSortBy: Seq[String] = Seq("-startDate") + override val removeAttribute: JsObject = Json.obj("status" → LogStatus.Deleted) } class Log(model: LogModel, attributes: JsObject) extends EntityDef[LogModel, Log](model, attributes) with LogAttributes diff --git a/thehive-backend/app/models/Migration.scala b/thehive-backend/app/models/Migration.scala index ae6a65ce22..febdd6e875 100644 --- a/thehive-backend/app/models/Migration.scala +++ b/thehive-backend/app/models/Migration.scala @@ -3,26 +3,24 @@ package models import java.nio.file.{Files, Path} import java.util.Date -import scala.collection.JavaConverters._ -import scala.concurrent.{ExecutionContext, Future} -import scala.math.BigDecimal.int2bigDecimal -import scala.util.Try - -import play.api.libs.json.JsValue.jsValueToJsLookup -import play.api.libs.json._ -import play.api.{Configuration, Environment, Logger} - import akka.NotUsed import akka.stream.Materializer import akka.stream.scaladsl.Source import javax.inject.{Inject, Singleton} -import services.{AlertSrv, DashboardSrv} - import org.elastic4play.ConflictError import org.elastic4play.controllers.Fields import org.elastic4play.services.JsonFormat.attachmentFormat -import org.elastic4play.services.{IndexType, _} +import org.elastic4play.services._ import org.elastic4play.utils.Hasher +import play.api.libs.json.JsValue.jsValueToJsLookup +import play.api.libs.json._ +import play.api.{Configuration, Environment, Logger} +import services.{AlertSrv, DashboardSrv} + +import scala.collection.JavaConverters._ +import scala.concurrent.{ExecutionContext, Future} +import scala.math.BigDecimal.int2bigDecimal +import scala.util.Try case class UpdateMispAlertArtifact() extends EventMessage @@ -143,8 +141,6 @@ class Migration( } - override def indexType(version: Int): IndexType.Value = if (version > 14) IndexType.indexWithoutMappingTypes else IndexType.indexWithMappingTypes - override val operations: PartialFunction[DatabaseState, Seq[Operation]] = { case DatabaseState(version) if version < 7 ⇒ Nil case DatabaseState(7) ⇒ @@ -248,7 +244,7 @@ class Migration( dataStr ← (artifact \ "data").asOpt[String] dataJson ← Try(Json.parse(dataStr)).toOption dataObj ← dataJson.asOpt[JsObject] - filename ← (dataObj \ "filename").asOpt[String].map(_.split("|").head) + filename ← (dataObj \ "filename").asOpt[String].map(_.split("\\|").head) attributeId ← (dataObj \ "attributeId").asOpt[String] attributeType ← (dataObj \ "attributeType").asOpt[String] } yield Future.successful( @@ -280,7 +276,7 @@ class Migration( } } Source - .fromFuture(artifactsAndData) + .future(artifactsAndData) .mapConcat { ad ⇒ val updatedAlert = alert + ("artifacts" → JsArray(ad.map(_._1))) updatedAlert :: ad.flatMap(_._2) @@ -355,7 +351,7 @@ class Migration( audit } } - Source.fromFuture(updatedAudit) + Source.future(updatedAudit) case audit ⇒ Source.single(audit) } case other ⇒ f(other) @@ -378,6 +374,7 @@ class Migration( ("sequenceCounter" → counter) } ) + case DatabaseState(15) ⇒ Nil } private def generateAlertId(alert: JsObject): String = { diff --git a/thehive-backend/app/models/Roles.scala b/thehive-backend/app/models/Roles.scala index 393b22cc67..9e6e574b31 100644 --- a/thehive-backend/app/models/Roles.scala +++ b/thehive-backend/app/models/Roles.scala @@ -1,16 +1,14 @@ package models -import play.api.libs.json.{JsString, JsValue} - -import com.sksamuel.elastic4s.http.ElasticDsl.keywordField -import com.sksamuel.elastic4s.mappings.KeywordField -import org.scalactic.{Every, Good, One, Or} +import com.sksamuel.elastic4s.ElasticDsl.keywordField +import com.sksamuel.elastic4s.requests.mappings.KeywordField import models.JsonFormat.roleFormat - -import org.elastic4play.{AttributeError, InvalidFormatAttributeError} import org.elastic4play.controllers.{InputValue, JsonInputValue, StringInputValue} import org.elastic4play.models.AttributeFormat import org.elastic4play.services.Role +import org.elastic4play.{AttributeError, InvalidFormatAttributeError} +import org.scalactic.{Every, Good, One, Or} +import play.api.libs.json.{JsString, JsValue} object Roles { object read extends Role("read") diff --git a/thehive-backend/app/models/User.scala b/thehive-backend/app/models/User.scala index 97fd563407..8c88d3465c 100644 --- a/thehive-backend/app/models/User.scala +++ b/thehive-backend/app/models/User.scala @@ -1,15 +1,12 @@ package models import scala.concurrent.Future - import play.api.libs.json.JsValue.jsValueToJsLookup import play.api.libs.json._ - import models.JsonFormat.userStatusFormat import services.AuditedModel - -import org.elastic4play.models.{AttributeDef, BaseEntity, EntityDef, HiveEnumeration, ModelDef, AttributeFormat ⇒ F, AttributeOption ⇒ O} -import org.elastic4play.services.{User ⇒ EUser} +import org.elastic4play.models.{AttributeDef, BaseEntity, EntityDef, HiveEnumeration, ModelDef, AttributeFormat => F, AttributeOption => O} +import org.elastic4play.services.{Role, User => EUser} object UserStatus extends Enumeration with HiveEnumeration { type Type = Value @@ -40,8 +37,8 @@ class UserModel extends ModelDef[UserModel, User]("user", "User", "/user") with } class User(model: UserModel, attributes: JsObject) extends EntityDef[UserModel, User](model, attributes) with UserAttributes with EUser { - override def getUserName = userName() - override def getRoles = roles() + override def getUserName: String = userName() + override def getRoles: Seq[Role] = roles() override def toJson: JsObject = super.toJson + diff --git a/thehive-backend/app/models/package.scala b/thehive-backend/app/models/package.scala index 5817120386..92fd3669a9 100644 --- a/thehive-backend/app/models/package.scala +++ b/thehive-backend/app/models/package.scala @@ -1,3 +1,3 @@ package object models { - val modelVersion = 15 + val modelVersion = 16 } diff --git a/thehive-backend/app/services/AlertSrv.scala b/thehive-backend/app/services/AlertSrv.scala index 79755cd5c3..952b5719dd 100644 --- a/thehive-backend/app/services/AlertSrv.scala +++ b/thehive-backend/app/services/AlertSrv.scala @@ -39,7 +39,6 @@ object AlertSrv { @Singleton class AlertSrv( - templates: Map[String, String], alertModel: AlertModel, createSrv: CreateSrv, getSrv: GetSrv, @@ -51,7 +50,6 @@ class AlertSrv( caseTemplateSrv: CaseTemplateSrv, attachmentSrv: AttachmentSrv, connectors: ConnectorRouter, - hashAlg: Seq[String], implicit val ec: ExecutionContext, implicit val mat: Materializer ) extends AlertTransformer { @@ -73,7 +71,6 @@ class AlertSrv( mat: Materializer ) = this( - Map.empty[String, String], alertModel: AlertModel, createSrv, getSrv, @@ -85,7 +82,6 @@ class AlertSrv( caseTemplateSrv, attachmentSrv, connectors, - (configuration.get[String]("datastore.hash.main") +: configuration.get[Seq[String]]("datastore.hash.extra")).distinct, ec, mat ) @@ -204,7 +200,7 @@ class AlertSrv( case None ⇒ updateSrv[AlertModel, Alert](alertModel, alert.id, Fields.empty.set("status", "New"), modifyConfig) } - def getCaseTemplate(alert: Alert, customCaseTemplate: Option[String]): Future[Option[CaseTemplate]] = + def getCaseTemplate(customCaseTemplate: Option[String]): Future[Option[CaseTemplate]] = customCaseTemplate.fold[Future[Option[CaseTemplate]]](Future.successful(None)) { templateName ⇒ caseTemplateSrv .getByName(templateName) @@ -226,7 +222,7 @@ class AlertSrv( } yield caze case _ ⇒ for { - caseTemplate ← getCaseTemplate(alert, customCaseTemplate) + caseTemplate ← getCaseTemplate(customCaseTemplate) caze ← caseSrv.create( Fields .empty diff --git a/thehive-backend/app/services/AuditSrv.scala b/thehive-backend/app/services/AuditSrv.scala index bd4c147c24..ee7e9e380b 100644 --- a/thehive-backend/app/services/AuditSrv.scala +++ b/thehive-backend/app/services/AuditSrv.scala @@ -24,7 +24,7 @@ trait AuditedModel { self: BaseModelDef ⇒ lazy val auditedAttributes: Map[String, Attribute[_]] = attributes.collect { case a if !a.isUnaudited ⇒ a.attributeName → a }.toMap - def selectAuditedAttributes(attrs: JsObject) = JsObject { + def selectAuditedAttributes(attrs: JsObject): JsObject = JsObject { attrs.fields.flatMap { case (attrName, value) ⇒ val attrNames = attrName.split("\\.").toSeq @@ -102,7 +102,7 @@ class AuditActor @Inject()(auditModel: AuditModel, createSrv: CreateSrv, eventSr extends Actor { object EntityExtractor { - def unapply(e: BaseEntity) = Some((e.model, e.id, e.routing)) + def unapply(e: BaseEntity): Some[(BaseModelDef, String, String)] = Some((e.model, e.id, e.routing)) } var currentRequestIds = Set.empty[String] private[AuditActor] lazy val logger = Logger(getClass) diff --git a/thehive-backend/app/services/CustomWSAPI.scala b/thehive-backend/app/services/CustomWSAPI.scala index ae57ec135b..f27e5a066d 100644 --- a/thehive-backend/app/services/CustomWSAPI.scala +++ b/thehive-backend/app/services/CustomWSAPI.scala @@ -60,7 +60,7 @@ object CustomWSAPI { .trustManagerConfig .withTrustStoreConfigs( clientConfig.wsClientConfig.ssl.trustManagerConfig.trustStoreConfigs :+ TrustStoreConfig( - filePath = Some(p.toString), + filePath = Some(p), data = None ) ) diff --git a/thehive-backend/app/services/OAuth2Srv.scala b/thehive-backend/app/services/OAuth2Srv.scala index 936a6ad678..d69865369d 100644 --- a/thehive-backend/app/services/OAuth2Srv.scala +++ b/thehive-backend/app/services/OAuth2Srv.scala @@ -1,18 +1,19 @@ package services -import javax.inject.{Inject, Singleton} +import java.util.UUID import akka.stream.Materializer +import javax.inject.{Inject, Singleton} import org.elastic4play.services.{AuthContext, AuthSrv} -import org.elastic4play.{AuthenticationError, AuthorizationError, OAuth2Redirect} -import play.api.http.Status -import play.api.libs.json.{JsObject, JsValue} +import org.elastic4play.{AuthenticationError, BadRequestError, NotFoundError} +import play.api.libs.json.JsObject import play.api.libs.ws.WSClient -import play.api.mvc.RequestHeader +import play.api.mvc.{RequestHeader, Result, Results} import play.api.{Configuration, Logger} import services.mappers.UserMapper import scala.concurrent.{ExecutionContext, Future} +import scala.util.{Failure, Success} case class OAuth2Config( clientId: String, @@ -24,25 +25,27 @@ case class OAuth2Config( tokenUrl: String, userUrl: String, scope: String, - autocreate: Boolean, - autoupdate: Boolean + authorizationHeader: String, + autoupdate: Boolean, + autocreate: Boolean ) object OAuth2Config { def apply(configuration: Configuration): Option[OAuth2Config] = for { - clientId ← configuration.getOptional[String]("auth.oauth2.clientId") - clientSecret ← configuration.getOptional[String]("auth.oauth2.clientSecret") - redirectUri ← configuration.getOptional[String]("auth.oauth2.redirectUri") - responseType ← configuration.getOptional[String]("auth.oauth2.responseType") - grantType ← configuration.getOptional[String]("auth.oauth2.grantType") + clientId ← configuration.getOptional[String]("auth.oauth2.clientId") + clientSecret ← configuration.getOptional[String]("auth.oauth2.clientSecret") + redirectUri ← configuration.getOptional[String]("auth.oauth2.redirectUri") + responseType ← configuration.getOptional[String]("auth.oauth2.responseType") + grantType = configuration.getOptional[String]("auth.oauth2.grantType").getOrElse("authorization_code") authorizationUrl ← configuration.getOptional[String]("auth.oauth2.authorizationUrl") - userUrl ← configuration.getOptional[String]("auth.oauth2.userUrl") tokenUrl ← configuration.getOptional[String]("auth.oauth2.tokenUrl") + userUrl ← configuration.getOptional[String]("auth.oauth2.userUrl") scope ← configuration.getOptional[String]("auth.oauth2.scope") - autocreate = configuration.getOptional[Boolean]("auth.sso.autocreate").getOrElse(false) - autoupdate = configuration.getOptional[Boolean]("auth.sso.autoupdate").getOrElse(false) + authorizationHeader = configuration.getOptional[String]("auth.oauth2.authorizationHeader").getOrElse("Bearer") + autocreate = configuration.getOptional[Boolean]("auth.sso.autocreate").getOrElse(false) + autoupdate = configuration.getOptional[Boolean]("auth.sso.autoupdate").getOrElse(false) } yield OAuth2Config( clientId, clientSecret, @@ -53,6 +56,7 @@ object OAuth2Config { tokenUrl, userUrl, scope, + authorizationHeader, autocreate, autoupdate ) @@ -79,104 +83,146 @@ class OAuth2Srv( private def withOAuth2Config[A](body: OAuth2Config ⇒ Future[A]): Future[A] = oauth2Config.fold[Future[A]](Future.failed(AuthenticationError("OAuth2 not configured properly")))(body) - override def authenticate()(implicit request: RequestHeader): Future[AuthContext] = - withOAuth2Config { cfg ⇒ - request - .queryString - .get(Oauth2TokenQueryString) - .flatMap(_.headOption) - .fold(createOauth2Redirect(cfg.clientId)) { code ⇒ - getAuthTokenAndAuthenticate(cfg.clientId, code) + override def authenticate()(implicit request: RequestHeader): Future[Either[Result, AuthContext]] = + withOAuth2Config { oauth2Config ⇒ + if (!isSecuredAuthCode(request)) { + logger.debug("Code or state is not provided, redirect to authorizationUrl") + Future.successful(Left(authRedirect(oauth2Config))) + } else { + (for { + token ← getToken(oauth2Config, request) + userData ← getUserData(oauth2Config, token) + authContext ← authenticate(oauth2Config, request, userData) + } yield Right(authContext)).recoverWith { + case error ⇒ Future.failed(AuthenticationError(s"OAuth2 authentication failure: ${error.getMessage}")) } + } } - private def getAuthTokenAndAuthenticate(clientId: String, code: String)(implicit request: RequestHeader): Future[AuthContext] = { - logger.debug("Getting user token with the code from the response") - withOAuth2Config { cfg ⇒ - ws.url(cfg.tokenUrl) - .post( - Map( - "code" → code, - "grant_type" → cfg.grantType, - "client_secret" → cfg.clientSecret, - "redirect_uri" → cfg.redirectUri, - "client_id" → clientId - ) - ) - .recoverWith { - case error ⇒ - logger.error(s"Token verification failure", error) - Future.failed(AuthenticationError("Token verification failure")) - } - .flatMap { r ⇒ - r.status match { - case Status.OK ⇒ - logger.debug("Getting user info using access token") - val accessToken = (r.json \ "access_token").asOpt[String].getOrElse("") - val authHeader = "Authorization" → s"Bearer $accessToken" - ws.url(cfg.userUrl) - .addHttpHeaders(authHeader) - .get() - .flatMap { userResponse ⇒ - if (userResponse.status != Status.OK) { - Future.failed(AuthenticationError(s"Unexpected response from server: ${userResponse.status} ${userResponse.body}")) - } else { - val response = userResponse.json.asInstanceOf[JsObject] - getOrCreateUser(response, authHeader) - } - } - case _ ⇒ - logger.error(s"Unexpected response from server: ${r.status} ${r.body}") - Future.failed(AuthenticationError("Unexpected response from server")) - } - } - } + private def isSecuredAuthCode(request: RequestHeader): Boolean = + request.queryString.contains("code") && request.queryString.contains("state") + + /** + * Filter checking whether we initiate the OAuth2 process + * and redirecting to OAuth2 server if necessary + * @return + */ + private def authRedirect(oauth2Config: OAuth2Config): Result = { + val state = UUID.randomUUID().toString + val queryStringParams = Map[String, Seq[String]]( + "scope" → Seq(oauth2Config.scope), + "response_type" → Seq(oauth2Config.responseType), + "redirect_uri" → Seq(oauth2Config.redirectUri), + "client_id" → Seq(oauth2Config.clientId), + "state" → Seq(state) + ) + + logger.debug(s"Redirecting to ${oauth2Config.redirectUri} with $queryStringParams and state $state") + Results + .Redirect(oauth2Config.authorizationUrl, queryStringParams, status = 302) + .withSession("state" → state) } - private def getOrCreateUser(response: JsValue, authHeader: (String, String))(implicit request: RequestHeader): Future[AuthContext] = - withOAuth2Config { cfg ⇒ - ssoMapper.getUserFields(response, Some(authHeader)).flatMap { userFields ⇒ - val userId = userFields.getString("login").getOrElse("") - userSrv - .get(userId) - .flatMap(user ⇒ { - if (cfg.autoupdate) { - logger.debug(s"Updating OAuth/OIDC user") - userSrv.inInitAuthContext { implicit authContext ⇒ - // Only update name and roles, not login (can't change it) - userSrv - .update(user, userFields.unset("login")) - .flatMap(user ⇒ { - userSrv.getFromUser(request, user, name) - }) - } - } else { - userSrv.getFromUser(request, user, name) + /** + * Enriching the initial request with OAuth2 token gotten + * from OAuth2 code + * @return + */ + private def getToken[A](oauth2Config: OAuth2Config, request: RequestHeader): Future[String] = { + val token = + for { + state ← request.session.get("state") + stateQs ← request.queryString.get("state").flatMap(_.headOption) + if state == stateQs + } yield request.queryString.get("code").flatMap(_.headOption) match { + case Some(code) ⇒ + logger.debug(s"Attempting to retrieve OAuth2 token from ${oauth2Config.tokenUrl} with code $code") + getAuthTokenFromCode(oauth2Config, code, state) + .map { t ⇒ + logger.trace(s"Got token $t") + t } - }) - .recoverWith { - case authErr: AuthorizationError ⇒ Future.failed(authErr) - case _ if cfg.autocreate ⇒ - logger.debug(s"Creating OAuth/OIDC user") - userSrv.inInitAuthContext { implicit authContext ⇒ - userSrv - .create(userFields) - .flatMap(user ⇒ { - userSrv.getFromUser(request, user, name) - }) - } - } + case None ⇒ + Future.failed(AuthenticationError(s"OAuth2 server code missing ${request.queryString.get("error")}")) } - } + token.getOrElse(Future.failed(BadRequestError("OAuth2 states mismatch"))) + } - private def createOauth2Redirect(clientId: String): Future[AuthContext] = - withOAuth2Config { cfg ⇒ - val queryStringParams = Map[String, Seq[String]]( - "scope" → Seq(cfg.scope), - "response_type" → Seq(cfg.responseType), - "redirect_uri" → Seq(cfg.redirectUri), - "client_id" → Seq(clientId) + /** + * Querying the OAuth2 server for a token + * @param code the previously obtained code + * @return + */ + private def getAuthTokenFromCode(oauth2Config: OAuth2Config, code: String, state: String): Future[String] = { + logger.trace(s""" + |Request to ${oauth2Config.tokenUrl} with + | code: $code + | grant_type: ${oauth2Config.grantType} + | client_secret: ${oauth2Config.clientSecret} + | redirect_uri: ${oauth2Config.redirectUri} + | client_id: ${oauth2Config.clientId} + | state: $state + |""".stripMargin) + ws.url(oauth2Config.tokenUrl) + .withHttpHeaders("Accept" → "application/json") + .post( + Map( + "code" → code, + "grant_type" → oauth2Config.grantType, + "client_secret" → oauth2Config.clientSecret, + "redirect_uri" → oauth2Config.redirectUri, + "client_id" → oauth2Config.clientId, + "state" → state + ) ) - Future.failed(OAuth2Redirect(cfg.authorizationUrl, queryStringParams)) - } + .transform { + case Success(r) if r.status == 200 ⇒ Success((r.json \ "access_token").asOpt[String].getOrElse("")) + case Failure(error) ⇒ Failure(AuthenticationError(s"OAuth2 token verification failure ${error.getMessage}")) + case Success(r) ⇒ Failure(AuthenticationError(s"OAuth2/token unexpected response from server (${r.status} ${r.statusText})")) + } + } + + /** + * Client query for user data with OAuth2 token + * @param token the token + * @return + */ + private def getUserData(oauth2Config: OAuth2Config, token: String): Future[JsObject] = { + logger.trace(s"Request to ${oauth2Config.userUrl} with authorization header: ${oauth2Config.authorizationHeader} $token") + ws.url(oauth2Config.userUrl) + .addHttpHeaders("Authorization" → s"${oauth2Config.authorizationHeader} $token") + .get() + .transform { + case Success(r) if r.status == 200 ⇒ Success(r.json.as[JsObject]) + case Failure(error) ⇒ Failure(AuthenticationError(s"OAuth2 user data fetch failure ${error.getMessage}")) + case Success(r) ⇒ Failure(AuthenticationError(s"OAuth2/userinfo unexpected response from server (${r.status} ${r.statusText})")) + } + } + + private def authenticate(oauth2Config: OAuth2Config, request: RequestHeader, userData: JsObject): Future[AuthContext] = + for { + userFields ← ssoMapper.getUserFields(userData) + login ← userFields.getString("login").fold(Future.failed[String](AuthenticationError("")))(Future.successful) + user ← userSrv + .get(login) + .flatMap { + case u if oauth2Config.autoupdate ⇒ + logger.debug(s"Updating OAuth/OIDC user") + userSrv.inInitAuthContext { implicit authContext ⇒ + // Only update name and roles, not login (can't change it) + userSrv + .update(u, userFields.unset("login")) + + } + case u ⇒ Future.successful(u) + } + .recoverWith { + case _: NotFoundError if oauth2Config.autocreate ⇒ + logger.debug(s"Creating OAuth/OIDC user") + userSrv.inInitAuthContext { implicit authContext ⇒ + userSrv.create(userFields.set("login", userFields.getString("login").get.toLowerCase)) + } + } + authContext ← userSrv.getFromUser(request, user, name) + } yield authContext } diff --git a/thehive-backend/app/services/StreamMessage.scala b/thehive-backend/app/services/StreamMessage.scala index 57b87d1e0b..db679edb6a 100644 --- a/thehive-backend/app/services/StreamMessage.scala +++ b/thehive-backend/app/services/StreamMessage.scala @@ -32,7 +32,7 @@ case class AggregatedAuditMessage(auxSrv: AuxSrv, message: Future[JsObject], sum } object AggregatedAuditMessage { - lazy val logger = Logger(getClass) + lazy val logger: Logger = Logger(getClass) def apply(auxSrv: AuxSrv, operation: AuditOperation)(implicit ec: ExecutionContext): AggregatedAuditMessage = { // First operation of the group diff --git a/thehive-backend/app/services/StreamSrv.scala b/thehive-backend/app/services/StreamSrv.scala index b293f18c39..6cca5fb54b 100644 --- a/thehive-backend/app/services/StreamSrv.scala +++ b/thehive-backend/app/services/StreamSrv.scala @@ -29,7 +29,7 @@ object StreamActor { case class StreamMessages(messages: Seq[JsObject]) extends StreamActorMessage object StreamMessages { - val empty = StreamMessages(Nil) + val empty: StreamMessages = StreamMessages(Nil) } } diff --git a/thehive-backend/app/services/TheHiveAuthSrv.scala b/thehive-backend/app/services/TheHiveAuthSrv.scala index e66c783cdc..a49224aedb 100644 --- a/thehive-backend/app/services/TheHiveAuthSrv.scala +++ b/thehive-backend/app/services/TheHiveAuthSrv.scala @@ -29,7 +29,6 @@ object TheHiveAuthSrv { class TheHiveAuthSrv @Inject()( configuration: Configuration, authModules: immutable.Set[AuthSrv], - userSrv: UserSrv, implicit override val ec: ExecutionContext ) extends MultiAuthSrv( TheHiveAuthSrv.getAuthSrv(configuration.getDeprecated[Option[Seq[String]]]("auth.provider", "auth.type").getOrElse(Seq("local")), authModules), diff --git a/thehive-backend/app/services/mappers/GroupUserMapper.scala b/thehive-backend/app/services/mappers/GroupUserMapper.scala index cf036ce379..1d3817738f 100644 --- a/thehive-backend/app/services/mappers/GroupUserMapper.scala +++ b/thehive-backend/app/services/mappers/GroupUserMapper.scala @@ -4,14 +4,14 @@ import javax.inject.Inject import scala.concurrent.{ExecutionContext, Future} import scala.util.parsing.combinator._ - import play.api.{Configuration, Logger} import play.api.libs.json._ import play.api.libs.ws.WSClient - import org.elastic4play.{AuthenticationError, AuthorizationError} import org.elastic4play.controllers.Fields +import scala.util.matching.Regex + class GroupUserMapper( loginAttrName: String, nameAttrName: String, @@ -25,7 +25,7 @@ class GroupUserMapper( @Inject() def this(configuration: Configuration, ws: WSClient, ec: ExecutionContext) = this( - configuration.getOptional[String]("auth.sso.attributes.login").getOrElse("sub"), + configuration.getOptional[String]("auth.sso.attributes.login").getOrElse("login"), configuration.getOptional[String]("auth.sso.attributes.name").getOrElse("name"), configuration.getOptional[String]("auth.sso.attributes.groups").getOrElse(""), configuration.getOptional[Seq[String]]("auth.sso.defaultRoles").getOrElse(Seq()), @@ -40,60 +40,57 @@ class GroupUserMapper( private[GroupUserMapper] lazy val logger = Logger(getClass) private class RoleListParser extends RegexParsers { - val str = "[a-zA-Z0-9_]+".r - val strSpc = "[a-zA-Z0-9_ ]+".r - val realStr = ("\""~>strSpc<~"\"" | "'"~>strSpc<~"'" | str) + val str: Regex = "[a-zA-Z0-9_]+".r + val strSpc: Regex = "[a-zA-Z0-9_ ]+".r + val realStr: Parser[String] = "\"" ~> strSpc <~ "\"" | "'" ~> strSpc <~ "'" | str - def expr: Parser[Seq[String]] = { + def expr: Parser[Seq[String]] = "[" ~ opt(realStr ~ rep("," ~ realStr)) ~ "]" ^^ { - case _ ~ Some(firstRole ~ list) ~ _ ⇒ list.foldLeft(Seq(firstRole)) { - case (queue, _ ~ role) ⇒ role +: queue - } - case _ ~ _ ⇒ Seq.empty[String] + case _ ~ Some(firstRole ~ list) ~ _ ⇒ + list.foldLeft(Seq(firstRole)) { + case (queue, _ ~ role) ⇒ role +: queue + } + case _ ~ _ ⇒ Seq.empty[String] } | opt(realStr) ^^ { case Some(role) ⇒ Seq(role) case None ⇒ Seq.empty[String] } - } } - override def getUserFields(jsValue: JsValue, authHeader: Option[(String, String)]): Future[Fields] = { + override def getUserFields(jsValue: JsValue, authHeader: Option[(String, String)]): Future[Fields] = groupsUrl match { - case Some(groupsEndpointUrl) ⇒ { - logger.debug(s"Retreiving groups from ${groupsEndpointUrl}") + case Some(groupsEndpointUrl) ⇒ + logger.debug(s"Retreiving groups from $groupsEndpointUrl") val apiCall = authHeader.fold(ws.url(groupsEndpointUrl))(headers ⇒ ws.url(groupsEndpointUrl).addHttpHeaders(headers)) - apiCall.get.flatMap { r ⇒ extractGroupsThenBuildUserFields(jsValue, r.json) } - } - case None ⇒ { + apiCall.get.flatMap { r ⇒ + extractGroupsThenBuildUserFields(jsValue, r.json) + } + case None ⇒ logger.debug(s"Extracting groups from user info") extractGroupsThenBuildUserFields(jsValue, jsValue) - } } - } - private def extractGroupsThenBuildUserFields(jsValue: JsValue, groupsContainer: JsValue): Future[Fields] = { - (groupsContainer \ groupsAttrName) match { + private def extractGroupsThenBuildUserFields(jsValue: JsValue, groupsContainer: JsValue): Future[Fields] = + groupsContainer \ groupsAttrName match { // Groups received as valid JSON array case JsDefined(JsArray(groupsList)) ⇒ mapGroupsAndBuildUserFields(jsValue, groupsList.map(_.as[String]).toList) // Groups list received as string (invalid JSON, for example: "ROLE" or "['Role 1', ROLE2, 'Role_3']") - case JsDefined(JsString(groupsStr)) ⇒ { + case JsDefined(JsString(groupsStr)) ⇒ val parser = new RoleListParser parser.parseAll(parser.expr, groupsStr) match { case parser.Success(result, _) ⇒ mapGroupsAndBuildUserFields(jsValue, result) case err: parser.NoSuccess ⇒ Future.failed(AuthenticationError(s"User info fails: can't parse groups list (${err.msg})")) } - } // Invalid group list case JsDefined(error) ⇒ - Future.failed(AuthenticationError(s"User info fails: invalid groups list received in user info ('${error}' of type ${error.getClass})")) + Future.failed(AuthenticationError(s"User info fails: invalid groups list received in user info ('$error' of type ${error.getClass})")) // Groups field is undefined case _: JsUndefined ⇒ - Future.failed(AuthenticationError(s"User info fails: groups attribute ${groupsAttrName} doesn't exist in user info")) + Future.failed(AuthenticationError(s"User info fails: groups attribute $groupsAttrName doesn't exist in user info")) } - } private def mapGroupsAndBuildUserFields(jsValue: JsValue, jsonGroups: Seq[String]): Future[Fields] = { val mappedRoles = jsonGroups.flatMap(mappings.get).flatten.toSet @@ -111,7 +108,8 @@ class GroupUserMapper( } yield Fields(Json.obj("login" → login, "name" → name, "roles" → roles)) fields match { case JsSuccess(f, _) ⇒ Future.successful(f) - case JsError(errors) ⇒ Future.failed(AuthenticationError(s"User info fails: ${errors.map(_._2).map(_.map(_.messages.mkString(", ")).mkString("; ")).mkString}")) + case JsError(errors) ⇒ + Future.failed(AuthenticationError(s"User info fails: ${errors.map(_._2).map(_.map(_.messages.mkString(", ")).mkString("; ")).mkString}")) } } } diff --git a/thehive-backend/app/services/mappers/SimpleUserMapper.scala b/thehive-backend/app/services/mappers/SimpleUserMapper.scala index 598d9d2ece..7389909c11 100644 --- a/thehive-backend/app/services/mappers/SimpleUserMapper.scala +++ b/thehive-backend/app/services/mappers/SimpleUserMapper.scala @@ -20,7 +20,7 @@ class SimpleUserMapper( @Inject() def this(configuration: Configuration, ec: ExecutionContext) = this( - configuration.getOptional[String]("auth.sso.attributes.login").getOrElse("sub"), + configuration.getOptional[String]("auth.sso.attributes.login").getOrElse("login"), configuration.getOptional[String]("auth.sso.attributes.name").getOrElse("name"), configuration.getOptional[String]("auth.sso.attributes.roles"), configuration.getOptional[Seq[String]]("auth.sso.defaultRoles").getOrElse(Seq()), @@ -37,7 +37,8 @@ class SimpleUserMapper( } yield Fields(Json.obj("login" → login, "name" → name, "roles" → roles)) fields match { case JsSuccess(f, _) ⇒ Future.successful(f) - case JsError(errors) ⇒ Future.failed(AuthenticationError(s"User info fails: ${errors.map(_._2).map(_.map(_.messages.mkString(", ")).mkString("; ")).mkString}")) + case JsError(errors) ⇒ + Future.failed(AuthenticationError(s"User info fails: ${errors.map(_._2).map(_.map(_.messages.mkString(", ")).mkString("; ")).mkString}")) } } } diff --git a/thehive-backend/conf/routes b/thehive-backend/conf/routes index f0acbc4687..420300c830 100644 --- a/thehive-backend/conf/routes +++ b/thehive-backend/conf/routes @@ -7,6 +7,7 @@ GET /api/status controllers.StatusCtrl.get GET /api/health controllers.StatusCtrl.health GET /api/logout controllers.AuthenticationCtrl.logout() POST /api/login controllers.AuthenticationCtrl.login() +GET /api/ssoLogin controllers.AuthenticationCtrl.ssoLogin() POST /api/ssoLogin controllers.AuthenticationCtrl.ssoLogin() POST /api/_search controllers.SearchCtrl.find() diff --git a/thehive-cortex/app/connectors/cortex/CortexConnector.scala b/thehive-cortex/app/connectors/cortex/CortexConnector.scala index 11ea0e0856..7d25dd9749 100644 --- a/thehive-cortex/app/connectors/cortex/CortexConnector.scala +++ b/thehive-cortex/app/connectors/cortex/CortexConnector.scala @@ -1,13 +1,12 @@ package connectors.cortex -import play.api.libs.concurrent.AkkaGuiceSupport -import play.api.{Configuration, Environment, Logger} - import connectors.ConnectorModule import connectors.cortex.controllers.CortexCtrl import connectors.cortex.services.JobReplicateActor +import play.api.Logger +import play.api.libs.concurrent.AkkaGuiceSupport -class CortexConnector(environment: Environment, configuration: Configuration) extends ConnectorModule with AkkaGuiceSupport { +class CortexConnector extends ConnectorModule with AkkaGuiceSupport { private[CortexConnector] lazy val logger = Logger(getClass) override def configure() { diff --git a/thehive-cortex/app/connectors/cortex/controllers/CortexCtrl.scala b/thehive-cortex/app/connectors/cortex/controllers/CortexCtrl.scala index 64591954ed..c0c463a689 100644 --- a/thehive-cortex/app/connectors/cortex/controllers/CortexCtrl.scala +++ b/thehive-cortex/app/connectors/cortex/controllers/CortexCtrl.scala @@ -3,21 +3,18 @@ package connectors.cortex.controllers import scala.concurrent.duration.{DurationInt, FiniteDuration} import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success} - import play.api.{Configuration, Logger} import play.api.http.Status import play.api.libs.json.{JsObject, Json} import play.api.mvc._ -import play.api.routing.SimpleRouter +import play.api.routing.{Router, SimpleRouter} import play.api.routing.sird.{DELETE, GET, PATCH, POST, UrlContext} - import akka.actor.ActorSystem import connectors.Connector import connectors.cortex.models.JsonFormat.{analyzerFormat, responderFormat} import connectors.cortex.services.{CortexActionSrv, CortexAnalyzerSrv, CortexConfig} import javax.inject.{Inject, Singleton} import models.{HealthStatus, Roles} - import org.elastic4play.controllers.{Authenticated, Fields, FieldsBodyParser, Renderer} import org.elastic4play.models.JsonFormat.baseModelEntityWrites import org.elastic4play.services.JsonFormat.{aggReads, queryReads} @@ -114,7 +111,7 @@ class CortexCtrl( override def health: HealthStatus.Type = _health - val router = SimpleRouter { + val router: Router = SimpleRouter { case POST(p"/job") ⇒ createJob case GET(p"/job/$jobId<[^/]*>") ⇒ getJob(jobId) case POST(p"/job/_search") ⇒ findJob @@ -194,33 +191,33 @@ class CortexCtrl( } @Timed - def getAnalyzer(analyzerId: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def getAnalyzer(analyzerId: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ cortexAnalyzerSrv.getAnalyzer(analyzerId).map { analyzer ⇒ renderer.toOutput(OK, analyzer) } } @Timed - def getAnalyzerFor(dataType: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def getAnalyzerFor(dataType: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ cortexAnalyzerSrv.getAnalyzersFor(dataType).map { analyzers ⇒ renderer.toOutput(OK, analyzers) } } @Timed - def listAnalyzer: Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def listAnalyzer: Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ cortexAnalyzerSrv.listAnalyzer.map { analyzers ⇒ renderer.toOutput(OK, analyzers) } } - def getResponder(responderId: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def getResponder(responderId: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ cortexActionSrv.getResponderById(responderId).map { responder ⇒ renderer.toOutput(OK, responder) } } - def getResponders(entityType: String, entityId: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def getResponders(entityType: String, entityId: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ cortexActionSrv.findResponderFor(entityType, entityId).map { responders ⇒ renderer.toOutput(OK, responders) } @@ -265,7 +262,7 @@ class CortexCtrl( renderer.toOutput(OK, actions, total) } - def getAction(actionId: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def getAction(actionId: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ cortexActionSrv.getAction(actionId).map { action ⇒ renderer.toOutput(OK, action) } diff --git a/thehive-cortex/app/connectors/cortex/controllers/ReportTemplateCtrl.scala b/thehive-cortex/app/connectors/cortex/controllers/ReportTemplateCtrl.scala index 51ff1bf8d5..8c36a0e14d 100644 --- a/thehive-cortex/app/connectors/cortex/controllers/ReportTemplateCtrl.scala +++ b/thehive-cortex/app/connectors/cortex/controllers/ReportTemplateCtrl.scala @@ -1,29 +1,26 @@ package connectors.cortex.controllers -import javax.inject.{Inject, Singleton} - -import scala.collection.JavaConverters._ -import scala.concurrent.{ExecutionContext, Future} -import scala.io.Source -import scala.util.control.NonFatal - import akka.stream.Materializer import akka.stream.scaladsl.Sink +import connectors.cortex.services.ReportTemplateSrv +import javax.inject.{Inject, Singleton} +import models.Roles +import net.lingala.zip4j.ZipFile +import net.lingala.zip4j.model.FileHeader +import org.elastic4play.controllers._ +import org.elastic4play.models.JsonFormat.baseModelEntityWrites +import org.elastic4play.services.JsonFormat.queryReads +import org.elastic4play.services.{AuxSrv, QueryDSL, QueryDef} +import org.elastic4play.{BadRequestError, Timed} import play.api.Logger import play.api.http.Status import play.api.libs.json.{JsBoolean, JsFalse, JsObject, JsTrue} import play.api.mvc._ -import org.elastic4play.{BadRequestError, Timed} -import org.elastic4play.controllers._ -import org.elastic4play.models.JsonFormat.baseModelEntityWrites -import org.elastic4play.services.{QueryDSL, QueryDef} -import org.elastic4play.services.AuxSrv -import org.elastic4play.services.JsonFormat.queryReads -import connectors.cortex.services.ReportTemplateSrv -import models.Roles -import net.lingala.zip4j.core.ZipFile -import net.lingala.zip4j.model.FileHeader +import scala.collection.JavaConverters._ +import scala.concurrent.{ExecutionContext, Future} +import scala.io.Source +import scala.util.control.NonFatal @Singleton class ReportTemplateCtrl @Inject()( @@ -48,14 +45,14 @@ class ReportTemplateCtrl @Inject()( } @Timed - def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ reportTemplateSrv .get(id) .map(reportTemplate ⇒ renderer.toOutput(OK, reportTemplate)) } @Timed - def getContent(analyzerId: String, reportType: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def getContent(analyzerId: String, reportType: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ import org.elastic4play.services.QueryDSL._ val (reportTemplates, total) = reportTemplateSrv.find(and("analyzerId" ~= analyzerId, "reportType" ~= reportType), Some("0-1"), Nil) total.foreach { t ⇒ diff --git a/thehive-cortex/app/connectors/cortex/models/Artifact.scala b/thehive-cortex/app/connectors/cortex/models/Artifact.scala index 0118a355ed..18495d7e71 100644 --- a/thehive-cortex/app/connectors/cortex/models/Artifact.scala +++ b/thehive-cortex/app/connectors/cortex/models/Artifact.scala @@ -6,6 +6,6 @@ import akka.NotUsed import akka.stream.scaladsl.Source import akka.util.ByteString -sealed abstract class CortexArtifact(attributes: JsObject) -case class FileArtifact(data: Source[ByteString, NotUsed], attributes: JsObject) extends CortexArtifact(attributes) -case class DataArtifact(data: String, attributes: JsObject) extends CortexArtifact(attributes) +sealed abstract class CortexArtifact +case class FileArtifact(data: Source[ByteString, NotUsed], attributes: JsObject) extends CortexArtifact +case class DataArtifact(data: String, attributes: JsObject) extends CortexArtifact diff --git a/thehive-cortex/app/connectors/cortex/models/Job.scala b/thehive-cortex/app/connectors/cortex/models/Job.scala index 9e722df139..85ead46bf5 100644 --- a/thehive-cortex/app/connectors/cortex/models/Job.scala +++ b/thehive-cortex/app/connectors/cortex/models/Job.scala @@ -57,7 +57,7 @@ object Job { } class Job(model: JobModel, attributes: JsObject) extends EntityDef[JobModel, Job](model, Job.fixJobAttr(attributes)) with JobAttributes { - override def toJson = super.toJson + ("report" → report().fold[JsValue](JsObject.empty)(r ⇒ Json.parse(r))) // FIXME is parse fails (invalid report) + override def toJson: JsObject = super.toJson + ("report" → report().fold[JsValue](JsObject.empty)(r ⇒ Json.parse(r))) // FIXME is parse fails (invalid report) } case class CortexJob( diff --git a/thehive-cortex/app/connectors/cortex/models/JsonFormat.scala b/thehive-cortex/app/connectors/cortex/models/JsonFormat.scala index 8f7d068f7b..a15f1dae70 100644 --- a/thehive-cortex/app/connectors/cortex/models/JsonFormat.scala +++ b/thehive-cortex/app/connectors/cortex/models/JsonFormat.scala @@ -43,24 +43,21 @@ object JsonFormat { ) private val fileArtifactFormat = OFormat(fileArtifactReads, fileArtifactWrites) private val dataArtifactFormat = Json.format[DataArtifact] - private val artifactReads = Reads[CortexArtifact]( + implicit val artifactReads: Reads[CortexArtifact] = Reads[CortexArtifact]( json ⇒ json.validate[JsObject].flatMap { case a if a.keys.contains("data") ⇒ json.validate[DataArtifact](dataArtifactFormat) case _ ⇒ json.validate[FileArtifact](fileArtifactFormat) } ) - private val artifactWrites = OWrites[CortexArtifact] { + implicit def artifactWrites[A <: CortexArtifact]: OWrites[A] = OWrites[A] { case dataArtifact: DataArtifact ⇒ dataArtifactFormat.writes(dataArtifact) case fileArtifact: FileArtifact ⇒ fileArtifactWrites.writes(fileArtifact) } - implicit val artifactFormat: OFormat[CortexArtifact] = OFormat(artifactReads, artifactWrites) +// implicit def artifactFormat[A <: CortexArtifact]: OFormat[A] = OFormat(artifactReads, artifactWrites) implicit val jobStatusFormat: Format[JobStatus.Type] = enumFormat(JobStatus) - private def filterObject(json: JsObject, attributes: String*): JsObject = - JsObject(attributes.flatMap(a ⇒ (json \ a).asOpt[JsValue].map(a → _))) - implicit val cortexJobReads: Reads[CortexJob] = Reads[CortexJob]( json ⇒ for { @@ -68,7 +65,11 @@ object JsonFormat { analyzerId ← (json \ "workerId").orElse(json \ "analyzerId").validate[String] analyzerName = (json \ "workerName").orElse(json \ "analyzerName").validate[String].getOrElse(analyzerId) analyzerDefinition = (json \ "workerDefinitionId").orElse(json \ "analyzerDefinitionId").validate[String].getOrElse(analyzerId) - attributes = filterObject(json.as[JsObject], "tlp", "message", "parameters") + attributes = JsObject( + (json \ "tlp").asOpt[JsValue].map("tlp" -> _).toList ::: + (json \ "message").asOpt[JsValue].map("message" -> _).toList ::: + (json \ "parameters").asOpt[JsValue].map("parameters" -> _).toList + ) artifact = (json \ "artifact") .validate[CortexArtifact] .getOrElse { diff --git a/thehive-cortex/app/connectors/cortex/models/ReportTemplate.scala b/thehive-cortex/app/connectors/cortex/models/ReportTemplate.scala index 6db3e1dce6..271e885ba2 100644 --- a/thehive-cortex/app/connectors/cortex/models/ReportTemplate.scala +++ b/thehive-cortex/app/connectors/cortex/models/ReportTemplate.scala @@ -28,7 +28,7 @@ trait ReportTemplateAttributes { _: AttributeDef ⇒ class ReportTemplateModel @Inject() extends ModelDef[ReportTemplateModel, ReportTemplate]("reportTemplate", "Report template", "/connector/cortex/reportTemplate") with ReportTemplateAttributes { - override def creationHook(parent: Option[BaseEntity], attrs: JsObject) = { + override def creationHook(parent: Option[BaseEntity], attrs: JsObject): Future[JsObject] = { val maybeId = for { analyzerId ← (attrs \ "analyzerId").asOpt[String] reportType ← (attrs \ "reportType").asOpt[String] diff --git a/thehive-cortex/app/connectors/cortex/services/ActionOperation.scala b/thehive-cortex/app/connectors/cortex/services/ActionOperation.scala index e5961c91c7..451ff4da12 100644 --- a/thehive-cortex/app/connectors/cortex/services/ActionOperation.scala +++ b/thehive-cortex/app/connectors/cortex/services/ActionOperation.scala @@ -99,16 +99,16 @@ case class AssignCase(owner: String, status: ActionOperationStatus.Type = Action } object ActionOperation { - val addTagToCaseWrites = Json.writes[AddTagToCase] - val addTagToArtifactWrites = Json.writes[AddTagToArtifact] - val createTaskWrites = Json.writes[CreateTask] - val addCustomFieldsWrites = Json.writes[AddCustomFields] - val closeTaskWrites = Json.writes[CloseTask] - val markAlertAsReadWrites = Json.writes[MarkAlertAsRead] - val addLogToTaskWrites = Json.writes[AddLogToTask] - val addTagToAlertWrites = Json.writes[AddTagToAlert] - val addArtifactToCaseWrites = Json.writes[AddArtifactToCase] - val assignCaseWrites = Json.writes[AssignCase] + val addTagToCaseWrites: OWrites[AddTagToCase] = Json.writes[AddTagToCase] + val addTagToArtifactWrites: OWrites[AddTagToArtifact] = Json.writes[AddTagToArtifact] + val createTaskWrites: OWrites[CreateTask] = Json.writes[CreateTask] + val addCustomFieldsWrites: OWrites[AddCustomFields] = Json.writes[AddCustomFields] + val closeTaskWrites: OWrites[CloseTask] = Json.writes[CloseTask] + val markAlertAsReadWrites: OWrites[MarkAlertAsRead] = Json.writes[MarkAlertAsRead] + val addLogToTaskWrites: OWrites[AddLogToTask] = Json.writes[AddLogToTask] + val addTagToAlertWrites: OWrites[AddTagToAlert] = Json.writes[AddTagToAlert] + val addArtifactToCaseWrites: OWrites[AddArtifactToCase] = Json.writes[AddArtifactToCase] + val assignCaseWrites: OWrites[AssignCase] = Json.writes[AssignCase] implicit val actionOperationReads: Reads[ActionOperation] = Reads[ActionOperation]( json ⇒ (json \ "type").asOpt[String].fold[JsResult[ActionOperation]](JsError("type is missing in action operation")) { @@ -170,7 +170,7 @@ class ActionOperationSrv @Inject()( implicit val mat: Materializer ) { - lazy val logger = Logger(getClass) + lazy val logger: Logger = Logger(getClass) lazy val alertSrv: AlertSrv = alertSrvProvider.get def findCaseEntity(entity: BaseEntity): Future[Case] = { @@ -294,7 +294,7 @@ class ActionOperationSrv @Inject()( case AddArtifactToCase(data, dataType, dataMessage, _, _) ⇒ for { initialCase ← findCaseEntity(entity) - artifact ← artifactSrv.create(initialCase.id, Fields.empty.set("data", data).set("dataType", dataType).set("message", dataMessage)) + _ ← artifactSrv.create(initialCase.id, Fields.empty.set("data", data).set("dataType", dataType).set("message", dataMessage)) } yield operation.updateStatus(ActionOperationStatus.Success, "") case AssignCase(owner, _, _) ⇒ for { diff --git a/thehive-cortex/app/connectors/cortex/services/CortexAnalyzerSrv.scala b/thehive-cortex/app/connectors/cortex/services/CortexAnalyzerSrv.scala index 18cb243c54..65c49cd9e1 100644 --- a/thehive-cortex/app/connectors/cortex/services/CortexAnalyzerSrv.scala +++ b/thehive-cortex/app/connectors/cortex/services/CortexAnalyzerSrv.scala @@ -263,7 +263,6 @@ class CortexAnalyzerSrv @Inject()( cortex .getAttachment(id) .flatMap(src ⇒ src.runWith(FileIO.toPath(file))) - .flatMap(ioResult ⇒ Future.fromTry(ioResult.status)) .flatMap(_ ⇒ attachmentSrv.save(fiv)) .andThen { case _ ⇒ Files.delete(file) } .map(a ⇒ Some(artifact + ("attachment" → Json.toJson(a)))) diff --git a/thehive-cortex/app/connectors/cortex/services/ReportTemplateSrv.scala b/thehive-cortex/app/connectors/cortex/services/ReportTemplateSrv.scala index 5748388ddb..766451ece6 100644 --- a/thehive-cortex/app/connectors/cortex/services/ReportTemplateSrv.scala +++ b/thehive-cortex/app/connectors/cortex/services/ReportTemplateSrv.scala @@ -1,27 +1,22 @@ package connectors.cortex.services -import javax.inject.{Inject, Singleton} - -import scala.concurrent.{ExecutionContext, Future} -import scala.util.Try - import akka.NotUsed import akka.stream.scaladsl.Source +import connectors.cortex.models.{ReportTemplate, ReportTemplateModel} +import javax.inject.{Inject, Singleton} +import org.elastic4play.controllers.Fields +import org.elastic4play.database.ModifyConfig +import org.elastic4play.services._ import play.api.Logger import play.api.libs.json.JsObject -import org.elastic4play.controllers.Fields -import org.elastic4play.services.{Agg, AuthContext, CreateSrv, DeleteSrv, FindSrv, GetSrv, QueryDef, UpdateSrv} -import connectors.cortex.models.{ReportTemplate, ReportTemplateModel} -import services.ArtifactSrv - -import org.elastic4play.database.ModifyConfig +import scala.concurrent.{ExecutionContext, Future} +import scala.util.Try @Singleton class ReportTemplateSrv @Inject()( reportTemplateModel: ReportTemplateModel, createSrv: CreateSrv, - artifactSrv: ArtifactSrv, getSrv: GetSrv, updateSrv: UpdateSrv, deleteSrv: DeleteSrv, @@ -56,7 +51,4 @@ class ReportTemplateSrv @Inject()( findSrv[ReportTemplateModel, ReportTemplate](reportTemplateModel, queryDef, range, sortBy) def stats(queryDef: QueryDef, aggs: Seq[Agg]): Future[JsObject] = findSrv(reportTemplateModel, queryDef, aggs: _*) - - def getStats(id: String): Future[JsObject] = - Future.successful(JsObject.empty) } diff --git a/thehive-misp/app/connectors/misp/MispConnector.scala b/thehive-misp/app/connectors/misp/MispConnector.scala index a3615b8390..466ed199dd 100644 --- a/thehive-misp/app/connectors/misp/MispConnector.scala +++ b/thehive-misp/app/connectors/misp/MispConnector.scala @@ -1,14 +1,12 @@ package connectors.misp +import connectors.ConnectorModule import javax.inject.Singleton - +import play.api.Logger import play.api.libs.concurrent.AkkaGuiceSupport -import play.api.{Configuration, Environment, Logger} - -import connectors.ConnectorModule @Singleton -class MispConnector(environment: Environment, configuration: Configuration) extends ConnectorModule with AkkaGuiceSupport { +class MispConnector extends ConnectorModule with AkkaGuiceSupport { private[MispConnector] lazy val logger = Logger(getClass) override def configure() { diff --git a/thehive-misp/app/connectors/misp/MispCtrl.scala b/thehive-misp/app/connectors/misp/MispCtrl.scala index 6e5b2456ee..38ebc65c6c 100644 --- a/thehive-misp/app/connectors/misp/MispCtrl.scala +++ b/thehive-misp/app/connectors/misp/MispCtrl.scala @@ -3,20 +3,17 @@ package connectors.misp import scala.concurrent.duration.{DurationInt, FiniteDuration} import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success} - import play.api.http.Status import play.api.libs.json.{JsObject, Json} import play.api.mvc._ -import play.api.routing.SimpleRouter +import play.api.routing.{Router, SimpleRouter} import play.api.routing.sird.{GET, POST, UrlContext} import play.api.{Configuration, Logger} - import akka.actor.ActorSystem import connectors.Connector import javax.inject.{Inject, Singleton} import models.{HealthStatus, _} import services.{AlertTransformer, CaseSrv} - import org.elastic4play.JsonFormat.tryWrites import org.elastic4play.controllers.{Authenticated, Renderer} import org.elastic4play.models.JsonFormat.baseModelEntityWrites @@ -116,7 +113,7 @@ class MispCtrl( private[MispCtrl] lazy val logger = Logger(getClass) - val router = SimpleRouter { + val router: Router = SimpleRouter { case GET(p"/_syncAlerts") ⇒ syncAlerts case GET(p"/_syncAllAlerts") ⇒ syncAllAlerts case GET(p"/_syncArtifacts") ⇒ syncArtifacts diff --git a/thehive-misp/app/connectors/misp/MispExport.scala b/thehive-misp/app/connectors/misp/MispExport.scala index db7abf38a1..4e7bde9e08 100644 --- a/thehive-misp/app/connectors/misp/MispExport.scala +++ b/thehive-misp/app/connectors/misp/MispExport.scala @@ -3,31 +3,27 @@ package connectors.misp import java.text.SimpleDateFormat import java.util.Date -import javax.inject.{Inject, Provider, Singleton} -import scala.concurrent.{ExecutionContext, Future} -import scala.util.{Success, Try} - -import play.api.Logger -import play.api.libs.json._ - +import akka.stream.Materializer import akka.stream.scaladsl.Sink -import connectors.misp.JsonFormat.tlpWrites +import connectors.misp.JsonFormat.{exportedAttributeWrites, tlpWrites} +import javax.inject.{Inject, Provider, Singleton} import models.{Artifact, Case} -import services.{AlertSrv, ArtifactSrv} -import JsonFormat.exportedAttributeWrites -import akka.stream.Materializer - -import org.elastic4play.{BadRequestError, InternalError} import org.elastic4play.controllers.Fields -import org.elastic4play.services.{Attachment, AttachmentSrv, AuthContext} import org.elastic4play.services.JsonFormat.attachmentFormat +import org.elastic4play.services.{Attachment, AttachmentSrv, AuthContext} import org.elastic4play.utils.RichFuture +import org.elastic4play.{BadRequestError, InternalError} +import play.api.Logger +import play.api.libs.json._ +import services.AlertSrv + +import scala.concurrent.{ExecutionContext, Future} +import scala.util.{Success, Try} @Singleton class MispExport @Inject()( mispConfig: MispConfig, mispSrv: MispSrv, - artifactSrv: ArtifactSrv, alertSrvProvider: Provider[AlertSrv], attachmentSrv: AttachmentSrv, implicit val ec: ExecutionContext, @@ -36,7 +32,7 @@ class MispExport @Inject()( lazy val dateFormat = new SimpleDateFormat("yy-MM-dd") private[misp] lazy val alertSrv = alertSrvProvider.get - lazy val logger = Logger(getClass) + lazy val logger: Logger = Logger(getClass) def relatedMispEvent(mispName: String, caseId: String): Future[(Option[String], Option[String])] = { import org.elastic4play.services.QueryDSL._ @@ -78,7 +74,7 @@ class MispExport @Inject()( val mispEvent = Json.obj( "Event" → Json.obj( "distribution" → 0, - "threat_level_id" → (4 - severity), + "threat_level_id" → math.min(4, math.max(1, 4 - severity)), "analysis" → 0, "info" → title, "date" → dateFormat.format(date), diff --git a/thehive-misp/app/connectors/misp/MispSrv.scala b/thehive-misp/app/connectors/misp/MispSrv.scala index d2254fea26..4f176debff 100644 --- a/thehive-misp/app/connectors/misp/MispSrv.scala +++ b/thehive-misp/app/connectors/misp/MispSrv.scala @@ -2,28 +2,26 @@ package connectors.misp import java.util.Date -import javax.inject.{Inject, Provider, Singleton} - -import scala.concurrent.{ExecutionContext, Future} -import play.api.Logger -import play.api.libs.json.JsLookupResult.jsLookupResultToJsLookup -import play.api.libs.json.JsValue.jsValueToJsLookup -import play.api.libs.json.Json.toJsFieldJsValueWrapper -import play.api.libs.json._ -import play.api.libs.ws.WSBodyWritables.writeableOf_JsValue import akka.NotUsed import akka.stream.Materializer import akka.stream.scaladsl.{FileIO, Sink, Source} import connectors.misp.JsonFormat._ +import javax.inject.{Inject, Provider, Singleton} import models._ -import net.lingala.zip4j.core.ZipFile +import net.lingala.zip4j.ZipFile import net.lingala.zip4j.exception.ZipException -import net.lingala.zip4j.model.FileHeader -import services._ import org.elastic4play.controllers.{Fields, FileInputValue} import org.elastic4play.services.{Attachment, AuthContext, TempSrv} import org.elastic4play.{InternalError, NotFoundError} +import play.api.Logger +import play.api.libs.json.JsLookupResult.jsLookupResultToJsLookup +import play.api.libs.json.JsValue.jsValueToJsLookup +import play.api.libs.json.Json.toJsFieldJsValueWrapper +import play.api.libs.json._ +import play.api.libs.ws.WSBodyWritables.writeableOf_JsValue +import services._ +import scala.concurrent.{ExecutionContext, Future} import scala.util.Try @Singleton @@ -64,7 +62,7 @@ class MispSrv @Inject()( logger.debug(s"Get MISP events from $fromDate") val date = fromDate.getTime / 1000 Source - .fromFuture { + .future { mispConnection("events/index") .post(Json.obj("searchpublish_timestamp" → date)) } @@ -208,7 +206,7 @@ class MispSrv @Inject()( case Some(id) ⇒ caseSrv.get(id) case None ⇒ for { - caseTemplate ← alertSrv.getCaseTemplate(alert, customCaseTemplate) + caseTemplate ← alertSrv.getCaseTemplate(customCaseTemplate) caze ← caseSrv.create(Fields(alert.toCaseJson), caseTemplate) _ ← importArtifacts(alert, caze) } yield caze @@ -273,10 +271,10 @@ class MispSrv @Inject()( val zipFile = new ZipFile(file.filepath.toFile) if (zipFile.isEncrypted) - zipFile.setPassword("infected") + zipFile.setPassword("infected".toCharArray) // Get the list of file headers from the zip file - val fileHeaders = zipFile.getFileHeaders.asScala.toList.asInstanceOf[List[FileHeader]] + val fileHeaders = zipFile.getFileHeaders.asScala.toList val (fileNameHeaders, contentFileHeaders) = fileHeaders.partition { fileHeader ⇒ fileHeader.getFileName.endsWith(".filename.txt") } @@ -300,7 +298,7 @@ class MispSrv @Inject()( tempFile = tempSrv.newTemporaryFile("misp", "malware") _ = logger.info(s"Extract malware file ${file.filepath} in file $tempFile") - _ = zipFile.extractFile(contentFileHeader, tempFile.getParent.toString, null, tempFile.getFileName.toString) + _ = zipFile.extractFile(contentFileHeader, tempFile.getParent.toString, tempFile.getFileName.toString) } yield FileInputValue(filename, tempFile, "application/octet-stream")).getOrElse(file) } catch { case e: ZipException ⇒ @@ -325,9 +323,7 @@ class MispSrv @Inject()( response .bodyAsSource .runWith(FileIO.toPath(tempFile)) - .map { ioResult ⇒ - if (!ioResult.wasSuccessful) // throw an exception if transfer failed - throw ioResult.getError + .map { _ ⇒ val contentType = response.headers.getOrElse("Content-Type", Seq("application/octet-stream")).head val filename = response .headers diff --git a/thehive-misp/app/connectors/misp/MispSynchro.scala b/thehive-misp/app/connectors/misp/MispSynchro.scala index 95cda14517..f7150a6af2 100644 --- a/thehive-misp/app/connectors/misp/MispSynchro.scala +++ b/thehive-misp/app/connectors/misp/MispSynchro.scala @@ -35,15 +35,21 @@ class MispSynchro @Inject()( tempSrv: TempSrv, lifecycle: ApplicationLifecycle, system: ActorSystem, - implicit val ec: ExecutionContext, implicit val mat: Materializer ) { private[misp] lazy val logger = Logger(getClass) private[misp] lazy val alertSrv = alertSrvProvider.get + implicit val ec: ExecutionContext = try { + system.dispatchers.lookup("misp-thread-pools") + } catch { + case e: Throwable => + logger.warn(s"Unable to use MISP specific dispatcher ($e). Fallback to default dispatcher") + system.dispatcher + } private[misp] def initScheduler(): Unit = { - val task = system.scheduler.schedule(0.seconds, mispConfig.interval) { + val task = system.scheduler.scheduleWithFixedDelay(0.seconds, mispConfig.interval) {() => if (migrationSrv.isReady) { logger.info("Update of MISP events is starting ...") userSrv diff --git a/thehive-misp/conf/reference.conf b/thehive-misp/conf/reference.conf index 790845e182..f254618bf2 100644 --- a/thehive-misp/conf/reference.conf +++ b/thehive-misp/conf/reference.conf @@ -25,3 +25,14 @@ misp { # Interval between two MISP event import interval = 1h } + +misp-thread-pool { + fork-join-executor { + # Min number of threads available for MISP synchronization + parallelism-min = 2 + # Parallelism (threads) ... ceil(available processors * factor) + parallelism-factor = 2.0 + # Max number of threads available for MISP synchronization + parallelism-max = 4 + } +} \ No newline at end of file diff --git a/ui/app/scripts/directives/report-observables.js b/ui/app/scripts/directives/report-observables.js index 499b5bcc5d..c10f5ff534 100644 --- a/ui/app/scripts/directives/report-observables.js +++ b/ui/app/scripts/directives/report-observables.js @@ -67,22 +67,49 @@ return item.selected === true; }), 'dataType'); - var message = [ - '### Discovered from:', - '- Observable: **['+ $scope.origin.dataType + '] - ' + $filter('fang')($scope.origin.data) + '**', - '- Analyzer: **'+ $scope.analyzer + '**' - ].join('\n'); + _.each(toImport, function(list, key) { - var params = { - dataType: key, - single: list.length === 1, - ioc: false, - sighted: false, - tlp: 2, - message: message, - tags: [{text: 'src:' + $scope.analyzer}] - }; + var message = [ + '### Discovered from:', + '- Observable: **['+ $scope.origin.dataType + '] - ' + $filter('fang')($scope.origin.data) + '**', + '- Analyzer: **'+ $scope.analyzer + '**' + ]; + + var params; + + if(list.length === 1) { + var obs = list[0]; + + if(obs.message) { + message.push('- Message: ' + obs.message); + } + + params = { + dataType: key, + single: true, + ioc: false, + sighted: false, + tlp: obs.tlp || 2, + message: message.join('\n'), + tags: [{text: 'src:' + $scope.analyzer}].concat(_.map(_.uniq(obs.tags), function(i) { + return {text: i}; + })) + }; + } else { + params = { + dataType: key, + single: list.length === 1, + ioc: false, + sighted: false, + tlp: 2, + message: message.join('\n'), + tags: [{text: 'src:' + $scope.analyzer}] + }; + } + + + if(key === 'file') { params.attachment = _.pluck(list, 'attachment'); diff --git a/ui/app/views/directives/report-observables.html b/ui/app/views/directives/report-observables.html index 8f167f57a6..0f37686f21 100644 --- a/ui/app/views/directives/report-observables.html +++ b/ui/app/views/directives/report-observables.html @@ -26,7 +26,8 @@ - + + @@ -38,6 +39,7 @@ offset: (pagination.currentPage-1)*pagination.pageSize | limitTo: pagination.pageSize "> + diff --git a/ui/app/views/login.html b/ui/app/views/login.html index 18bc6d7729..7b42eede7c 100644 --- a/ui/app/views/login.html +++ b/ui/app/views/login.html @@ -22,10 +22,10 @@
    -
    -
    - -
    +
    OR
    + +
    diff --git a/ui/app/views/partials/admin/case-template/tasks.html b/ui/app/views/partials/admin/case-template/tasks.html index a09eae453c..e60f16559b 100644 --- a/ui/app/views/partials/admin/case-template/tasks.html +++ b/ui/app/views/partials/admin/case-template/tasks.html @@ -7,7 +7,7 @@

    - No tasks have been specified. + No tasks have been specified. Add a task
    diff --git a/ui/bower.json b/ui/bower.json index baf64fcae5..4da35f6d5b 100644 --- a/ui/bower.json +++ b/ui/bower.json @@ -1,6 +1,6 @@ { "name": "thehive", - "version": "3.4.2", + "version": "3.5.0-RC1", "license": "AGPL-3.0", "dependencies": { "jquery": "^3.4.1", diff --git a/ui/package.json b/ui/package.json index 4203f746c1..43d762078a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "thehive", - "version": "3.4.2", + "version": "3.5.0-RC1", "license": "AGPL-3.0", "repository": { "type": "git", diff --git a/version.sbt b/version.sbt index 06daa17521..95b3e51d97 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "3.4.2-1" +version in ThisBuild := "3.5.0-RC1-1"
    Type Data