Compare commits
15 Commits
master
...
wip-integr
Author | SHA1 | Date |
---|---|---|
Aravinth Manivannan | 455e34afe3 | |
Aravinth Manivannan | c24343be52 | |
Aravinth Manivannan | 785be9da38 | |
Aravinth Manivannan | fe189fb2e7 | |
Aravinth Manivannan | e72d8b76c4 | |
Aravinth Manivannan | 254dafbff0 | |
Aravinth Manivannan | 695b94a4d1 | |
Aravinth Manivannan | a5abfe0f04 | |
Aravinth Manivannan | 1004d540ad | |
Aravinth Manivannan | 4714bc4123 | |
Aravinth Manivannan | 1a8bc9c1ab | |
Aravinth Manivannan | caa190d99c | |
Aravinth Manivannan | b1e1a18e67 | |
Aravinth Manivannan | 759fa3f883 | |
Aravinth Manivannan | c1577f824c |
|
@ -0,0 +1,148 @@
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
databse.db
|
||||||
|
.env
|
||||||
|
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
tmp/
|
||||||
|
northstar.db
|
||||||
|
instance
|
||||||
|
northstar/static/docs/openapi
|
|
@ -153,3 +153,4 @@ cython_debug/
|
||||||
#.idea/
|
#.idea/
|
||||||
keys
|
keys
|
||||||
htmlcov/
|
htmlcov/
|
||||||
|
tmp/
|
||||||
|
|
|
@ -3,13 +3,13 @@ pipeline:
|
||||||
image: python
|
image: python
|
||||||
environment:
|
environment:
|
||||||
- DATABSE_URL=postgres://postgres:password@database:5432/postgres
|
- DATABSE_URL=postgres://postgres:password@database:5432/postgres
|
||||||
- EMAIL_URL=smtp://admin:password@localhost:10025
|
- EMAIL_URL=smtp://admin:password@smtp:10025
|
||||||
commands:
|
commands:
|
||||||
- pip install virtualenv
|
- pip install virtualenv
|
||||||
- make env
|
- make env
|
||||||
- make lint
|
# - make lint
|
||||||
- make test
|
# - make coverage
|
||||||
- make coverage
|
- make integration-test
|
||||||
secrets: [ STRIPE_PUBLIC_KEY, STRIPE_SECRET_KEY ]
|
secrets: [ STRIPE_PUBLIC_KEY, STRIPE_SECRET_KEY ]
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
@ -18,9 +18,17 @@ services:
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_PASSWORD=password
|
- POSTGRES_PASSWORD=password
|
||||||
|
|
||||||
|
# gitea:
|
||||||
|
# image: gitea/gitea:1.16.5
|
||||||
|
# container_name: hostea-dash-gitea
|
||||||
|
# # network_mode: host
|
||||||
|
# restart: always
|
||||||
|
|
||||||
smtp:
|
smtp:
|
||||||
image: maildev/maildev
|
image: maildev/maildev:latest
|
||||||
|
container_name: hostea-dash-maildev
|
||||||
environment:
|
environment:
|
||||||
- MAILDEV_SMTP_PORT=10025
|
- MAILDEV_SMTP_PORT=10025
|
||||||
|
- MAILDEV_WEB_PORT=1080
|
||||||
- MAILDEV_INCOMING_USER=admin
|
- MAILDEV_INCOMING_USER=admin
|
||||||
- MAILDEV_INCOMING_PASS=password
|
- MAILDEV_INCOMING_PASS=password
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
FROM python
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.source https://gitea.hostea.org/Hostea/dashboard
|
||||||
|
|
||||||
|
RUN useradd -ms /bin/bash -u 1001 hostea
|
||||||
|
RUN apt-get update && apt-get install -y ca-certificates git
|
||||||
|
USER hostea
|
||||||
|
|
||||||
|
WORKDIR /home/hostea
|
||||||
|
run mkdir app/
|
||||||
|
WORKDIR /home/hostea/app/
|
||||||
|
RUN pip3 install virtualenv
|
||||||
|
RUN python3 -m virtualenv venv
|
||||||
|
COPY requirements.txt .
|
||||||
|
# See https://github.com/pypa/pip/issues/9819
|
||||||
|
RUN ./venv/bin/pip install --use-feature=in-tree-build -r requirements.txt
|
||||||
|
COPY . .
|
||||||
|
#ENV . ./venv/bin/activate && make env
|
||||||
|
CMD [ "./venv/bin/python3", "manage.py", "runserver", "0.0.0.0:8000" ]
|
4
Makefile
4
Makefile
|
@ -32,12 +32,16 @@ freeze: ## Freeze python dependencies
|
||||||
help: ## Prints help for targets with comments
|
help: ## Prints help for targets with comments
|
||||||
@cat $(MAKEFILE_LIST) | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
@cat $(MAKEFILE_LIST) | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||||
|
|
||||||
|
integration-test: ## run integration tests
|
||||||
|
. ./venv/bin/activate && integration/tests.sh
|
||||||
|
|
||||||
lint: ## Run linter
|
lint: ## Run linter
|
||||||
@./venv/bin/black ./dashboard/*
|
@./venv/bin/black ./dashboard/*
|
||||||
@./venv/bin/black ./accounts/*
|
@./venv/bin/black ./accounts/*
|
||||||
@./venv/bin/black ./dash/*
|
@./venv/bin/black ./dash/*
|
||||||
@./venv/bin/black ./support/*
|
@./venv/bin/black ./support/*
|
||||||
@./venv/bin/black ./billing/*
|
@./venv/bin/black ./billing/*
|
||||||
|
@./venv/bin/black ./integration/
|
||||||
|
|
||||||
migrate: ## Run migrations
|
migrate: ## Run migrations
|
||||||
$(call run_migrations)
|
$(call run_migrations)
|
||||||
|
|
|
@ -171,7 +171,7 @@ HOSTEA = {
|
||||||
"SUDO_TTL": 60 * 5,
|
"SUDO_TTL": 60 * 5,
|
||||||
},
|
},
|
||||||
"META": {
|
"META": {
|
||||||
"GITEA_INSTANCE": "https://gitea.hostea.org", # meta Gitea insatnce
|
"GITEA_INSTANCE": "http://localhost:3000", # meta Gitea insatnce
|
||||||
"GITEA_ORG_NAME": "Hostea", # Organisation name on Hostea meta instance
|
"GITEA_ORG_NAME": "Hostea", # Organisation name on Hostea meta instance
|
||||||
# Repository dedicated for handling support
|
# Repository dedicated for handling support
|
||||||
# ref: https://gitea.hostea.org/Hostea/july-mvp/issues/17
|
# ref: https://gitea.hostea.org/Hostea/july-mvp/issues/17
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
version: "3"
|
||||||
|
|
||||||
|
#networks:
|
||||||
|
# hostea-dash-gitea:
|
||||||
|
# external: false
|
||||||
|
# hostea-dash-smtp:
|
||||||
|
# external: false
|
||||||
|
|
||||||
|
|
||||||
|
services:
|
||||||
|
#gitea:
|
||||||
|
# image: gitea/gitea:1.16.5
|
||||||
|
# container_name: hostea-dash-gitea
|
||||||
|
# network_mode: host
|
||||||
|
# environment:
|
||||||
|
# - USER_UID=1000
|
||||||
|
# - USER_GID=1000
|
||||||
|
# restart: always
|
||||||
|
# #networks:
|
||||||
|
# # - hostea-dash-gitea
|
||||||
|
# volumes:
|
||||||
|
# - /etc/timezone:/etc/timezone:ro
|
||||||
|
# - /etc/localtime:/etc/localtime:ro
|
||||||
|
# #ports:
|
||||||
|
# # - "8080:3000"
|
||||||
|
# # - "2221:22"
|
||||||
|
|
||||||
|
smtp:
|
||||||
|
image: maildev/maildev:latest
|
||||||
|
restart: always
|
||||||
|
container_name: hostea-dash-maildev
|
||||||
|
network_mode: host
|
||||||
|
#networks:
|
||||||
|
# - hostea-dash-smtp
|
||||||
|
environment:
|
||||||
|
- MAILDEV_SMTP_PORT=10025
|
||||||
|
- MAILDEV_INCOMING_USER=admin
|
||||||
|
- MAILDEV_INCOMING_PASS=password
|
||||||
|
#ports:
|
||||||
|
# - "10025:10025"
|
||||||
|
# - "1080:1080"
|
|
@ -0,0 +1,75 @@
|
||||||
|
APP_NAME = Gitea: Git with a cup of tea
|
||||||
|
RUN_USER = atm
|
||||||
|
RUN_MODE = prod
|
||||||
|
|
||||||
|
[database]
|
||||||
|
DB_TYPE = sqlite3
|
||||||
|
HOST = 127.0.0.1:3306
|
||||||
|
NAME = gitea
|
||||||
|
USER = gitea
|
||||||
|
PASSWD =
|
||||||
|
SCHEMA =
|
||||||
|
SSL_MODE = disable
|
||||||
|
CHARSET = utf8
|
||||||
|
PATH = ./tmp/gitea/db/gitea.db
|
||||||
|
LOG_SQL = false
|
||||||
|
|
||||||
|
[repository]
|
||||||
|
ROOT = ./tmp/gitea/repos/
|
||||||
|
|
||||||
|
[server]
|
||||||
|
SSH_DOMAIN = localhost
|
||||||
|
DOMAIN = localhost
|
||||||
|
HTTP_PORT = 3000
|
||||||
|
ROOT_URL = http://localhost:3000/
|
||||||
|
DISABLE_SSH = false
|
||||||
|
SSH_PORT = 2222
|
||||||
|
LFS_START_SERVER = true
|
||||||
|
LFS_JWT_SECRET = MilbUZw4BbeFsnOWBDGzYrgBINrkJIcoOPivE9IPNAQ
|
||||||
|
OFFLINE_MODE = false
|
||||||
|
|
||||||
|
[lfs]
|
||||||
|
PATH = ./tmp/gitea/lfs/
|
||||||
|
|
||||||
|
[mailer]
|
||||||
|
ENABLED = false
|
||||||
|
|
||||||
|
[service]
|
||||||
|
REGISTER_EMAIL_CONFIRM = false
|
||||||
|
ENABLE_NOTIFY_MAIL = false
|
||||||
|
DISABLE_REGISTRATION = false
|
||||||
|
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
|
||||||
|
ENABLE_CAPTCHA = false
|
||||||
|
REQUIRE_SIGNIN_VIEW = false
|
||||||
|
DEFAULT_KEEP_EMAIL_PRIVATE = false
|
||||||
|
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
|
||||||
|
DEFAULT_ENABLE_TIMETRACKING = true
|
||||||
|
NO_REPLY_ADDRESS = noreply.localhost
|
||||||
|
|
||||||
|
[picture]
|
||||||
|
DISABLE_GRAVATAR = false
|
||||||
|
ENABLE_FEDERATED_AVATAR = true
|
||||||
|
|
||||||
|
[openid]
|
||||||
|
ENABLE_OPENID_SIGNIN = true
|
||||||
|
ENABLE_OPENID_SIGNUP = true
|
||||||
|
|
||||||
|
[session]
|
||||||
|
PROVIDER = file
|
||||||
|
|
||||||
|
[log]
|
||||||
|
MODE = console
|
||||||
|
LEVEL = debug
|
||||||
|
ROOT_PATH = ./tmp/gitea/log
|
||||||
|
ROUTER = console
|
||||||
|
|
||||||
|
[repository.pull-request]
|
||||||
|
DEFAULT_MERGE_STYLE = merge
|
||||||
|
|
||||||
|
[repository.signing]
|
||||||
|
DEFAULT_TRUST_MODEL = committer
|
||||||
|
|
||||||
|
[security]
|
||||||
|
INSTALL_LOCK = true
|
||||||
|
INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE2NTQ2MDY2ODV9.WbIw4n8M_MXy594pqgmEMD3NUtTpL8hcUC_uhoSc5ec
|
||||||
|
PASSWORD_HASH_ALGO = pbkdf2
|
|
@ -0,0 +1,13 @@
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from .cli import Cli
|
||||||
|
|
||||||
|
|
||||||
|
def admin(args):
|
||||||
|
print(args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cli = Cli()
|
||||||
|
opts = cli.parse()
|
||||||
|
opts.func(opts, c=cli.c)
|
|
@ -0,0 +1,284 @@
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from requests import Session
|
||||||
|
|
||||||
|
|
||||||
|
def gitea_from_args(args, c: Session):
|
||||||
|
from .gitea import Gitea
|
||||||
|
|
||||||
|
return Gitea(
|
||||||
|
host=args.host,
|
||||||
|
username=args.username,
|
||||||
|
password=args.password,
|
||||||
|
email=args.email,
|
||||||
|
c=c,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Gitea:
|
||||||
|
def __init__(self, parser, c: Session):
|
||||||
|
self.c = c
|
||||||
|
self.parser = parser
|
||||||
|
self.subparser = self.parser.add_subparsers()
|
||||||
|
self.install()
|
||||||
|
self.register()
|
||||||
|
self.login()
|
||||||
|
self.create_repository()
|
||||||
|
self.install_sso()
|
||||||
|
|
||||||
|
def __add_credentials_parser(self, parser):
|
||||||
|
group = parser.add_argument_group("credentials", "User credentials")
|
||||||
|
group.add_argument("username", type=str, help="Gitea user's username")
|
||||||
|
group.add_argument("password", type=str, help="Gitea user's password")
|
||||||
|
group.add_argument("email", type=str, help="Gitea user's email")
|
||||||
|
group.add_argument("host", type=str, help="URI at which Gitea is running")
|
||||||
|
|
||||||
|
def install(self):
|
||||||
|
def run(args, c: Session):
|
||||||
|
gitea = gitea_from_args(args, c=c)
|
||||||
|
gitea.install()
|
||||||
|
|
||||||
|
self.install_parser = self.subparser.add_parser(
|
||||||
|
name="install", description="Install Gitea", help="Install Gitea"
|
||||||
|
)
|
||||||
|
self.__add_credentials_parser(self.install_parser)
|
||||||
|
self.install_parser.set_defaults(func=run)
|
||||||
|
|
||||||
|
def register(self):
|
||||||
|
def run(args, c: Session):
|
||||||
|
gitea = gitea_from_args(args, c=c)
|
||||||
|
gitea.register()
|
||||||
|
|
||||||
|
self.register_parser = self.subparser.add_parser(
|
||||||
|
name="register",
|
||||||
|
description="Gitea user registration",
|
||||||
|
help="Register a user on Gitea",
|
||||||
|
)
|
||||||
|
self.__add_credentials_parser(self.register_parser)
|
||||||
|
self.register_parser.set_defaults(func=run)
|
||||||
|
|
||||||
|
def login(self):
|
||||||
|
def run(args, c: Session):
|
||||||
|
gitea = gitea_from_args(args, c=c)
|
||||||
|
gitea.login()
|
||||||
|
|
||||||
|
self.login_parser = self.subparser.add_parser(
|
||||||
|
name="login", description="Gitea user login", help="Login on Gitea"
|
||||||
|
)
|
||||||
|
self.__add_credentials_parser(self.login_parser)
|
||||||
|
self.login_parser.set_defaults(func=run)
|
||||||
|
|
||||||
|
def create_repository(self):
|
||||||
|
def run(args, c: Session):
|
||||||
|
gitea = gitea_from_args(args, c=c)
|
||||||
|
gitea.login()
|
||||||
|
gitea.create_repository(name=args.repo_name)
|
||||||
|
|
||||||
|
self.create_repository_parser = self.subparser.add_parser(
|
||||||
|
name="create_repo",
|
||||||
|
description="Create repository on Gitea",
|
||||||
|
help="Create repository on Gitea",
|
||||||
|
)
|
||||||
|
self.__add_credentials_parser(self.create_repository_parser)
|
||||||
|
self.create_repository_parser.set_defaults(func=run)
|
||||||
|
self.create_repository_parser.add_argument(
|
||||||
|
"repo_name", type=str, help="Name of the repository to be created"
|
||||||
|
)
|
||||||
|
|
||||||
|
def install_sso(self):
|
||||||
|
def run(args, c: Session):
|
||||||
|
gitea = gitea_from_args(args, c=c)
|
||||||
|
gitea.login()
|
||||||
|
print(f"CLIENT ID: {args.client_id}")
|
||||||
|
gitea.install_sso(
|
||||||
|
sso_name=args.sso_name,
|
||||||
|
client_id=args.client_id,
|
||||||
|
client_secret=args.client_secret,
|
||||||
|
sso_auto_discovery_url=args.sso_auto_discovery_url,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.install_sso_parser = self.subparser.add_parser(
|
||||||
|
name="install_sso",
|
||||||
|
description="Install SSO on Gitea",
|
||||||
|
help="Install SSO on Gitea",
|
||||||
|
)
|
||||||
|
self.__add_credentials_parser(self.install_sso_parser)
|
||||||
|
self.install_sso_parser.add_argument(
|
||||||
|
"sso_name", type=str, help="(Human readable)Name of the SSO"
|
||||||
|
)
|
||||||
|
self.install_sso_parser.add_argument(
|
||||||
|
"client_id", type=str, help="Client ID generated by the SSO"
|
||||||
|
)
|
||||||
|
self.install_sso_parser.add_argument(
|
||||||
|
"client_secret", type=str, help="Client secret generated by the SSO"
|
||||||
|
)
|
||||||
|
self.install_sso_parser.add_argument(
|
||||||
|
"sso_auto_discovery_url",
|
||||||
|
type=str,
|
||||||
|
help="OIDC Auto Discovery URL of the SSO",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.install_sso_parser.set_defaults(func=run)
|
||||||
|
|
||||||
|
|
||||||
|
def dash_from_args(args, c: Session):
|
||||||
|
from .hostea import Hostea
|
||||||
|
|
||||||
|
return Hostea(
|
||||||
|
username=args.username,
|
||||||
|
email=args.email,
|
||||||
|
password=args.password,
|
||||||
|
host=args.host,
|
||||||
|
c=c,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Hostea:
|
||||||
|
def __init__(self, parser, c: Session):
|
||||||
|
self.c = c
|
||||||
|
self.parser = parser
|
||||||
|
self.subparser = self.parser.add_subparsers()
|
||||||
|
self.register()
|
||||||
|
self.login()
|
||||||
|
self.support()
|
||||||
|
|
||||||
|
def __add_credentials_parser(self, parser):
|
||||||
|
group = parser.add_argument_group("credentials", "User credentials")
|
||||||
|
group.add_argument("username", type=str, help="Hostea user's username")
|
||||||
|
group.add_argument("password", type=str, help="Hostea user's password")
|
||||||
|
group.add_argument("email", type=str, help="Hostea user's email")
|
||||||
|
group.add_argument("host", type=str, help="URI at which Hostea is running")
|
||||||
|
|
||||||
|
def register(self):
|
||||||
|
def run(args, c: Session):
|
||||||
|
dash = dash_from_args(args, c=c)
|
||||||
|
dash.register(maildev_host=args.maildev_host)
|
||||||
|
|
||||||
|
self.register_parser = self.subparser.add_parser(
|
||||||
|
name="register",
|
||||||
|
description="register new user ",
|
||||||
|
help="Register new user on Hostea Dashboard",
|
||||||
|
)
|
||||||
|
self.__add_credentials_parser(self.register_parser)
|
||||||
|
self.register_parser.add_argument(
|
||||||
|
"maildev_host", type=str, help="URI at which maildev is running"
|
||||||
|
)
|
||||||
|
self.register_parser.set_defaults(func=run)
|
||||||
|
|
||||||
|
def login(self):
|
||||||
|
def run(args, c: Session):
|
||||||
|
dash = dash_from_args(args, c=c)
|
||||||
|
dash.login()
|
||||||
|
|
||||||
|
self.login_parser = self.subparser.add_parser(
|
||||||
|
name="login",
|
||||||
|
description="login",
|
||||||
|
help="Login user on Hostea Dashboard",
|
||||||
|
)
|
||||||
|
self.__add_credentials_parser(self.login_parser)
|
||||||
|
self.login_parser.set_defaults(func=run)
|
||||||
|
|
||||||
|
def support(self):
|
||||||
|
def run(args, c: Session):
|
||||||
|
from .gitea import GiteaSSO
|
||||||
|
|
||||||
|
dash = dash_from_args(args, c=c)
|
||||||
|
dash.login()
|
||||||
|
|
||||||
|
gitea = GiteaSSO(
|
||||||
|
username=dash.username,
|
||||||
|
email=dash.email,
|
||||||
|
gitea_host=args.gitea_host,
|
||||||
|
hostea_org=args.gitea_hostea_org,
|
||||||
|
support_repo=args.support_repo,
|
||||||
|
c=c,
|
||||||
|
)
|
||||||
|
dash.new_ticket(gitea.new_issues_uri)
|
||||||
|
gitea.new_issue()
|
||||||
|
|
||||||
|
self.support_parser = self.subparser.add_parser(
|
||||||
|
name="support",
|
||||||
|
description="Hostea support",
|
||||||
|
help="Support functionality on Hostea Dashboard",
|
||||||
|
)
|
||||||
|
self.__add_credentials_parser(self.support_parser)
|
||||||
|
self.support_parser.add_argument(
|
||||||
|
"gitea_host", type=str, help="URI at which Gitea is running"
|
||||||
|
)
|
||||||
|
self.support_parser.add_argument(
|
||||||
|
"gitea_hostea_org",
|
||||||
|
type=str,
|
||||||
|
help="Hostea namespace(username/org) on Gitea, where support repository is hosted",
|
||||||
|
)
|
||||||
|
self.support_parser.add_argument(
|
||||||
|
"support_repo", type=str, help="support repository name"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.support_parser.set_defaults(func=run)
|
||||||
|
|
||||||
|
|
||||||
|
class Cli:
|
||||||
|
def __init__(self):
|
||||||
|
c = Session()
|
||||||
|
self.c = c
|
||||||
|
self.parser = argparse.ArgumentParser(
|
||||||
|
description="Install and Bootstrap Gitea and Hostea Dashboard"
|
||||||
|
)
|
||||||
|
self.subparser = self.parser.add_subparsers()
|
||||||
|
self.check_env()
|
||||||
|
self.gitea()
|
||||||
|
self.hostea()
|
||||||
|
|
||||||
|
def __add_credentials_parser(self, parser):
|
||||||
|
group = parser.add_argument_group("credentials", "User credentials")
|
||||||
|
group.add_argument("username", type=str, help="Gitea user's username")
|
||||||
|
group.add_argument("password", type=str, help="Gitea user's password")
|
||||||
|
group.add_argument("email", type=str, help="Gitea user's email")
|
||||||
|
|
||||||
|
def check_env(self):
|
||||||
|
def run(args, c: Session):
|
||||||
|
from .gitea import Gitea
|
||||||
|
from .hostea import Hostea
|
||||||
|
|
||||||
|
Hostea.check_online(
|
||||||
|
dashboard_host=args.hostea_host, maildev_host=args.maildev_host
|
||||||
|
)
|
||||||
|
Gitea.check_online(host=args.gitea_host)
|
||||||
|
|
||||||
|
self.check_env_parser = self.subparser.add_parser(
|
||||||
|
name="check_env",
|
||||||
|
description="Check and block until environment is ready",
|
||||||
|
help="Check and block until environment is ready",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.check_env_parser.add_argument(
|
||||||
|
"gitea_host", type=str, help="URI at which Gitea is running"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.check_env_parser.add_argument(
|
||||||
|
"hostea_host", type=str, help="URI at which Hostea is running"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.check_env_parser.add_argument(
|
||||||
|
"maildev_host", type=str, help="URI at which maildev is running"
|
||||||
|
)
|
||||||
|
self.check_env_parser.set_defaults(func=run)
|
||||||
|
|
||||||
|
def hostea(self):
|
||||||
|
self.hostea = self.subparser.add_parser(
|
||||||
|
name="hostea",
|
||||||
|
description="Hostea Dashboard",
|
||||||
|
help="Hostea Dashboard-related functionality",
|
||||||
|
)
|
||||||
|
Hostea(parser=self.hostea, c=self.c)
|
||||||
|
|
||||||
|
def gitea(self):
|
||||||
|
self.gitea = self.subparser.add_parser(
|
||||||
|
name="gitea",
|
||||||
|
description="Gitea",
|
||||||
|
help="Gitea-related functionality",
|
||||||
|
)
|
||||||
|
Gitea(parser=self.gitea, c=self.c)
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
return self.parser.parse_args()
|
|
@ -0,0 +1,38 @@
|
||||||
|
from html.parser import HTMLParser
|
||||||
|
|
||||||
|
|
||||||
|
class ParseCSRF(HTMLParser):
|
||||||
|
token: str = None
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
HTMLParser.__init__(self)
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
# @classmethod
|
||||||
|
# def dashboard_parser(cls) -> "ParseCSRF":
|
||||||
|
# return cls(name="csrfmiddlewaretoken")
|
||||||
|
#
|
||||||
|
# @classmethod
|
||||||
|
# def gitea_parser(cls) -> "ParseCSRF":
|
||||||
|
# return cls(name="_csrf")
|
||||||
|
#
|
||||||
|
def handle_starttag(self, tag: str, attrs: (str, str)):
|
||||||
|
if self.token:
|
||||||
|
return
|
||||||
|
|
||||||
|
if tag != "input":
|
||||||
|
return
|
||||||
|
|
||||||
|
token = None
|
||||||
|
for (index, (k, v)) in enumerate(attrs):
|
||||||
|
if k == "value":
|
||||||
|
token = v
|
||||||
|
|
||||||
|
if all([k == "name", v == self.name]):
|
||||||
|
if token:
|
||||||
|
self.token = token
|
||||||
|
return
|
||||||
|
for (inner_index, (nk, nv)) in enumerate(attrs, start=index):
|
||||||
|
if nk == "value":
|
||||||
|
self.token = nv
|
||||||
|
return
|
|
@ -0,0 +1,410 @@
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
from urllib.parse import urlunparse, urlparse
|
||||||
|
from html.parser import HTMLParser
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from requests import Session
|
||||||
|
from requests.auth import HTTPBasicAuth
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from .csrf import ParseCSRF
|
||||||
|
|
||||||
|
# GITEA_USER = "root"
|
||||||
|
# GITEA_EMAIL = "root@example.com"
|
||||||
|
# GITEA_PASSWORD = "foobarpassword"
|
||||||
|
# HOST = "http://localhost:8080"
|
||||||
|
#
|
||||||
|
# REPOS = []
|
||||||
|
|
||||||
|
|
||||||
|
class Gitea:
|
||||||
|
def __init__(self, host: str, username: str, password: str, email: str, c: Session):
|
||||||
|
self.host = host
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self.email = email
|
||||||
|
self.c = c
|
||||||
|
self.__csrf_key = "_csrf"
|
||||||
|
self.__logged_in = False
|
||||||
|
|
||||||
|
def get_uri(self, path: str):
|
||||||
|
parsed = urlparse(self.host)
|
||||||
|
return urlunparse((parsed.scheme, parsed.netloc, path, "", "", ""))
|
||||||
|
|
||||||
|
def get_api_uri(self, path: str):
|
||||||
|
parsed = urlparse(self.host)
|
||||||
|
return urlunparse(
|
||||||
|
(
|
||||||
|
parsed.scheme,
|
||||||
|
f"{self.username}:{self.password}@{parsed.netloc}",
|
||||||
|
path,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_online(host: str):
|
||||||
|
"""
|
||||||
|
Check if Gitea instance is online
|
||||||
|
"""
|
||||||
|
count = 0
|
||||||
|
parsed = urlparse(host)
|
||||||
|
url = urlunparse((parsed.scheme, parsed.netloc, "api/v1/nodeinfo", "", "", ""))
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
res = requests.get(url, allow_redirects=False)
|
||||||
|
if any([res.status_code == 302, res.status_code == 200]):
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
sleep(2)
|
||||||
|
print(f"Retrying {count} time")
|
||||||
|
count += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
def install(self):
|
||||||
|
"""
|
||||||
|
Install Gitea, first form that a user sees when a new instance is
|
||||||
|
deployed
|
||||||
|
"""
|
||||||
|
cwd = os.environ.get("PWD")
|
||||||
|
user = os.environ.get("USER")
|
||||||
|
payload = {
|
||||||
|
"db_type": "sqlite3",
|
||||||
|
"db_host": "localhost:3306",
|
||||||
|
"db_user": "root",
|
||||||
|
"db_passwd": "",
|
||||||
|
"db_name": "gitea",
|
||||||
|
"ssl_mode": "disable",
|
||||||
|
"db_schema": "",
|
||||||
|
"charset": "utf8",
|
||||||
|
"db_path": f"{cwd}/tmp/gitea/db/gitea.db",
|
||||||
|
"app_name": "Gitea:+Git+with+a+cup+of+tea",
|
||||||
|
"repo_root_path": f"{cwd}/tmp/gitea/repos/",
|
||||||
|
"lfs_root_path": f"{cwd}/tmp/gitea/lfs/",
|
||||||
|
"run_user": user,
|
||||||
|
"domain": "localhost",
|
||||||
|
"ssh_port": "2222",
|
||||||
|
"http_port": "3000",
|
||||||
|
"app_url": self.get_uri(""),
|
||||||
|
"log_root_path": f"{cwd}/tmp/gitea/log/",
|
||||||
|
"smtp_host": "",
|
||||||
|
"smtp_from": "",
|
||||||
|
"smtp_user": "",
|
||||||
|
"smtp_passwd": "",
|
||||||
|
"enable_federated_avatar": "on",
|
||||||
|
"enable_open_id_sign_in": "on",
|
||||||
|
"enable_open_id_sign_up": "on",
|
||||||
|
"default_allow_create_organization": "on",
|
||||||
|
"default_enable_timetracking": "on",
|
||||||
|
"no_reply_address": "noreply.localhost",
|
||||||
|
"password_algorithm": "pbkdf2",
|
||||||
|
"admin_name": "",
|
||||||
|
"admin_passwd": "",
|
||||||
|
"admin_confirm_passwd": "",
|
||||||
|
"admin_email": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = self.c.post(self.get_uri(""), data=payload)
|
||||||
|
sleep(10)
|
||||||
|
|
||||||
|
def get_csrf_token(self, url: str) -> str:
|
||||||
|
"""
|
||||||
|
Get CSRF token at a URI
|
||||||
|
"""
|
||||||
|
resp = self.c.get(url, allow_redirects=False)
|
||||||
|
if resp.status_code != 200 and resp.status_code != 302:
|
||||||
|
print(resp.status_code, resp.text)
|
||||||
|
raise Exception(f"Can't get csrf token: {resp.status_code}")
|
||||||
|
parser = ParseCSRF(name=self.__csrf_key)
|
||||||
|
parser.feed(resp.text)
|
||||||
|
csrf = parser.token
|
||||||
|
return csrf
|
||||||
|
|
||||||
|
def register(self):
|
||||||
|
"""
|
||||||
|
Register User
|
||||||
|
"""
|
||||||
|
url = self.get_uri("/user/sign_up")
|
||||||
|
csrf = self.get_csrf_token(url)
|
||||||
|
payload = {
|
||||||
|
"_csrf": csrf,
|
||||||
|
"user_name": self.username,
|
||||||
|
"password": self.password,
|
||||||
|
"retype": self.password,
|
||||||
|
"email": self.email,
|
||||||
|
}
|
||||||
|
self.c.post(url, data=payload, allow_redirects=False)
|
||||||
|
|
||||||
|
def login(self):
|
||||||
|
"""
|
||||||
|
Login, must be called at least once before performing authenticated
|
||||||
|
operations
|
||||||
|
"""
|
||||||
|
if self.__logged_in:
|
||||||
|
return
|
||||||
|
url = self.get_uri("/user/login")
|
||||||
|
csrf = self.get_csrf_token(url)
|
||||||
|
payload = {
|
||||||
|
"_csrf": csrf,
|
||||||
|
"user_name": self.username,
|
||||||
|
"password": self.password,
|
||||||
|
"remember": "on",
|
||||||
|
}
|
||||||
|
resp = self.c.post(url, data=payload, allow_redirects=False)
|
||||||
|
if any(
|
||||||
|
[resp.status_code == 302, resp.status_code == 200, resp.status_code == 303]
|
||||||
|
):
|
||||||
|
print("User logged in")
|
||||||
|
self.__logged_in = True
|
||||||
|
return
|
||||||
|
|
||||||
|
raise Exception(
|
||||||
|
f"[ERROR] Authentication failed. status code {resp.status_code}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_repository(self, name: str):
|
||||||
|
"""
|
||||||
|
Create repository
|
||||||
|
"""
|
||||||
|
self.login()
|
||||||
|
|
||||||
|
def get_repository_payload(csrf: str, name: str, user_id: str):
|
||||||
|
data = {
|
||||||
|
"_csrf": csrf,
|
||||||
|
"uid": user_id,
|
||||||
|
"repo_name": name,
|
||||||
|
"description": f"this repository is named {name}",
|
||||||
|
"repo_template": "",
|
||||||
|
"issue_labels": "",
|
||||||
|
"gitignores": "",
|
||||||
|
"license": "",
|
||||||
|
"readme": "Default",
|
||||||
|
"default_branch": "master",
|
||||||
|
"trust_model": "default",
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
|
||||||
|
url = self.get_uri("/repo/create")
|
||||||
|
user_id = self.c.get(self.get_api_uri("/api/v1/user")).json()["id"]
|
||||||
|
|
||||||
|
csrf = self.get_csrf_token(url)
|
||||||
|
data = get_repository_payload(csrf, name, user_id=user_id)
|
||||||
|
|
||||||
|
resp = self.c.post(url, data=data, allow_redirects=False)
|
||||||
|
print(f"Created repository {name}")
|
||||||
|
if resp.status_code != 302 and resp.status_code != 200:
|
||||||
|
raise Exception(
|
||||||
|
f"Error while creating repository: {name} {resp.status_code}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def install_sso(
|
||||||
|
self,
|
||||||
|
sso_name: str,
|
||||||
|
client_id: str,
|
||||||
|
client_secret: str,
|
||||||
|
sso_auto_discovery_url: str,
|
||||||
|
):
|
||||||
|
self.login()
|
||||||
|
"""
|
||||||
|
Install SSO.
|
||||||
|
|
||||||
|
- sso_name: human readable SSO name. Doesn't have any functionality
|
||||||
|
except assisting admins ID an SSO by a name
|
||||||
|
|
||||||
|
- client_id: OAuth stuff, will be generated by the SSO that should be integrated
|
||||||
|
|
||||||
|
- client_secret: OAuth stuff, will be generated by the SSO that should be integrated
|
||||||
|
|
||||||
|
- sso_auto_discovery_url: Again OAuth stuff. Should be available in the SSO documentation.
|
||||||
|
In Hostea SSO's case, it is available at
|
||||||
|
https://hostea-dash.example.org/o/.well-known/openid-configuration/
|
||||||
|
"""
|
||||||
|
csrf = self.get_csrf_token(self.get_uri("/admin/auths/new"))
|
||||||
|
payload = {
|
||||||
|
"_autofill_dummy_username": "",
|
||||||
|
"_autofill_dummy_password": "",
|
||||||
|
"_csrf": csrf,
|
||||||
|
"type": "6",
|
||||||
|
"name": sso_name,
|
||||||
|
"security_protocol": "",
|
||||||
|
"host": "",
|
||||||
|
"port": "",
|
||||||
|
"bind_dn": "",
|
||||||
|
"bind_password": "",
|
||||||
|
"user_base": "",
|
||||||
|
"user_dn": "",
|
||||||
|
"filter": "",
|
||||||
|
"admin_filter": ["", ""],
|
||||||
|
"attribute_username": "",
|
||||||
|
"attribute_name": "",
|
||||||
|
"attribute_surname": "",
|
||||||
|
"attribute_mail": "",
|
||||||
|
"attribute_ssh_public_key": "",
|
||||||
|
"attribute_avatar": "",
|
||||||
|
"group_dn": "",
|
||||||
|
"group_member_uid": "",
|
||||||
|
"user_uid": "",
|
||||||
|
"group_filter": "",
|
||||||
|
"group_team_map": "",
|
||||||
|
"search_page_size": "",
|
||||||
|
"smtp_auth": "PLAIN",
|
||||||
|
"smtp_host": "",
|
||||||
|
"smtp_port": "",
|
||||||
|
"helo_hostname": "",
|
||||||
|
"allowed_domains": "",
|
||||||
|
"pam_service_name": "",
|
||||||
|
"pam_email_domain": "",
|
||||||
|
"oauth2_provider": "openidConnect",
|
||||||
|
"oauth2_key": client_id,
|
||||||
|
"oauth2_secret": client_secret,
|
||||||
|
"oauth2_icon_url": "",
|
||||||
|
"open_id_connect_auto_discovery_url": sso_auto_discovery_url,
|
||||||
|
"oauth2_auth_url": "",
|
||||||
|
"oauth2_token_url": "",
|
||||||
|
"oauth2_profile_url": "",
|
||||||
|
"oauth2_email_url": "",
|
||||||
|
"oauth2_tenant": "",
|
||||||
|
"oauth2_scopes": "openid",
|
||||||
|
"oauth2_required_claim_name": "",
|
||||||
|
"oauth2_required_claim_value": "",
|
||||||
|
"oauth2_group_claim_name": "",
|
||||||
|
"oauth2_admin_group": "",
|
||||||
|
"oauth2_restricted_group": "",
|
||||||
|
"sspi_auto_create_users": "on",
|
||||||
|
"sspi_auto_activate_users": "on",
|
||||||
|
"sspi_strip_domain_names": "on",
|
||||||
|
"sspi_separator_replacement": "_",
|
||||||
|
"sspi_default_language": "",
|
||||||
|
"is_sync_enabled": "on",
|
||||||
|
"is_active": "on",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = self.c.post(self.get_uri("/admin/auths/new"), data=payload)
|
||||||
|
|
||||||
|
|
||||||
|
class ParseSSOLogin(HTMLParser):
|
||||||
|
url: str = None
|
||||||
|
|
||||||
|
def handle_starttag(self, tag: str, attrs: (str, str)):
|
||||||
|
if self.url:
|
||||||
|
return
|
||||||
|
|
||||||
|
if tag != "a":
|
||||||
|
return
|
||||||
|
|
||||||
|
token = None
|
||||||
|
for (index, (k, v)) in enumerate(attrs):
|
||||||
|
if k == "href":
|
||||||
|
if "/user/oauth2/" in v:
|
||||||
|
self.url = v
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class GiteaSSO:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
username: str,
|
||||||
|
email: str,
|
||||||
|
gitea_host: str,
|
||||||
|
hostea_org: str,
|
||||||
|
support_repo: str,
|
||||||
|
c: Session,
|
||||||
|
):
|
||||||
|
self.c = c
|
||||||
|
self.username = username
|
||||||
|
self.gitea_host = gitea_host
|
||||||
|
self.hostea_org = hostea_org
|
||||||
|
self.support_repo = support_repo
|
||||||
|
self.email = email
|
||||||
|
|
||||||
|
self.__csrf_key = "_csrf"
|
||||||
|
|
||||||
|
url = urlparse(self.gitea_host)
|
||||||
|
repo = f"{self.hostea_org}/{self.support_repo}"
|
||||||
|
issues = f"{repo}/issues"
|
||||||
|
new_issues = f"{issues}/new"
|
||||||
|
|
||||||
|
self.__partial_call_back_url = urlunparse(
|
||||||
|
(url.scheme, url.netloc, "/user/oauth2/", "", "", "")
|
||||||
|
)
|
||||||
|
self.__login = urlunparse((url.scheme, url.netloc, "/user/login/", "", "", ""))
|
||||||
|
self.__link_acount = urlunparse(
|
||||||
|
(url.scheme, url.netloc, "/user/link_account/", "", "", "")
|
||||||
|
)
|
||||||
|
self.__link_acount_signup = urlunparse(
|
||||||
|
(url.scheme, url.netloc, "/user/link_account_signup/", "", "", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
self.__me = urlunparse(
|
||||||
|
(url.scheme, url.netloc, f"/{self.username}", "", "", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
self.issues_uri = urlunparse((url.scheme, url.netloc, issues, "", "", ""))
|
||||||
|
self.new_issues_uri = urlunparse(
|
||||||
|
(url.scheme, url.netloc, new_issues, "", "", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_csrf(self, url: str) -> str:
|
||||||
|
resp = self.c.get(url)
|
||||||
|
parser = ParseCSRF(name=self.__csrf_key)
|
||||||
|
parser.feed(resp.text)
|
||||||
|
return parser.token
|
||||||
|
|
||||||
|
def _sso_login(self):
|
||||||
|
resp = self.c.get(self.__login)
|
||||||
|
parser = ParseSSOLogin()
|
||||||
|
parser.feed(resp.text)
|
||||||
|
|
||||||
|
url = urlparse(self.gitea_host)
|
||||||
|
## SSO URL in Gitea login page
|
||||||
|
sso = urlunparse((url.scheme, url.netloc, parser.url, "", "", ""))
|
||||||
|
|
||||||
|
# redirects are enabled to for a cleaner implementation. Commented out
|
||||||
|
# code below does the same in a step-by-step manner
|
||||||
|
resp = self.c.get(sso)
|
||||||
|
|
||||||
|
# resp = c.get(sso, allow_redirects=False)
|
||||||
|
# ## Visiting SSO URL redirects the user with HTTP 307 to the SSO for authorization
|
||||||
|
# assert resp.status_code == 307
|
||||||
|
|
||||||
|
# resp = c.get(resp.headers["Location"], allow_redirects=False)
|
||||||
|
|
||||||
|
# assert self.__partial_call_back_url in resp.headers["Location"]
|
||||||
|
# resp = c.get(resp.headers["Location"], allow_redirects=False)
|
||||||
|
# assert resp.status_code == 303
|
||||||
|
# assert resp.headers["Location"] in self.__link_acount
|
||||||
|
|
||||||
|
# to register account, the user has to visit form at self.__link_acount
|
||||||
|
csrf = self.get_csrf(self.__link_acount)
|
||||||
|
# which makes a POST request to self.__link_acount_signup
|
||||||
|
# weird, but have to go to above URL to collect CSRF toekn
|
||||||
|
payload = {
|
||||||
|
"user_name": self.username,
|
||||||
|
"email": self.email,
|
||||||
|
self.__csrf_key: csrf,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = self.c.post(self.__link_acount_signup, payload, allow_redirects=True)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
# redirect mechanisms seems to change for every version
|
||||||
|
# print(resp.status_code)
|
||||||
|
# print(resp.headers["Location"])
|
||||||
|
# assert resp.status_code == 303
|
||||||
|
# assert resp.headers["Location"] == self.new_issues_uri
|
||||||
|
|
||||||
|
resp = self.c.get(self.__me)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert self.username in resp.text
|
||||||
|
|
||||||
|
def new_issue(self):
|
||||||
|
resp = self.c.get(self.new_issues_uri, allow_redirects=False)
|
||||||
|
resp.status_code = 303
|
||||||
|
assert "/user/login" in resp.headers["Location"]
|
||||||
|
|
||||||
|
self._sso_login()
|
||||||
|
resp = self.c.get(self.new_issues_uri, allow_redirects=False)
|
||||||
|
assert resp.status_code == 200
|
|
@ -0,0 +1,129 @@
|
||||||
|
import logging
|
||||||
|
from urllib.parse import urlparse, urlunparse
|
||||||
|
from html.parser import HTMLParser
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from requests import Session
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from .csrf import ParseCSRF
|
||||||
|
|
||||||
|
|
||||||
|
class Hostea:
|
||||||
|
def __init__(self, username: str, email: str, password: str, host: str, c: Session):
|
||||||
|
self.username = username
|
||||||
|
self.email = email
|
||||||
|
self.password = password
|
||||||
|
self.csrf_key = "csrfmiddlewaretoken"
|
||||||
|
self.host = host
|
||||||
|
self.c = c
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_online(dashboard_host: str, maildev_host: str):
|
||||||
|
"""
|
||||||
|
Check if Hostea Dashboard is online
|
||||||
|
"""
|
||||||
|
count = 0
|
||||||
|
dash_parsed = urlparse(dashboard_host)
|
||||||
|
maildev_parsed = urlparse(maildev_host)
|
||||||
|
urls = [
|
||||||
|
urlunparse((dash_parsed.scheme, dash_parsed.netloc, "/login/", "", "", "")),
|
||||||
|
urlunparse((maildev_parsed.scheme, maildev_parsed.netloc, "", "", "", "")),
|
||||||
|
]
|
||||||
|
|
||||||
|
for url in urls:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
res = requests.get(url, allow_redirects=False)
|
||||||
|
if any([res.status_code == 302, res.status_code == 200]):
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
sleep(2)
|
||||||
|
print(e)
|
||||||
|
print(f"[Hostea] Retrying {count} time for {url}")
|
||||||
|
count += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
def get_uri(self, path: str):
|
||||||
|
parsed = urlparse(self.host)
|
||||||
|
return urlunparse((parsed.scheme, parsed.netloc, path, "", "", ""))
|
||||||
|
|
||||||
|
def get_csrf(self, url: str) -> str:
|
||||||
|
resp = self.c.get(url=url)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
parser = ParseCSRF(name=self.csrf_key)
|
||||||
|
parser.feed(resp.text)
|
||||||
|
csrf = parser.token
|
||||||
|
return csrf
|
||||||
|
|
||||||
|
def __get_verification_link(self, maildev_host: str):
|
||||||
|
def maildev_uri(maildev_host: str, path: str):
|
||||||
|
parsed = urlparse(maildev_host)
|
||||||
|
return urlunparse((parsed.scheme, parsed.netloc, path, "", "", ""))
|
||||||
|
|
||||||
|
resp = self.c.get(maildev_uri(maildev_host=maildev_host, path="/email/"))
|
||||||
|
# resp = self.c.get("http://localhost:1080/email/")
|
||||||
|
emails = resp.json()
|
||||||
|
for email in emails:
|
||||||
|
if email["to"][0]["address"] == self.email:
|
||||||
|
logging.info("[Dashboard] Found verification link")
|
||||||
|
resp = self.c.delete(
|
||||||
|
maildev_uri(maildev_host=maildev_host, path=f"/email/{email['id']}")
|
||||||
|
)
|
||||||
|
return str.strip(email["text"].split("\n")[1])
|
||||||
|
logging.critical("[Dashboard] Verification link not found")
|
||||||
|
|
||||||
|
def register(self, maildev_host: str):
|
||||||
|
url = self.get_uri("/register/")
|
||||||
|
csrf = self.get_csrf(url)
|
||||||
|
payload = {
|
||||||
|
"username": self.username,
|
||||||
|
"password": self.password,
|
||||||
|
"email": self.email,
|
||||||
|
"confirm_password": self.password,
|
||||||
|
self.csrf_key: csrf,
|
||||||
|
}
|
||||||
|
|
||||||
|
logging.info("Registering user")
|
||||||
|
resp = self.c.post(url, payload, allow_redirects=False)
|
||||||
|
assert resp.status_code == 302
|
||||||
|
assert "pending" in resp.headers["Location"]
|
||||||
|
|
||||||
|
email_verification_link = self.__get_verification_link(
|
||||||
|
maildev_host=maildev_host
|
||||||
|
)
|
||||||
|
csrf = self.get_csrf(email_verification_link)
|
||||||
|
payload = {
|
||||||
|
self.csrf_key: csrf,
|
||||||
|
}
|
||||||
|
resp = self.c.post(email_verification_link, payload, allow_redirects=False)
|
||||||
|
assert resp.status_code == 302
|
||||||
|
assert resp.headers["Location"] == "/login/"
|
||||||
|
logging.info("[Dashboard] Email verified user")
|
||||||
|
|
||||||
|
def login(self):
|
||||||
|
url = self.get_uri("/login/")
|
||||||
|
|
||||||
|
csrf = self.get_csrf(url)
|
||||||
|
payload = {
|
||||||
|
"login": self.username,
|
||||||
|
"password": self.password,
|
||||||
|
self.csrf_key: csrf,
|
||||||
|
}
|
||||||
|
|
||||||
|
logging.info("Logging In user")
|
||||||
|
resp = self.c.post(url, payload, allow_redirects=False)
|
||||||
|
|
||||||
|
assert resp.status_code == 302
|
||||||
|
assert resp.headers["Location"] == "/"
|
||||||
|
|
||||||
|
resp = self.c.get(self.get_uri("/support/new/"))
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
def new_ticket(self, support_repository_new_issue: str):
|
||||||
|
resp = self.c.get(self.get_uri("/support/new/"))
|
||||||
|
|
||||||
|
|
||||||
|
# print(resp.text)
|
||||||
|
# print(support_repository_new_issue)
|
||||||
|
# assert support_repository_new_issue in resp.text
|
|
@ -0,0 +1,186 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -Exeuo pipefail
|
||||||
|
|
||||||
|
readonly GITEA_PID_FILE=./tmp/gitea.pid
|
||||||
|
readonly SERVER_PID_FILE=./tmp/gitea.pid
|
||||||
|
|
||||||
|
readonly DASHBOARD_URL="http://localhost:8000"
|
||||||
|
readonly GITEA_URL="http://localhost:3000"
|
||||||
|
|
||||||
|
|
||||||
|
readonly DASHBOARD_OIDC_DISCOVERY_URL="$DASHBOARD_URL/o/.well-known/openid-configuration/"
|
||||||
|
|
||||||
|
readonly DASHBOARD_ADMIN_USERNAME=root
|
||||||
|
readonly DASHBOARD_ADMIN_PASSWORD=supercomplicatedpassword
|
||||||
|
readonly DASHBOARD_ADMIN_EMAIL="$DASHBOARD_ADMIN_USERNAME@dash.example.org"
|
||||||
|
readonly DASHBOARD_OIDC_APP_NAME=hostea-gitea
|
||||||
|
|
||||||
|
readonly GITEA_ROOT_USERNAME=root
|
||||||
|
readonly GITEA_ROOT_EMAIL="$GITEA_ROOT_USERNAME@example.org"
|
||||||
|
readonly GITEA_ROOT_PASSOWRD=supercomplicatedpassword
|
||||||
|
readonly GITEA_HOSTEA_SSO_NAME=hostea-sso
|
||||||
|
readonly GITEA_OIDC_CALLBACK="$GITEA_URL/user/oauth2/$GITEA_HOSTEA_SSO_NAME/callback"
|
||||||
|
|
||||||
|
readonly GITEA_HOSTEA_USERNAME=hostea
|
||||||
|
readonly GITEA_HOSTEA_PASSWORD=supercomplicatedpassword
|
||||||
|
readonly GITEA_HOSTEA_EMAIL="$GITEA_HOSTEA_USERNAME@example.org"
|
||||||
|
readonly GITEA_HOSTEA_SUPPORT_REPO="support"
|
||||||
|
|
||||||
|
readonly HOSTEA_CUSTOMER_USERNAME=batman
|
||||||
|
readonly HOSTEA_CUSTOMER_PASSWORD=supercomplicatedpassword
|
||||||
|
readonly HOSTEA_CUSTOMER_EMAIL="$HOSTEA_CUSTOMER_USERNAME@example.org"
|
||||||
|
|
||||||
|
|
||||||
|
if [ -z ${CI+x} ];
|
||||||
|
then
|
||||||
|
MAILDEV_URL="http://localhost:1080"
|
||||||
|
else
|
||||||
|
MAILDEV_URL="http://smtp:1080"
|
||||||
|
fi
|
||||||
|
|
||||||
|
OIDC_CLIENT_ID=""
|
||||||
|
OIDC_CLIENT_SECRET=""
|
||||||
|
|
||||||
|
wait_for_env() {
|
||||||
|
curl $DASHBOARD_URL || true
|
||||||
|
python -m integration \
|
||||||
|
check_env $GITEA_URL $DASHBOARD_URL $MAILDEV_URL
|
||||||
|
}
|
||||||
|
|
||||||
|
# create OIDC app on Hostea Dashboard
|
||||||
|
oidc_dashboard_init() {
|
||||||
|
python -m integration \
|
||||||
|
hostea register \
|
||||||
|
$DASHBOARD_ADMIN_USERNAME $DASHBOARD_ADMIN_PASSWORD \
|
||||||
|
$DASHBOARD_ADMIN_EMAIL \
|
||||||
|
$DASHBOARD_URL \
|
||||||
|
$MAILDEV_URL
|
||||||
|
|
||||||
|
resp=$(python manage.py create_oidc \
|
||||||
|
$DASHBOARD_OIDC_APP_NAME $DASHBOARD_ADMIN_USERNAME \
|
||||||
|
$GITEA_OIDC_CALLBACK)
|
||||||
|
OIDC_CLIENT_ID=$(echo $resp | cut -d ":" -f 2 | cut -d " " -f 2)
|
||||||
|
OIDC_CLIENT_SECRET=$(echo $resp | cut -d ":" -f 3 | cut -d " " -f 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
# register root user on Gitea to simulate Hoste admin and integrate SSO
|
||||||
|
gitea_root(){
|
||||||
|
python -m integration \
|
||||||
|
gitea install \
|
||||||
|
$GITEA_ROOT_USERNAME $GITEA_ROOT_PASSOWRD \
|
||||||
|
$GITEA_ROOT_EMAIL \
|
||||||
|
$GITEA_URL
|
||||||
|
python -m integration \
|
||||||
|
gitea register \
|
||||||
|
$GITEA_ROOT_USERNAME $GITEA_ROOT_PASSOWRD \
|
||||||
|
$GITEA_ROOT_EMAIL \
|
||||||
|
$GITEA_URL
|
||||||
|
python -m integration \
|
||||||
|
gitea login \
|
||||||
|
$GITEA_ROOT_USERNAME $GITEA_ROOT_PASSOWRD \
|
||||||
|
$GITEA_ROOT_EMAIL \
|
||||||
|
$GITEA_URL
|
||||||
|
python -m integration \
|
||||||
|
gitea install_sso \
|
||||||
|
$GITEA_ROOT_USERNAME $GITEA_ROOT_PASSOWRD \
|
||||||
|
$GITEA_ROOT_EMAIL \
|
||||||
|
$GITEA_URL \
|
||||||
|
$GITEA_HOSTEA_SSO_NAME \
|
||||||
|
$OIDC_CLIENT_ID $OIDC_CLIENT_SECRET \
|
||||||
|
$DASHBOARD_OIDC_DISCOVERY_URL
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# register user "Hostea" on Gitea and create support repository
|
||||||
|
support_repo_init() {
|
||||||
|
python -m integration \
|
||||||
|
gitea register \
|
||||||
|
$GITEA_HOSTEA_USERNAME $GITEA_HOSTEA_PASSWORD \
|
||||||
|
$GITEA_HOSTEA_EMAIL \
|
||||||
|
$GITEA_URL
|
||||||
|
python -m integration \
|
||||||
|
gitea login \
|
||||||
|
$GITEA_HOSTEA_USERNAME $GITEA_HOSTEA_PASSWORD \
|
||||||
|
$GITEA_HOSTEA_EMAIL \
|
||||||
|
$GITEA_URL
|
||||||
|
python -m integration \
|
||||||
|
gitea create_repo \
|
||||||
|
$GITEA_HOSTEA_USERNAME $GITEA_HOSTEA_PASSWORD \
|
||||||
|
$GITEA_HOSTEA_EMAIL \
|
||||||
|
$GITEA_URL \
|
||||||
|
$GITEA_HOSTEA_SUPPORT_REPO
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create user on Hostea to simulate a Hostea customer
|
||||||
|
hostea_customer_simulation() {
|
||||||
|
python -m integration \
|
||||||
|
hostea register \
|
||||||
|
$HOSTEA_CUSTOMER_USERNAME $HOSTEA_CUSTOMER_PASSWORD \
|
||||||
|
$HOSTEA_CUSTOMER_EMAIL \
|
||||||
|
$DASHBOARD_URL \
|
||||||
|
$MAILDEV_URL
|
||||||
|
python -m integration \
|
||||||
|
hostea login \
|
||||||
|
$HOSTEA_CUSTOMER_USERNAME $HOSTEA_CUSTOMER_PASSWORD \
|
||||||
|
$HOSTEA_CUSTOMER_EMAIL $DASHBOARD_URL
|
||||||
|
python -m integration \
|
||||||
|
hostea support \
|
||||||
|
$HOSTEA_CUSTOMER_USERNAME $HOSTEA_CUSTOMER_PASSWORD \
|
||||||
|
$HOSTEA_CUSTOMER_EMAIL \
|
||||||
|
$DASHBOARD_URL \
|
||||||
|
$GITEA_URL \
|
||||||
|
$GITEA_HOSTEA_USERNAME $GITEA_HOSTEA_SUPPORT_REPO
|
||||||
|
}
|
||||||
|
|
||||||
|
gitea(){
|
||||||
|
readonly BIN=tmp/gitea/bin/gitea
|
||||||
|
readonly SOURCE="https://github.com/go-gitea/gitea/releases/download/v1.16.5/gitea-1.16.5-linux-amd64"
|
||||||
|
readonly CONFIG_FILE=gitea/app.ini
|
||||||
|
|
||||||
|
mkdir -p tmp/gitea/bin || true
|
||||||
|
|
||||||
|
for dir in repos db lfs log
|
||||||
|
do
|
||||||
|
rm -rf tmp/gitea/$dir || true
|
||||||
|
mkdir -p tmp/gitea/$dir || true
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -e $BIN ];
|
||||||
|
then
|
||||||
|
wget --quiet --output-document=$BIN $SOURCE
|
||||||
|
chmod +x $BIN
|
||||||
|
fi
|
||||||
|
|
||||||
|
nohup $BIN --config $CONFIG_FILE web > /dev/null 2>&1 &
|
||||||
|
GITEA_PID=$!
|
||||||
|
echo $GITEA_PID > $GITEA_PID_FILE
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_env() {
|
||||||
|
mkdir tmp/ || true
|
||||||
|
nohup python manage.py runserver > /dev/null 2>&1 &
|
||||||
|
SERVER_PID=$!
|
||||||
|
echo $SERVER_PID > $SERVER_PID_FILE
|
||||||
|
gitea
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown_env() {
|
||||||
|
kill $(cat $GITEA_PID_FILE)
|
||||||
|
kill $(cat $SERVER_PID_FILE)
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
teardown_env || true
|
||||||
|
setup_env
|
||||||
|
wait_for_env
|
||||||
|
oidc_dashboard_init
|
||||||
|
echo "OIDC APP initialized. CLIENT_ID: $OIDC_CLIENT_ID CLIENT SECRET: $OIDC_CLIENT_SECRET"
|
||||||
|
gitea_root
|
||||||
|
support_repo_init
|
||||||
|
hostea_customer_simulation
|
||||||
|
teardown_env
|
||||||
|
echo "All Good! :)"
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
Loading…
Reference in New Issue