Skip to content
Snippets Groups Projects
Commit 057f55f0 authored by Steffen Schulze's avatar Steffen Schulze
Browse files

Merge branch 'main' into 'main'

Project house-keeping, refactoring and reorganizing

See merge request eclipse/xfsc/ocm/ocm-engine!8
parents ff4c37c0 1b303a06
No related branches found
No related tags found
No related merge requests found
Showing
with 1461 additions and 1350 deletions
node_modules
.dockerignore
.editorconfig
.eslintignore
.eslintrc.*
.git
.gitattributes
.gitignore
*.md
dist
\ No newline at end of file
.gitlab-ci.yml
.lintstagedrc
.prettierignore
.prettierrc*
**/.env*
**/*.md
**/*.postman_collection.json
**/*.tsbuildinfo
**/coverage
**/deployment
**/dist
**/test
commitlint.config.cjs
Dockerfile
jest.config.*
node_modules
# Ignore everything
*
# Except these files
!*.ts
!*.d.ts
# .. also in subdirectories
!*/
# ... in these directories
!apps/**/src/*
# Explicitly ignore these locations
node_modules
apps/**/dist
compose
documentation
module.exports = {
parser: '@typescript-eslint/parser',
extends: [
'eslint:recommended',
'plugin:import/recommended',
'plugin:import/typescript',
'plugin:@typescript-eslint/recommended',
'plugin:workspaces/recommended',
'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
],
plugins: ['workspaces'],
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.eslint.json'],
},
settings: {
'import/extensions': ['.js', '.ts'],
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
typescript: {
project: 'packages/*/tsconfig.json',
alwaysTryTypes: true,
},
},
},
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-use-before-define': [
'error',
{ functions: false, classes: false, variables: true },
],
'@typescript-eslint/explicit-member-accessibility': 'error',
'no-console': 'error',
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'import/no-cycle': 'error',
'import/newline-after-import': ['error', { count: 1 }],
'import/order': [
'error',
{
groups: ['type', ['builtin', 'external'], 'parent', 'sibling', 'index'],
alphabetize: {
order: 'asc',
},
'newlines-between': 'always',
},
],
'@typescript-eslint/no-non-null-assertion': 'error',
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: false,
},
],
'no-restricted-imports': [
'error',
{
patterns: ['packages/*'],
},
],
// Do not allow const enums
// https://github.com/typescript-eslint/typescript-eslint/issues/561#issuecomment-593059472
// https://ncjamieson.com/dont-export-const-enums/
'no-restricted-syntax': [
'error',
{
selector: 'TSEnumDeclaration[const=true]',
message: "Don't declare const enums",
},
],
},
overrides: [
{
files: ['*.spec.ts', '*.e2e-spec.ts', '**/tests/**'],
env: {
jest: true,
node: true,
},
rules: {
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: true,
},
],
},
},
],
};
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Swap the comments on the following lines if you don't wish to use zero-installs
# Documentation here: https://yarnpkg.com/features/zero-installs
# !.yarn/cache
.pnp.*
node_modules
.idea
# Created by https://www.toptal.com/developers/gitignore/api/node,visualstudiocode,jetbrains+all,macos,windows,linux
# Edit at https://www.toptal.com/developers/gitignore?templates=node,visualstudiocode,jetbrains+all,macos,windows,linux
**/*.env
### JetBrains+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# compiled output
node_modules/
apps/*/node_modules
/dist
/apps/**/dist/
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### JetBrains+all Patch ###
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.
.idea/*
!.idea/codeStyles
!.idea/runConfigurations
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### Node ###
# Logs
/logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
logs/log.json
.pnpm-debug.log*
# OS
.DS_Store
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Tests
/coverage
/.nyc_output
# Bower dependency directory (https://bower.io/)
bower_components
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# node-waf configuration
.lock-wscript
# IDE - VSCode
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/node,visualstudiocode,jetbrains+all,macos,windows,linux
#Env files
**/*.env
!config/env/development.env
.idea
*.env
include:
- project: '${HELPERS_PATH}'
file: '${HELPERS_FILE}'
stages:
- lint
- test
- build
- release
- docker
- registries
- helm
- deploy-test
# Lint microservices
lint-attestation-manager:
extends: .lint-attestation-manager
stage: lint
lint-connection-manager:
extends: .lint-connection-manager
stage: lint
lint-proof-manager:
extends: .lint-proof-manager
stage: lint
lint-ssi-abstraction:
extends: .lint-ssi-abstraction
stage: lint
# Test microservices
test-attestation-manager:
extends: .test-attestation-manager
stage: test
test-connection-manager:
extends: .test-connection-manager
stage: test
test-proof-manager:
extends: .test-proof-manager
stage: test
test-ssi-abstraction:
extends: .test-ssi-abstraction
stage: test
# Bare microservice build
build-attestation-manager:
extends: .build-attestation-manager
stage: build
build-connection-manager:
extends: .build-connection-manager
stage: build
build-proof-manager:
extends: .build-proof-manager
stage: build
build-ssi-abstraction:
extends: .build-ssi-abstraction
stage: build
# Docker build microservices
docker-attestation-manager:
extends: .docker-attestation-manager
stage: docker
docker-connection-manager:
extends: .docker-connection-manager
stage: docker
docker-proof-manager:
extends: .docker-proof-manager
stage: docker
docker-ssi-abstraction:
extends: .docker-ssi-abstraction
stage: docker
# Push to registries
registry-attestation-manager:
extends: .registry-attestation-manager
stage: registries
registry-connection-manager:
extends: .registry-connection-manager
stage: registries
registry-proof-manager:
extends: .registry-proof-manager
stage: registries
registry-ssi-abstraction:
extends: .registry-ssi-abstraction
stage: registries
# Configure helm
helm-attestation-manager:
extends: .helm-attestation-manager
stage: helm
helm-connection-manager:
extends: .helm-connection-manager
stage: helm
helm-proof-manager:
extends: .helm-proof-manager
stage: helm
helm-ssi-abstraction:
extends: .helm-ssi-abstraction
stage: helm
deploy attestation ocm:
extends: .deploy-attestation-manager-ocm-main
stage: deploy-test
deploy attestation ocm tagged:
extends: .deploy-attestation-manager-ocm-main-tag
stage: deploy-test
deploy attestation ocm test:
extends: .deploy-attestation-manager-ocm-test
stage: deploy-test
deploy attestation ocm test tagged:
extends: .deploy-attestation-manager-ocm-test-tag
stage: deploy-test
deploy connection ocm:
extends: .deploy-connection-manager-ocm-main
stage: deploy-test
deploy connection ocm tagged:
extends: .deploy-connection-manager-ocm-main-tag
stage: deploy-test
deploy connection ocm test:
extends: .deploy-connection-manager-ocm-test
stage: deploy-test
deploy connection ocm test tagged:
extends: .deploy-connection-manager-ocm-test-tag
stage: deploy-test
deploy proof ocm:
extends: .deploy-proof-manager-ocm-main
stage: deploy-test
deploy proof ocm tagged:
extends: .deploy-proof-manager-ocm-main-tag
stage: deploy-test
deploy proof ocm test:
extends: .deploy-proof-manager-ocm-test
stage: deploy-test
deploy proof ocm test tagged:
extends: .deploy-proof-manager-ocm-test-tag
stage: deploy-test
deploy ssi-abstraction ocm:
extends: .deploy-ssi-abstraction-ocm-main
stage: deploy-test
deploy ssi-abstraction ocm tagged:
extends: .deploy-ssi-abstraction-ocm-main-tag
stage: deploy-test
deploy ssi-abstraction ocm test:
extends: .deploy-ssi-abstraction-ocm-test
stage: deploy-test
deploy ssi-abstraction ocm test tagged:
extends: .deploy-ssi-abstraction-ocm-test-tag
stage: deploy-test
commit lint:
extends: .commit-lint
stage: lint
changelog:
extends: .changelog
stage: release
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no -- commitlint --edit ${1}
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm lint-staged
{
"apps/**/*.ts": ["npm run lint", "npm run format"]
}
# Ignore everything
*
# Except for these files
!*.ts
!*.d.ts
!jest.config.js
# .. also in subdirectories
!*/
# ... in these ones
!apps/**/src/*
# Explicitly ignore these locations
node_modules
apps/**/dist
compose
documentation
# Ignore ssi-abstraction for now
apps/ssi-abstraction
{
"singleQuote": true,
"trailingComma": "all"
}
\ No newline at end of file
}
.swcrc 0 → 100644
{
"jsc": {
"preserveAllComments": true,
"parser": {
"syntax": "typescript",
"tsx": false,
"decorators": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
},
"target": "es2022"
},
"module": {
"type": "es6"
},
"sourceMaps": true,
"exclude": ["__tests__/.*.ts"]
}
# Base
FROM node:20 AS base
ARG APP_HOME=/home/node/app
ARG SERVICE
WORKDIR ${APP_HOME}
RUN corepack enable
# # libindy build
# FROM node:20-bullseye AS ssi-base
# RUN apt-get update \
# && apt-get install -y --no-install-recommends libsodium-dev libzmq3-dev
# RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain '1.58.0'
# RUN git clone https://github.com/hyperledger/indy-sdk
# RUN cd indy-sdk/libindy && ~/.cargo/bin/cargo build --release
# RUN cd indy-sdk/libindy && mv target/release/libindy.so /usr/lib/libindy.so
# Build
FROM base AS build
ARG APP_HOME=/home/node/app
WORKDIR ${APP_HOME}
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig*.json .swcrc ./
COPY apps/${SERVICE}/package.json ./apps/${SERVICE}/
COPY apps/shared/package.json ./apps/shared/
RUN pnpm install --frozen-lockfile
COPY apps/${SERVICE} ./apps/${SERVICE}
COPY apps/shared ./apps/shared
RUN pnpm --filter shared build
RUN pnpm --filter ${SERVICE} build:production
RUN pnpm --filter ${SERVICE} --prod deploy build
RUN pnpm --filter shared --prod deploy shared
# This is a way of keeping the generated prisma client in the build folder
RUN if [ -d ./apps/${SERVICE}/node_modules/\@prisma/client ]; then \
GLOBAL_PRISMA_SETUP=`realpath ./apps/${SERVICE}/node_modules/\@prisma/client` \
GLOBAL_PRISMA_CLIENT=`readlink -f ${GLOBAL_PRISMA_SETUP}/../../.prisma` \
BUILD_PRISMA_SETUP=`realpath ./build/node_modules/\@prisma/client` \
BUILD_PRISMA_CLIENT=`readlink -f ${BUILD_PRISMA_SETUP}/../..` \
sh -c 'cp -r $GLOBAL_PRISMA_CLIENT $BUILD_PRISMA_CLIENT'; \
fi
# Final
FROM node:20 AS final
ARG APP_HOME=/home/node/app
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
WORKDIR ${APP_HOME}
ENTRYPOINT ["./docker-entrypoint.sh"]
CMD ["node", "dist/main.js"]
COPY --chown=node:node ./docker-entrypoint.sh ./docker-entrypoint.sh
COPY --from=build --chown=node:node ${APP_HOME}/build/dist ./dist
COPY --from=build --chown=node:node ${APP_HOME}/shared/dist ./shared
COPY --from=build --chown=node:node ${APP_HOME}/build/node_modules ./node_modules
COPY --from=build --chown=node:node ${APP_HOME}/build/package.json .
# Cut unnecessary stuff from package.json. Only leave name, version and module type
RUN node -e "\
const { name, version, type } = JSON.parse(fs.readFileSync('./package.json', 'utf-8'));\
fs.writeFileSync('./package.json', JSON.stringify({ name, version, type }, null, 2));\
"
# USER node
# ocm-engine version 1
#### Dependencies
Node 12
Python 2.5.0 >= <3.0.0
pnpm
pnpm
### Setup local
......@@ -13,13 +14,12 @@ pnpm
app options: attestation, connection, principal, ssi
### Docker compose
### Docker compose
1. Go to compose dir
1. Go to compose dir
2. docker-compose up
## Example Flows (OCM Usage)
Please refer to [OCM-flow-overview](documentation/ocm-flow-overview.md)
Dockerfile
Jenkinsfile
coverage
docker-compose.yml
docs
node_modules
yarn-error.log
*.md
!README.md
.circle*
.codecov*
.coveralls*
.dockerignore
.drone*
.editorconfig
# .env
.git*
.huskyrc*
.lintstagedrc*
.npmignore
.prettierrc*
dist
\ No newline at end of file
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'airbnb-base',
'airbnb-typescript/base'
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
"@typescript-eslint/ban-ts-comment": "off",
"prefer-spread": "off"
},
};
{
"singleQuote": true,
"trailingComma": "all"
}
\ No newline at end of file
# GDPR Compliance Document
The objective of this document is to detail, the data being stored and proccessed by the Organization Credential Manager's, Attestation Manger.
## What information is stored
### Source User Information
The Open Id connect claims that MAY contain all sorts of personal data (like email, name, age and others), are received from any external source.
### Technical User Information (Public)
......@@ -15,18 +18,25 @@ The Open Id connect claims that MAY contain all sorts of personal data (like ema
- Offered credential attributes and attachments
## How is the information stored
### Source User Information
Source User Information is encrypted using the Private Key of the Organizations SSI Agent and stored until the issuance of credential in Organization's SSI Agent's PostgreSQL database.
### Technical User Information (Public)
Technical User Information is encrypted using the Private Key of the Organizations SSI Agent and stored internally (on the agent) on PostgreSQL and externally/ metadata (shared between the OCM services) on PostgreSQL of Organization.
## Who can access the information
The Source User Information and Technical User Information both are accessible only by the Organization specific SSI agent's private key.
## How long will the information stay
## How long will the information stay
### Source User Information
The Source User Information is wiped out once the credential is issued.
### Technical User Information (Public)
The Technical User Information is wiped out according to the retention periods (not defined yet).
# OCM Attestation Manager
## Description
<hr/>
The Attestation Manager is the microservice responsible for handling the features related to Issuance of Credentials. It handles REST endpoints for Schemas, Credential Definitions and Verifiable Credentials.
## Usage
<hr/>
### Swagger Documentation:
### Swagger Documentation:
[Swagger/OpenAPI](swagger.json)
## Installation
<hr/>
### Pre-requisite
* yarn
* docker
* docker-compose
* PostgreSQL
- pnpm
- docker
- docker-compose
- PostgreSQL
### OCM Services Dependencies
* SSI Abstraction
* Connection Manager
- SSI Abstraction
- Connection Manager
## Running the app
......@@ -40,29 +43,36 @@ The Attestation Manager is the microservice responsible for handling the feature
./deployment/dev
```
* (optional) Edit docker-compose.yml in "infrastructure" to use either **/ci/** or **/dev/** Dockerfiles.
- (optional) Edit docker-compose.yml in "infrastructure" to use either **/ci/** or **/dev/** Dockerfiles.
- Run while in **"infrastructure"** project:
* Run while in **"infrastructure"** project:
```bash
$ docker-compose up --build attestation-m
```
to run only Attestation Manager or
```bash
$ docker-compose up --build
```
to run all the services.
## Build
```
yarn build
pnpm build
```
## Run
```
yarn start
pnpm start
```
### Environment Variables Required
```
1. PORT
2. DATABASE_URL
......@@ -72,16 +82,19 @@ yarn start
```
### Outgoing communication services
```
1. SSI Abstraction
```
### Incomming communication services
```
1. Principal Manager
```
### Features supported
```
1. Create Schema
2. Create Credential Definition
......@@ -90,33 +103,35 @@ yarn start
5. Accept Credential
```
## Test
<hr/>
```bash
# unit tests
$ npm run test
$ pnpm test
# e2e tests
$ npm run test:e2e
$ pnpm test:e2e
# test coverage
$ npm run test:cov
$ pnpm test:cov
```
## GDPR
<hr/>
[GDPR](GDPR.md)
## Dependencies
<hr/>
[Dependencies](package.json)
## License
<hr/>
[Apache 2.0 license](LICENSE)
FROM node:16-slim AS builder
RUN apt-get update
RUN apt-get install -y openssl
WORKDIR /app
RUN npm i -g pnpm
COPY . .
RUN pnpm install
RUN pnpm -F attestation-manager prisma:generate
RUN pnpm -F attestation-manager build
FROM node:16-slim
RUN apt-get update
RUN apt-get install -y openssl
ENV PATH /usr/src/app/node_modules/.bin:$PATH
WORKDIR /usr/src/app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/start.sh ./start.sh
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/src/prisma prisma
EXPOSE 3005
RUN chmod +x ./start.sh
CMD ["./start.sh"]
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment