From 69e786cdb26465edaddb5ae666c5010511cfc1ef Mon Sep 17 00:00:00 2001 From: Etienne Monier Date: Tue, 8 Mar 2022 10:23:01 +0100 Subject: [PATCH 1/9] feat: Enable user and pass authentification Token is not the only way to authentificate to gitea. Using credentials should also be enabled. When initializing the Gitea object, the token, the auth or none of them can be provided. --- gitea/gitea.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/gitea/gitea.py b/gitea/gitea.py index 80af2e6..47160d0 100644 --- a/gitea/gitea.py +++ b/gitea/gitea.py @@ -19,17 +19,30 @@ class Gitea: CREATE_ORG = """/admin/users/%s/orgs""" # CREATE_TEAM = """/orgs/%s/teams""" # - def __init__(self, gitea_url: str, token_text: str, log_level="INFO"): + def __init__( + self, + gitea_url: str, + token_text=None, + auth=None, + log_level="INFO" + ): """ Initializing Gitea-instance.""" self.logger = logging.getLogger(__name__) self.logger.setLevel(log_level) self.headers = { - "Authorization": "token " + token_text, "Content-type": "application/json", } self.url = gitea_url self.requests = requests.Session() + # Manage authentification + if not token_text and not auth: + raise ValueError("Please provide auth or token_text, but not both") + if token_text: + self.headers["Authorization"] = "token " + token_text + if auth: + self.requests.auth = tuple(auth.split(":")) + def __get_url(self, endpoint): url = self.url + "/api/v1" + endpoint self.logger.debug("Url: %s" % url) From d0bc054b30deb29e0ab6d43be5c523bdd3bd4461 Mon Sep 17 00:00:00 2001 From: Etienne Monier Date: Tue, 8 Mar 2022 11:27:06 +0100 Subject: [PATCH 2/9] feat: Add an option to disable SSL certificate check In case of particular self-signed SSL certificate, the requests verification can fail. To avoid that, an option was added to use the requests verify option. --- gitea/gitea.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/gitea/gitea.py b/gitea/gitea.py index 47160d0..fef7519 100644 --- a/gitea/gitea.py +++ b/gitea/gitea.py @@ -1,9 +1,10 @@ -import json import logging +import json from typing import List, Dict, Union -import requests from frozendict import frozendict +import requests +import urllib3 from .apiobject import User, Organization, Repository, Team from .exceptions import NotFoundException, ConflictException, AlreadyExistsException @@ -24,6 +25,7 @@ class Gitea: gitea_url: str, token_text=None, auth=None, + verify=True, log_level="INFO" ): """ Initializing Gitea-instance.""" @@ -43,6 +45,12 @@ class Gitea: if auth: self.requests.auth = tuple(auth.split(":")) + # Manage SSL certification verification + self.requests.verify = verify + if not verify: + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + def __get_url(self, endpoint): url = self.url + "/api/v1" + endpoint self.logger.debug("Url: %s" % url) From 3f7fe2ac313970569f92340614a2fd3f05fffec1 Mon Sep 17 00:00:00 2001 From: Etienne Monier Date: Tue, 8 Mar 2022 11:38:26 +0100 Subject: [PATCH 3/9] fix: Add a docstring for the Gitea.__init__ function --- gitea/gitea.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/gitea/gitea.py b/gitea/gitea.py index fef7519..b7cb900 100644 --- a/gitea/gitea.py +++ b/gitea/gitea.py @@ -28,7 +28,17 @@ class Gitea: verify=True, log_level="INFO" ): - """ Initializing Gitea-instance.""" + """ Initializing Gitea-instance + + Args: + gitea_url (str): The Gitea instance URL. + token_text (str, None): The access token, by default None. + auth (str, None): The user credentials `username:password`, + by default None. + verify (bool): If True, allow insecure server connections + when using SSL. + log_level (str): The log level, by default `INFO`. + """ self.logger = logging.getLogger(__name__) self.logger.setLevel(log_level) self.headers = { From 67862b07c5f3131b9e251f0e5b305bb759e73003 Mon Sep 17 00:00:00 2001 From: Etienne Monier Date: Tue, 8 Mar 2022 11:32:26 +0100 Subject: [PATCH 4/9] fix: Add some other extensions to .gitignore --- .gitignore | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 8f9b5db..6b45421 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,39 @@ -*.pyc -.*.sw* -*.token +# pyenv +.python-version -# local virtual environment +# dotenv +.env + +# virtualenv .venv +venv/ +ENV/ +# IDE settings +.vscode/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +.*.sw* +*.token \ No newline at end of file From 507ac1de18d741f4137ce32a27dab158ca79f6ec Mon Sep 17 00:00:00 2001 From: Etienne Monier Date: Tue, 8 Mar 2022 16:32:11 +0100 Subject: [PATCH 5/9] fix: Pass the auth arg as a tuple instead of str --- gitea/gitea.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gitea/gitea.py b/gitea/gitea.py index b7cb900..eede110 100644 --- a/gitea/gitea.py +++ b/gitea/gitea.py @@ -33,8 +33,8 @@ class Gitea: Args: gitea_url (str): The Gitea instance URL. token_text (str, None): The access token, by default None. - auth (str, None): The user credentials `username:password`, - by default None. + auth (tuple, None): The user credentials + `(username, password)`, by default None. verify (bool): If True, allow insecure server connections when using SSL. log_level (str): The log level, by default `INFO`. @@ -53,7 +53,7 @@ class Gitea: if token_text: self.headers["Authorization"] = "token " + token_text if auth: - self.requests.auth = tuple(auth.split(":")) + self.requests.auth = auth # Manage SSL certification verification self.requests.verify = verify From 4af5ce7ae92e274f61d8f1cf7b77e06d9015ab82 Mon Sep 17 00:00:00 2001 From: Etienne Monier Date: Wed, 9 Mar 2022 15:51:34 +0100 Subject: [PATCH 6/9] feat: Set the default branch on repo creation A new option is proposed for the gitea.Gitea.create_repo to set the default branch. --- gitea/gitea.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gitea/gitea.py b/gitea/gitea.py index eede110..127b55e 100644 --- a/gitea/gitea.py +++ b/gitea/gitea.py @@ -228,6 +228,7 @@ class Gitea: license: str = None, readme: str = "Default", issue_labels: str = None, + default_branch="master", ): """ Create a Repository. Throws: @@ -249,6 +250,7 @@ class Gitea: "license": license, "issue_labels": issue_labels, "readme": readme, + "default_branch": default_branch, }, ) if "id" in result: From ad69de4b867ef86b95e25a7cc4691006d52ada4c Mon Sep 17 00:00:00 2001 From: Etienne Monier Date: Wed, 9 Mar 2022 21:41:14 +0100 Subject: [PATCH 7/9] feat: Add some options when creating a team These two options are: - can_create_org_repo which allow the users to create organization repositories - include_all_repositories which allow the user to have access to all repos By default, these two rights are not provided for security reason. --- gitea/gitea.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gitea/gitea.py b/gitea/gitea.py index 127b55e..babf685 100644 --- a/gitea/gitea.py +++ b/gitea/gitea.py @@ -300,6 +300,8 @@ class Gitea: name: str, description: str = "", permission: str = "read", + can_create_org_repo: bool = False, + includes_all_repositories: bool = False, units=( "repo.code", "repo.issues", @@ -324,6 +326,8 @@ class Gitea: "name": name, "description": description, "permission": permission, + "can_create_org_repo": can_create_org_repo, + "includes_all_repositories": includes_all_repositories, "units": units, }, ) From cfd1b7f42de92215dd16d9fece0805f24bf79c11 Mon Sep 17 00:00:00 2001 From: Etienne Monier Date: Thu, 10 Mar 2022 09:29:07 +0100 Subject: [PATCH 8/9] feat: Add full_name option to create_user --- gitea/gitea.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gitea/gitea.py b/gitea/gitea.py index babf685..b98259a 100644 --- a/gitea/gitea.py +++ b/gitea/gitea.py @@ -179,6 +179,7 @@ class Gitea: user_name: str, email: str, password: str, + full_name: str = None, login_name: str = None, change_pw=True, send_notify=True, @@ -191,9 +192,12 @@ class Gitea: """ if not login_name: login_name = user_name + if not full_name: + full_name = user_name request_data = { "source_id": source_id, "login_name": login_name, + "full_name": full_name, "username": user_name, "email": email, "password": password, From f9e4c68fa7537fac6faff7367338b851dd7a8f46 Mon Sep 17 00:00:00 2001 From: Etienne Monier Date: Fri, 11 Mar 2022 11:36:39 +0100 Subject: [PATCH 9/9] feat: Implement User and Organization repo creation Until now, the repository creation was only possible using the gitea.Gitea.create_repo function. Get, this only allows the admin to create a repo for a user or an organization. By implementing these two new functions: 1. a non-root user can now create an own repo, 2. an allowed user can now create a repo into an organization. --- gitea/apiobject.py | 78 ++++++++++++++++++++++++++++++++++++++++++++++ gitea/gitea.py | 12 ++++--- tests/conftest.py | 36 +++++++++++++++++++++ 3 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 tests/conftest.py diff --git a/gitea/apiobject.py b/gitea/apiobject.py index 9fd5174..77526ba 100644 --- a/gitea/apiobject.py +++ b/gitea/apiobject.py @@ -48,6 +48,45 @@ class Organization(ApiObject): ) self.dirty_fields = {} + def create_repo( + self, + repoName: str, + description: str = "", + private: bool = False, + autoInit=True, + gitignores: str = None, + license: str = None, + readme: str = "Default", + issue_labels: str = None, + default_branch="master", + ): + """Create an organization Repository + + Throws: + AlreadyExistsException: If the Repository exists already. + Exception: If something else went wrong. + """ + result = self.gitea.requests_post( + f"/orgs/{self.name}/repos", + data={ + "name": repoName, + "description": description, + "private": private, + "auto_init": autoInit, + "gitignores": gitignores, + "license": license, + "issue_labels": issue_labels, + "readme": readme, + "default_branch": default_branch, + }, + ) + if "id" in result: + self.gitea.logger.info("Successfully created Repository %s " % result["name"]) + else: + self.gitea.logger.error(result["message"]) + raise Exception("Repository not created... (gitea: %s)" % result["message"]) + return Repository.parse_response(self, result) + def get_repositories(self) -> List["Repository"]: results = self.gitea.requests_get_paginated( Organization.ORG_REPOS_REQUEST % self.username @@ -175,6 +214,45 @@ class User(ApiObject): self.gitea.requests_patch(User.ADMIN_EDIT_USER.format(**args), data=values) self.dirty_fields = {} + def create_repo( + self, + repoName: str, + description: str = "", + private: bool = False, + autoInit=True, + gitignores: str = None, + license: str = None, + readme: str = "Default", + issue_labels: str = None, + default_branch="master", + ): + """Create a user Repository + + Throws: + AlreadyExistsException: If the Repository exists already. + Exception: If something else went wrong. + """ + result = self.gitea.requests_post( + "/user/repos", + data={ + "name": repoName, + "description": description, + "private": private, + "auto_init": autoInit, + "gitignores": gitignores, + "license": license, + "issue_labels": issue_labels, + "readme": readme, + "default_branch": default_branch, + }, + ) + if "id" in result: + self.gitea.logger.info("Successfully created Repository %s " % result["name"]) + else: + self.gitea.logger.error(result["message"]) + raise Exception("Repository not created... (gitea: %s)" % result["message"]) + return Repository.parse_response(self, result) + def get_repositories(self) -> List["Repository"]: """ Get all Repositories owned by this User.""" url = f"/users/{self.username}/repos" diff --git a/gitea/gitea.py b/gitea/gitea.py index b98259a..5358a13 100644 --- a/gitea/gitea.py +++ b/gitea/gitea.py @@ -234,11 +234,15 @@ class Gitea: issue_labels: str = None, default_branch="master", ): - """ Create a Repository. - Throws: - AlreadyExistsException, if Repository exists already. - Exception, if something else went wrong. + """ Create a Repository as the administrator + Throws: + AlreadyExistsException: If the Repository exists already. + Exception: If something else went wrong. + + Note: + Non-admin users can not use this method. Please use instead + `gitea.User.create_repo` or `gitea.Organization.create_repo`. """ # although this only says user in the api, this also works for # organizations diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..607c173 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +"""Fixtures for testing py-gitea + +Instructions +------------ +put a ".token" file into your directory containg only the token for gitea + +""" + +import os + +import pytest + +from gitea import Gitea + +@pytest.fixture +def instance(scope="module"): + try: + url = os.getenv("PY_GITEA_URL") + token = os.getenv("PY_GITEA_TOKEN") + auth = os.getenv("PY_GITEA_AUTH") + if not url: + raise ValueError("No Gitea URL was provided") + if token and auth: + raise ValueError("Please provide auth or token_text, but not both") + g = Gitea(url, token_text=token, auth=auth, verify=False) + print("Gitea Version: " + g.get_version()) + print("API-Token belongs to user: " + g.get_user().username) + return g + except: + assert ( + False + ), "Gitea could not load. \ + - Instance running at http://localhost:3000 \ + - Token at .token \ + ?"