mihari 5.2.3 → 5.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/build_frontend.sh +1 -9
  3. data/frontend/.browserslistrc +3 -0
  4. data/frontend/.eslintrc.js +33 -0
  5. data/frontend/.gitignore +25 -0
  6. data/frontend/README.md +3 -0
  7. data/frontend/babel.config.js +3 -0
  8. data/frontend/index.html +21 -0
  9. data/frontend/jest.config.js +9 -0
  10. data/frontend/package-lock.json +13216 -0
  11. data/frontend/package.json +54 -0
  12. data/frontend/public/favicon.ico +0 -0
  13. data/frontend/scripts/swagger_doc_to_yaml.rb +23 -0
  14. data/frontend/src/App.vue +27 -0
  15. data/frontend/src/api-helper.ts +113 -0
  16. data/frontend/src/api.ts +105 -0
  17. data/frontend/src/components/ErrorMessage.vue +32 -0
  18. data/frontend/src/components/Loading.vue +15 -0
  19. data/frontend/src/components/Navbar.vue +59 -0
  20. data/frontend/src/components/Pagination.vue +126 -0
  21. data/frontend/src/components/alert/Alert.vue +92 -0
  22. data/frontend/src/components/alert/Alerts.vue +66 -0
  23. data/frontend/src/components/alert/AlertsWithPagination.vue +91 -0
  24. data/frontend/src/components/alert/AlertsWrapper.vue +141 -0
  25. data/frontend/src/components/alert/Form.vue +185 -0
  26. data/frontend/src/components/artifact/AS.vue +29 -0
  27. data/frontend/src/components/artifact/Artifact.vue +321 -0
  28. data/frontend/src/components/artifact/ArtifactTag.vue +70 -0
  29. data/frontend/src/components/artifact/ArtifactTags.vue +29 -0
  30. data/frontend/src/components/artifact/ArtifactWrapper.vue +62 -0
  31. data/frontend/src/components/artifact/CPEs.vue +23 -0
  32. data/frontend/src/components/artifact/DnsRecords.vue +38 -0
  33. data/frontend/src/components/artifact/Ports.vue +23 -0
  34. data/frontend/src/components/artifact/ReverseDnsNames.vue +31 -0
  35. data/frontend/src/components/artifact/Tags.vue +29 -0
  36. data/frontend/src/components/artifact/WhoisRecord.vue +49 -0
  37. data/frontend/src/components/config/Configs.vue +68 -0
  38. data/frontend/src/components/config/ConfigsWrapper.vue +40 -0
  39. data/frontend/src/components/link/Link.vue +32 -0
  40. data/frontend/src/components/link/Links.vue +47 -0
  41. data/frontend/src/components/rule/EditRule.vue +74 -0
  42. data/frontend/src/components/rule/EditRuleWrapper.vue +56 -0
  43. data/frontend/src/components/rule/Form.vue +160 -0
  44. data/frontend/src/components/rule/InputForm.vue +80 -0
  45. data/frontend/src/components/rule/NewRule.vue +60 -0
  46. data/frontend/src/components/rule/Rule.vue +108 -0
  47. data/frontend/src/components/rule/RuleWrapper.vue +62 -0
  48. data/frontend/src/components/rule/Rules.vue +88 -0
  49. data/frontend/src/components/rule/RulesWrapper.vue +130 -0
  50. data/frontend/src/components/rule/YAML.vue +47 -0
  51. data/frontend/src/components/tag/Tag.vue +73 -0
  52. data/frontend/src/components/tag/Tags.vue +37 -0
  53. data/frontend/src/countries.ts +350 -0
  54. data/frontend/src/index.ts +23 -0
  55. data/frontend/src/links/anyrun.ts +19 -0
  56. data/frontend/src/links/base.ts +14 -0
  57. data/frontend/src/links/censys.ts +20 -0
  58. data/frontend/src/links/crtsh.ts +20 -0
  59. data/frontend/src/links/dnslytics.ts +38 -0
  60. data/frontend/src/links/greynoise.ts +20 -0
  61. data/frontend/src/links/index.ts +40 -0
  62. data/frontend/src/links/intezer.ts +20 -0
  63. data/frontend/src/links/otx.ts +33 -0
  64. data/frontend/src/links/securitytrails.ts +38 -0
  65. data/frontend/src/links/shodan.ts +20 -0
  66. data/frontend/src/links/urlscan.ts +50 -0
  67. data/frontend/src/links/virustotal.ts +72 -0
  68. data/frontend/src/main.ts +11 -0
  69. data/frontend/src/router/index.ts +57 -0
  70. data/frontend/src/rule.ts +14 -0
  71. data/frontend/src/shims-vue.d.ts +6 -0
  72. data/frontend/src/swagger.yaml +737 -0
  73. data/frontend/src/types.ts +188 -0
  74. data/frontend/src/utils.ts +60 -0
  75. data/frontend/src/views/Alerts.vue +20 -0
  76. data/frontend/src/views/Artifact.vue +44 -0
  77. data/frontend/src/views/Configs.vue +20 -0
  78. data/frontend/src/views/EditRule.vue +44 -0
  79. data/frontend/src/views/NewRule.vue +26 -0
  80. data/frontend/src/views/Rule.vue +44 -0
  81. data/frontend/src/views/Rules.vue +20 -0
  82. data/frontend/tests/unit/utils.spec.ts +7 -0
  83. data/frontend/tsconfig.json +40 -0
  84. data/frontend/vite.config.js +24 -0
  85. data/lefthook.yml +10 -0
  86. data/lib/mihari/analyzers/base.rb +22 -5
  87. data/lib/mihari/analyzers/rule.rb +8 -29
  88. data/lib/mihari/commands/search.rb +16 -7
  89. data/lib/mihari/entities/rule.rb +1 -1
  90. data/lib/mihari/entities/tag.rb +1 -1
  91. data/lib/mihari/schemas/analyzer.rb +2 -7
  92. data/lib/mihari/schemas/rule.rb +1 -1
  93. data/lib/mihari/structs/config.rb +39 -16
  94. data/lib/mihari/structs/rule.rb +1 -1
  95. data/lib/mihari/version.rb +1 -1
  96. data/lib/mihari/web/public/assets/index-ac4e5ffa.js +50 -0
  97. data/lib/mihari/web/public/index.html +1 -1
  98. data/mihari.gemspec +5 -5
  99. metadata +97 -16
  100. data/.gitmodules +0 -0
  101. data/.overcommit.yml +0 -12
  102. data/lib/mihari/web/public/assets/index-cbe1734c.js +0 -50
@@ -0,0 +1,188 @@
1
+ export interface Pagination {
2
+ total: number;
3
+ currentPage: number;
4
+ pageSize: number;
5
+ }
6
+
7
+ export interface ConfigValue {
8
+ key: string;
9
+ value: string | null;
10
+ }
11
+
12
+ export interface Config {
13
+ name: string;
14
+ isConfigured: boolean;
15
+ values: ConfigValue[];
16
+ type: string;
17
+ }
18
+
19
+ export interface Tag {
20
+ name: string;
21
+ }
22
+
23
+ export interface Tags {
24
+ tags: string[];
25
+ }
26
+
27
+ export interface RuleSet {
28
+ ruleIds: string[];
29
+ }
30
+
31
+ export interface DnsRecord {
32
+ resource: string;
33
+ value: string;
34
+ }
35
+
36
+ export interface Contact {
37
+ name: string | null;
38
+ organization: string | null;
39
+ }
40
+
41
+ export interface Registrar {
42
+ name: string | null;
43
+ organization: string | null;
44
+ }
45
+
46
+ export interface WhoisRecord {
47
+ createdOn: Date | null;
48
+ updatedOn: Date | null;
49
+ expiresOn: Date | null;
50
+ registrar: Registrar | null;
51
+ contacts: Contact[];
52
+ }
53
+
54
+ export interface AutonomousSystem {
55
+ asn: number;
56
+ }
57
+
58
+ export interface Geolocation {
59
+ country: string;
60
+ countryCode: string;
61
+ }
62
+
63
+ export interface ReverseDnsName {
64
+ name: string;
65
+ }
66
+
67
+ export interface CPE {
68
+ cpe: string;
69
+ }
70
+
71
+ export interface Port {
72
+ port: string;
73
+ }
74
+
75
+ export interface Artifact {
76
+ id: string;
77
+ data: string;
78
+ dataType: string;
79
+ source: string;
80
+ metadata: unknown | null;
81
+ createdAt: string;
82
+
83
+ autonomousSystem: AutonomousSystem | null;
84
+ whoisRecord: WhoisRecord | null;
85
+ geolocation: Geolocation | null;
86
+
87
+ dnsRecords: DnsRecord[] | null;
88
+ reverseDnsNames: ReverseDnsName[] | null;
89
+ cpes: CPE[] | null;
90
+ ports: Port[] | null;
91
+ }
92
+
93
+ export interface ArtifactWithTags extends Artifact {
94
+ tags: string[];
95
+ }
96
+
97
+ export interface Alert {
98
+ id: string;
99
+ ruleId: string;
100
+ createdAt: string;
101
+
102
+ tags: Tag[];
103
+ artifacts: Artifact[];
104
+ }
105
+
106
+ export interface Alerts extends Pagination {
107
+ alerts: Alert[];
108
+ }
109
+
110
+ export interface PaginationParams {
111
+ page: number | undefined;
112
+ }
113
+
114
+ export interface AlertSearchParams extends PaginationParams {
115
+ artifact: string | undefined;
116
+ ruleId: string | undefined;
117
+ tag: string | undefined;
118
+ fromAt: string | undefined;
119
+ toAt: string | undefined;
120
+ }
121
+
122
+ export interface IPInfo {
123
+ ip: string;
124
+ hostname: string | null;
125
+ loc: string;
126
+ countryCode: string;
127
+ asn: string;
128
+ }
129
+
130
+ export interface GCS {
131
+ lat: number;
132
+ long: number;
133
+ }
134
+
135
+ export interface Country {
136
+ name: string;
137
+ code: string;
138
+ lat: number;
139
+ long: number;
140
+ }
141
+
142
+ export type LinkType = "ip" | "domain" | "url" | "hash";
143
+
144
+ export interface Link {
145
+ name: string;
146
+ type: string;
147
+ baseURL: string;
148
+ // eslint-disable-next-line no-unused-vars
149
+ href(data: string): string;
150
+ favicon(): string;
151
+ }
152
+
153
+ export interface Rule {
154
+ id: string;
155
+ title: string;
156
+ description: string;
157
+ yaml: string;
158
+ createdAt: string;
159
+ updatedAt: string;
160
+ tags: Tag[];
161
+ }
162
+
163
+ export interface CreateRule {
164
+ yaml: string;
165
+ }
166
+
167
+ export interface UpdateRule {
168
+ id: string;
169
+ yaml: string;
170
+ }
171
+
172
+ export interface Query {
173
+ analyzer: string;
174
+ query: string;
175
+ interval: null;
176
+ }
177
+
178
+ export interface Rules extends Pagination {
179
+ rules: Rule[];
180
+ }
181
+
182
+ export interface RuleSearchParams extends PaginationParams {
183
+ description: string | undefined;
184
+ tag: string | undefined;
185
+ title: string | undefined;
186
+ fromAt: string | undefined;
187
+ toAt: string | undefined;
188
+ }
@@ -0,0 +1,60 @@
1
+ import dayjs from "dayjs";
2
+ import relativeTime from "dayjs/plugin/relativeTime";
3
+ import timezone from "dayjs/plugin/timezone";
4
+ import utc from "dayjs/plugin/utc";
5
+ import { LocationQueryValue } from "vue-router";
6
+
7
+ import { getCountryByCode } from "@/countries";
8
+ import { GCS, IPInfo } from "@/types";
9
+
10
+ dayjs.extend(relativeTime);
11
+ dayjs.extend(timezone);
12
+ dayjs.extend(utc);
13
+
14
+ export function getLocalDatetime(datetime: string): string {
15
+ return dayjs(datetime).local().format("YYYY-MM-DD HH:mm:ss");
16
+ }
17
+
18
+ export function getHumanizedRelativeTime(datetime: string): string {
19
+ return dayjs(datetime).local().fromNow();
20
+ }
21
+
22
+ export function getGCSByCountryCode(countryCode: string): GCS | undefined {
23
+ const country = getCountryByCode(countryCode);
24
+ if (country !== undefined) {
25
+ return { lat: country.lat, long: country.long };
26
+ }
27
+ }
28
+
29
+ export function getGCSByIPInfo(ipinfo: IPInfo): GCS | undefined {
30
+ if (ipinfo.loc !== undefined) {
31
+ const numbers = ipinfo.loc.split(",");
32
+ if (numbers.length === 2) {
33
+ const lat = numbers[0];
34
+ const long = numbers[1];
35
+
36
+ return { lat: parseFloat(lat), long: parseFloat(long) };
37
+ }
38
+ }
39
+ return getGCSByCountryCode(ipinfo.countryCode);
40
+ }
41
+
42
+ export function normalizeQueryParam(
43
+ param:
44
+ | undefined
45
+ | null
46
+ | string
47
+ | string[]
48
+ | LocationQueryValue
49
+ | LocationQueryValue[]
50
+ ): string | undefined {
51
+ if (param === undefined || param === null) {
52
+ return undefined;
53
+ }
54
+
55
+ if (typeof param === "string") {
56
+ return param;
57
+ }
58
+
59
+ return param.toString();
60
+ }
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <Alerts></Alerts>
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { useTitle } from "@vueuse/core";
7
+ import { defineComponent } from "vue";
8
+
9
+ import Alerts from "@/components/alert/AlertsWrapper.vue";
10
+
11
+ export default defineComponent({
12
+ name: "AlertsView",
13
+ components: {
14
+ Alerts,
15
+ },
16
+ setup() {
17
+ useTitle("Alerts - Mihari");
18
+ },
19
+ });
20
+ </script>
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <Artifact :id="artifactId"></Artifact>
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { useTitle } from "@vueuse/core";
7
+ import { defineComponent, onMounted, ref, watch } from "vue";
8
+
9
+ import Artifact from "@/components/artifact/ArtifactWrapper.vue";
10
+
11
+ export default defineComponent({
12
+ name: "ArtifactView",
13
+ components: {
14
+ Artifact,
15
+ },
16
+ props: {
17
+ id: {
18
+ type: String,
19
+ required: true,
20
+ },
21
+ },
22
+ setup(props) {
23
+ const artifactId = ref<string>(props.id);
24
+
25
+ const updateTitle = () => {
26
+ useTitle(`Artifact:${artifactId.value} - Mihari`);
27
+ };
28
+
29
+ onMounted(() => {
30
+ updateTitle();
31
+ });
32
+
33
+ watch(
34
+ () => props.id,
35
+ () => {
36
+ artifactId.value = props.id;
37
+ updateTitle();
38
+ }
39
+ );
40
+
41
+ return { artifactId };
42
+ },
43
+ });
44
+ </script>
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <Configs></Configs>
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { useTitle } from "@vueuse/core";
7
+ import { defineComponent } from "vue";
8
+
9
+ import Configs from "@/components/config/ConfigsWrapper.vue";
10
+
11
+ export default defineComponent({
12
+ name: "ConfigView",
13
+ components: {
14
+ Configs,
15
+ },
16
+ setup() {
17
+ useTitle("Config - Mihari");
18
+ },
19
+ });
20
+ </script>
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <EditRule :id="id"></EditRule>
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { useTitle } from "@vueuse/core";
7
+ import { defineComponent, onMounted, ref, watch } from "vue";
8
+
9
+ import EditRule from "@/components/rule/EditRuleWrapper.vue";
10
+
11
+ export default defineComponent({
12
+ name: "EditRuleView",
13
+ components: {
14
+ EditRule,
15
+ },
16
+ props: {
17
+ id: {
18
+ type: String,
19
+ required: true,
20
+ },
21
+ },
22
+ setup(props) {
23
+ const ruleId = ref<string>(props.id);
24
+
25
+ const updateTitle = () => {
26
+ useTitle(`Edit rule:${ruleId.value} - Mihari`);
27
+ };
28
+
29
+ onMounted(() => {
30
+ updateTitle();
31
+ });
32
+
33
+ watch(
34
+ () => props.id,
35
+ () => {
36
+ ruleId.value = props.id;
37
+ updateTitle();
38
+ }
39
+ );
40
+
41
+ return { ruleId };
42
+ },
43
+ });
44
+ </script>
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <NewRule></NewRule>
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { useTitle } from "@vueuse/core";
7
+ import { defineComponent, onMounted } from "vue";
8
+
9
+ import NewRule from "@/components/rule/NewRule.vue";
10
+
11
+ export default defineComponent({
12
+ name: "NewRuleView",
13
+ components: {
14
+ NewRule,
15
+ },
16
+ setup() {
17
+ const updateTitle = () => {
18
+ useTitle(`New rule - Mihari`);
19
+ };
20
+
21
+ onMounted(() => {
22
+ updateTitle();
23
+ });
24
+ },
25
+ });
26
+ </script>
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <Rule :id="ruleId"></Rule>
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { useTitle } from "@vueuse/core";
7
+ import { defineComponent, onMounted, ref, watch } from "vue";
8
+
9
+ import Rule from "@/components/rule/RuleWrapper.vue";
10
+
11
+ export default defineComponent({
12
+ name: "RuleView",
13
+ components: {
14
+ Rule,
15
+ },
16
+ props: {
17
+ id: {
18
+ type: String,
19
+ required: true,
20
+ },
21
+ },
22
+ setup(props) {
23
+ const ruleId = ref<string>(props.id);
24
+
25
+ const updateTitle = () => {
26
+ useTitle(`Rule:${ruleId.value} - Mihari`);
27
+ };
28
+
29
+ onMounted(() => {
30
+ updateTitle();
31
+ });
32
+
33
+ watch(
34
+ () => props.id,
35
+ () => {
36
+ ruleId.value = props.id;
37
+ updateTitle();
38
+ }
39
+ );
40
+
41
+ return { ruleId };
42
+ },
43
+ });
44
+ </script>
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <Rules></Rules>
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { useTitle } from "@vueuse/core";
7
+ import { defineComponent } from "vue";
8
+
9
+ import Rules from "@/components/rule/RulesWrapper.vue";
10
+
11
+ export default defineComponent({
12
+ name: "RulesView",
13
+ components: {
14
+ Rules,
15
+ },
16
+ setup() {
17
+ useTitle("Rules - Mihari");
18
+ },
19
+ });
20
+ </script>
@@ -0,0 +1,7 @@
1
+ import { getHumanizedRelativeTime } from "@/utils";
2
+
3
+ describe("getHumanizedRelativeTime", () => {
4
+ it("returns a relative time in humanized format", () => {
5
+ expect(getHumanizedRelativeTime("1970-01-01 00:00:00")).toContain("years");
6
+ });
7
+ });
@@ -0,0 +1,40 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "esnext",
4
+ "module": "esnext",
5
+ "strict": true,
6
+ "jsx": "preserve",
7
+ "importHelpers": true,
8
+ "moduleResolution": "node",
9
+ "skipLibCheck": true,
10
+ "esModuleInterop": true,
11
+ "allowSyntheticDefaultImports": true,
12
+ "sourceMap": true,
13
+ "baseUrl": ".",
14
+ "types": [
15
+ "webpack-env",
16
+ "jest"
17
+ ],
18
+ "paths": {
19
+ "@/*": [
20
+ "src/*"
21
+ ]
22
+ },
23
+ "lib": [
24
+ "esnext",
25
+ "dom",
26
+ "dom.iterable",
27
+ "scripthost"
28
+ ]
29
+ },
30
+ "include": [
31
+ "src/**/*.ts",
32
+ "src/**/*.tsx",
33
+ "src/**/*.vue",
34
+ "tests/**/*.ts",
35
+ "tests/**/*.tsx"
36
+ ],
37
+ "exclude": [
38
+ "node_modules"
39
+ ]
40
+ }
@@ -0,0 +1,24 @@
1
+ import vue from "@vitejs/plugin-vue";
2
+ import path from "path";
3
+ import { defineConfig, loadEnv } from "vite";
4
+
5
+ export default defineConfig(({ _, mode }) => {
6
+ const env = loadEnv(mode, process.cwd(), "");
7
+ const target = env.BACKEND_URL || "http://localhost:9292/";
8
+ const port = env.port || 8080;
9
+
10
+ return {
11
+ plugins: [vue()],
12
+ server: {
13
+ port,
14
+ proxy: {
15
+ "/api": target,
16
+ },
17
+ },
18
+ resolve: {
19
+ alias: {
20
+ "@": path.resolve(__dirname, "./src"),
21
+ },
22
+ },
23
+ };
24
+ });
data/lefthook.yml ADDED
@@ -0,0 +1,10 @@
1
+ pre-commit:
2
+ parallel: true
3
+ commands:
4
+ standard:
5
+ glob: "*.rb"
6
+ run: bundle exec standardrb --fix {staged_files} && git add {staged_files}
7
+ eslint:
8
+ root: "frontend/"
9
+ glob: "*.{js,ts,vue}"
10
+ run: npx eslint --fix {staged_files} && git add {staged_files}
@@ -35,17 +35,34 @@ module Mihari
35
35
  end
36
36
  end
37
37
 
38
- # @return [String]
39
- def source
40
- self.class.to_s.split("::").last.to_s
41
- end
42
-
43
38
  # @return [String]
44
39
  def class_name
45
40
  self.class.to_s.split("::").last
46
41
  end
47
42
 
43
+ alias_method :source, :class_name
44
+
48
45
  class << self
46
+ #
47
+ # Initialize an analyzer by query params
48
+ #
49
+ # @param [Hash] params
50
+ #
51
+ # @return [Mihari::Analyzers::Base]
52
+ #
53
+ def from_query(params)
54
+ # get options and set default value as an empty hash
55
+ options = params[:options] || {}
56
+
57
+ # set interval in the top level
58
+ interval = options[:interval]
59
+ params[:interval] = interval if interval
60
+
61
+ query = params[:query]
62
+
63
+ new(query, **params)
64
+ end
65
+
49
66
  def inherited(child)
50
67
  super
51
68
  Mihari.analyzers << child
@@ -46,7 +46,7 @@ module Mihari
46
46
  #
47
47
  # @param [Mihari::Structs::Rule] rule
48
48
  #
49
- def initialize(rule:)
49
+ def initialize(rule)
50
50
  @rule = rule
51
51
  @base_time = Time.now.utc
52
52
 
@@ -153,15 +153,6 @@ module Mihari
153
153
  end
154
154
  end
155
155
 
156
- #
157
- # Deep copied queries
158
- #
159
- # @return [Array<Hash>]
160
- #
161
- def queries
162
- rule.queries.map(&:deep_dup)
163
- end
164
-
165
156
  #
166
157
  # Get analyzer class
167
158
  #
@@ -177,26 +168,13 @@ module Mihari
177
168
  end
178
169
 
179
170
  #
180
- # @return [Array<Mihari::Analyzers::Base>] <description>
171
+ # @return [Array<Mihari::Analyzers::Base>]
181
172
  #
182
173
  def analyzers
183
- @analyzers ||= queries.map do |params|
184
- analyzer_name = params[:analyzer]
174
+ @analyzers ||= rule.queries.map do |query_params|
175
+ analyzer_name = query_params[:analyzer]
185
176
  klass = get_analyzer_class(analyzer_name)
186
-
187
- # set interval in the top level
188
- options = params[:options] || {}
189
- interval = options[:interval]
190
- params[:interval] = interval if interval
191
-
192
- # set rule
193
- params[:rule] = rule
194
- query = params[:query]
195
-
196
- analyzer = klass.new(query, **params)
197
- raise ConfigurationError, "#{analyzer.source} is not configured correctly" unless analyzer.configured?
198
-
199
- analyzer
177
+ klass.from_query(query_params)
200
178
  end
201
179
  end
202
180
 
@@ -240,8 +218,9 @@ module Mihari
240
218
  # Validate configuration of analyzers
241
219
  #
242
220
  def validate_analyzer_configurations
243
- # memoize analyzers & raise ConfigurationError if there is an analyzer which is not configured
244
- analyzers
221
+ analyzers.map do |analyzer|
222
+ raise ConfigurationError, "#{analyzer.source} is not configured correctly" unless analyzer.configured?
223
+ end
245
224
  end
246
225
  end
247
226
  end