juice-shop/juice-shop

View on GitHub
frontend/src/app/Services/local-backup.service.ts

Summary

Maintainability
A
0 mins
Test Coverage
C
75%
/*
 * Copyright (c) 2014-2024 Bjoern Kimminich & the OWASP Juice Shop contributors.
 * SPDX-License-Identifier: MIT
 */

import { Injectable } from '@angular/core'
import { type Backup } from '../Models/backup.model'
import { CookieService } from 'ngx-cookie'
import { saveAs } from 'file-saver'
import { SnackBarHelperService } from './snack-bar-helper.service'
import { MatSnackBar } from '@angular/material/snack-bar'
import { forkJoin, from, of } from 'rxjs'
import { ChallengeService } from './challenge.service'

@Injectable({
  providedIn: 'root'
})
export class LocalBackupService {
  private readonly VERSION = 1

  constructor (private readonly cookieService: CookieService, private readonly challengeService: ChallengeService, private readonly snackBarHelperService: SnackBarHelperService, private readonly snackBar: MatSnackBar) { }

  save (fileName: string = 'owasp_juice_shop') {
    const backup: Backup = { version: this.VERSION }

    backup.banners = {
      welcomeBannerStatus: this.cookieService.get('welcomebanner_status') ? this.cookieService.get('welcomebanner_status') : undefined,
      cookieConsentStatus: this.cookieService.get('cookieconsent_status') ? this.cookieService.get('cookieconsent_status') : undefined
    }
    backup.language = this.cookieService.get('language') ? this.cookieService.get('language') : undefined

    const continueCode = this.challengeService.continueCode()
    const continueCodeFindIt = this.challengeService.continueCodeFindIt()
    const continueCodeFixIt = this.challengeService.continueCodeFixIt()
    forkJoin([continueCode, continueCodeFindIt, continueCodeFixIt]).subscribe(([continueCode, continueCodeFindIt, continueCodeFixIt]) => {
      backup.continueCode = continueCode
      backup.continueCodeFindIt = continueCodeFindIt
      backup.continueCodeFixIt = continueCodeFixIt
      const blob = new Blob([JSON.stringify(backup)], { type: 'text/plain;charset=utf-8' })
      saveAs(blob, `${fileName}-${new Date().toISOString().split('T')[0]}.json`)
    }, () => {
      console.log('Failed to retrieve continue code(s) for backup from server. Using cookie values as fallback.')
      backup.continueCode = this.cookieService.get('continueCode') ? this.cookieService.get('continueCode') : undefined
      backup.continueCodeFindIt = this.cookieService.get('continueCodeFindIt') ? this.cookieService.get('continueCodeFindIt') : undefined
      backup.continueCodeFixIt = this.cookieService.get('continueCodeFixIt') ? this.cookieService.get('continueCodeFixIt') : undefined
      const blob = new Blob([JSON.stringify(backup)], { type: 'text/plain;charset=utf-8' })
      saveAs(blob, `${fileName}-${new Date().toISOString().split('T')[0]}.json`)
    })
  }

  restore (backupFile: File) {
    return from(backupFile.text().then((backupData) => {
      const backup: Backup = JSON.parse(backupData)

      if (backup.version === this.VERSION) {
        this.restoreCookie('welcomebanner_status', backup.banners?.welcomeBannerStatus)
        this.restoreCookie('cookieconsent_status', backup.banners?.cookieConsentStatus)
        this.restoreCookie('language', backup.language)
        this.restoreCookie('continueCodeFindIt', backup.continueCodeFindIt)
        this.restoreCookie('continueCodeFixIt', backup.continueCodeFixIt)
        this.restoreCookie('continueCode', backup.continueCode)

        const snackBarRef = this.snackBar.open('Backup has been restored from ' + backupFile.name, 'Apply changes now', {
          duration: 10000
        })
        snackBarRef.onAction().subscribe(() => {
          const hackingProgress = backup.continueCode ? this.challengeService.restoreProgress(encodeURIComponent(backup.continueCode)) : of(true)
          const findItProgress = backup.continueCodeFindIt ? this.challengeService.restoreProgressFindIt(encodeURIComponent(backup.continueCodeFindIt)) : of(true)
          const fixItProgress = backup.continueCodeFixIt ? this.challengeService.restoreProgressFixIt(encodeURIComponent(backup.continueCodeFixIt)) : of(true)
          forkJoin([hackingProgress, findItProgress, fixItProgress]).subscribe(() => {
            location.reload()
          }, (err) => { console.log(err) })
        })
      } else {
        this.snackBarHelperService.open(`Version ${backup.version} is incompatible with expected version ${this.VERSION}`, 'errorBar')
      }
    }).catch((err: Error) => {
      this.snackBarHelperService.open(`Backup restore operation failed: ${err.message}`, 'errorBar')
    }))
  }

  private restoreCookie (cookieName: string, cookieValue: string) {
    if (cookieValue) {
      const expires = new Date()
      expires.setFullYear(expires.getFullYear() + 1)
      this.cookieService.put(cookieName, cookieValue, { expires })
    } else {
      this.cookieService.remove(cookieName)
    }
  }
}