juice-shop
207 строк · 6.2 Кб
1/*
2* Copyright (c) 2014-2024 Bjoern Kimminich & the OWASP Juice Shop contributors.
3* SPDX-License-Identifier: MIT
4*/
5
6import { CodeSnippetService, type CodeSnippet } from '../Services/code-snippet.service'
7import { CodeFixesService } from '../Services/code-fixes.service'
8import { CookieService } from 'ngx-cookie'
9import { ChallengeService } from '../Services/challenge.service'
10import { VulnLinesService, type result } from '../Services/vuln-lines.service'
11import { Component, Inject, type OnInit } from '@angular/core'
12
13import { MAT_DIALOG_DATA } from '@angular/material/dialog'
14import { UntypedFormControl } from '@angular/forms'
15import { ConfigurationService } from '../Services/configuration.service'
16import { type ThemePalette } from '@angular/material/core'
17
18enum ResultState {
19Undecided,
20Right,
21Wrong,
22}
23
24export interface Solved {
25findIt: boolean
26fixIt: boolean
27}
28
29export interface RandomFixes {
30fix: string
31index: number
32}
33
34@Component({
35selector: 'code-snippet',
36templateUrl: './code-snippet.component.html',
37styleUrls: ['./code-snippet.component.scss'],
38host: { class: 'code-snippet' }
39})
40export class CodeSnippetComponent implements OnInit {
41public snippet: CodeSnippet = null
42public fixes: string [] = null
43public selectedLines: number[]
44public selectedFix: number = 0
45public tab: UntypedFormControl = new UntypedFormControl(0)
46public lock: ResultState = ResultState.Undecided
47public result: ResultState = ResultState.Undecided
48public hint: string = null
49public explanation: string = null
50public solved: Solved = { findIt: false, fixIt: false }
51public showFeedbackButtons: boolean = true
52public randomFixes: RandomFixes[] = []
53
54constructor (@Inject(MAT_DIALOG_DATA) public dialogData: any, private readonly configurationService: ConfigurationService, private readonly codeSnippetService: CodeSnippetService, private readonly vulnLinesService: VulnLinesService, private readonly codeFixesService: CodeFixesService, private readonly challengeService: ChallengeService, private readonly cookieService: CookieService) { }
55
56ngOnInit () {
57this.configurationService.getApplicationConfiguration().subscribe((config) => {
58this.showFeedbackButtons = config.challenges.showFeedbackButtons
59}, (err) => { console.log(err) })
60
61this.codeSnippetService.get(this.dialogData.key).subscribe((snippet) => {
62this.snippet = snippet
63this.solved.findIt = false
64if (this.dialogData.codingChallengeStatus >= 1) {
65this.result = ResultState.Right
66this.lock = ResultState.Right
67this.solved.findIt = true
68}
69}, (err) => {
70this.snippet = { snippet: err.error }
71})
72this.codeFixesService.get(this.dialogData.key).subscribe((fixes) => {
73this.fixes = fixes.fixes
74if (this.fixes) {
75this.shuffle()
76}
77this.solved.fixIt = this.dialogData.codingChallengeStatus >= 2
78}, () => {
79this.fixes = null
80})
81}
82
83addLine = (lines: number[]) => {
84this.selectedLines = lines
85}
86
87setFix = (fix: number) => {
88this.selectedFix = fix
89this.explanation = null
90}
91
92changeFix (event: Event) {
93this.setFix(parseInt((event.target as HTMLSelectElement).value, 10))
94}
95
96toggleTab = (event: number) => {
97this.tab.setValue(event)
98this.result = ResultState.Undecided
99if (event === 0) {
100if (this.solved.findIt) this.result = ResultState.Right
101}
102if (event === 1) {
103if (this.solved.fixIt) this.result = ResultState.Right
104}
105}
106
107checkFix = () => {
108this.codeFixesService.check(this.dialogData.key, this.randomFixes[this.selectedFix].index).subscribe((verdict) => {
109this.setVerdict(verdict.verdict)
110this.explanation = verdict.explanation
111})
112}
113
114checkLines = () => {
115this.vulnLinesService.check(this.dialogData.key, this.selectedLines).subscribe((verdict: result) => {
116this.setVerdict(verdict.verdict)
117this.hint = verdict.hint
118})
119}
120
121lockIcon (): string {
122if (this.fixes === null) {
123return 'lock'
124}
125switch (this.lock) {
126case ResultState.Right:
127return 'lock_open'
128case ResultState.Wrong:
129return 'lock'
130case ResultState.Undecided:
131return 'lock'
132}
133}
134
135lockColor (): ThemePalette {
136switch (this.lockIcon()) {
137case 'lock_open':
138return 'accent'
139case 'lock':
140return 'warn'
141}
142}
143
144shuffle () {
145this.randomFixes = this.fixes
146.map((fix, index) => ({ fix, index, sort: Math.random() }))
147.sort((a, b) => a.sort - b.sort)
148.map(({ fix, index }) => ({ fix, index }))
149}
150
151setVerdict = (verdict: boolean) => {
152if (this.result === ResultState.Right) return
153if (verdict) {
154if (this.tab.value === 0) {
155this.solved.findIt = true
156this.challengeService.continueCodeFindIt().subscribe((continueCode) => {
157if (!continueCode) {
158throw (new Error('Received invalid continue code from the server!'))
159}
160const expires = new Date()
161expires.setFullYear(expires.getFullYear() + 1)
162this.cookieService.put('continueCodeFindIt', continueCode, { expires })
163}, (err) => { console.log(err) })
164} else {
165this.solved.fixIt = true
166this.challengeService.continueCodeFixIt().subscribe((continueCode) => {
167if (!continueCode) {
168throw (new Error('Received invalid continue code from the server!'))
169}
170const expires = new Date()
171expires.setFullYear(expires.getFullYear() + 1)
172this.cookieService.put('continueCodeFixIt', continueCode, { expires })
173}, (err) => { console.log(err) })
174}
175this.result = ResultState.Right
176this.lock = ResultState.Right
177import('../../confetti').then(module => {
178module.shootConfetti()
179})
180.then(() => {
181if (this.tab.value === 0 && this.fixes !== null) this.toggleTab(1)
182})
183} else {
184this.result = ResultState.Wrong
185}
186}
187
188resultIcon (): string {
189switch (this.result) {
190case ResultState.Right:
191return 'check'
192case ResultState.Wrong:
193return 'clear'
194default:
195return 'send'
196}
197}
198
199resultColor (): ThemePalette {
200switch (this.resultIcon()) {
201case 'check':
202return 'accent'
203case 'clear':
204return 'warn'
205}
206}
207}
208