Skip to content

Commit

Permalink
Proxy for API interception and credential handling
Browse files Browse the repository at this point in the history
  • Loading branch information
gnarzilla committed Nov 17, 2024
1 parent 149c03d commit 29872ad
Show file tree
Hide file tree
Showing 26 changed files with 2,920 additions and 125 deletions.
43 changes: 16 additions & 27 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,26 @@ name = "guardian"
version = "1.0.0"
description = "Git Authentication & Development Assistant"
requires-python = ">=3.8"
dependencies = [
"click>=8.0.0",
"rich>=13.0.0",
"keyring>=24.0.0",
"PyYAML>=6.0",
]
dependencies = [ "markdown>=3.7", "markupsafe>=3.0.2", "beautifulsoup4>=4.12.0","aiohttp>=3.9.0", "pyyaml>=6.0.2", "secretstorage>=3.3.3", "astroid>=3.3.5", "autocommand>=2.2.2", "babel>=2.16.0", "backports.tarfile>=1.2.0", "certifi>=2024.8.30", "cffi>=1.17.1", "charset-normalizer>=3.4.0", "click>=8.1.7", "colorama>=0.4.6", "coverage>=7.6.4", "cryptography>=43.0.3", "dill>=0.3.9", "ghp-import>=2.1.0", "github-cli>=1.0.0", "idna>=3.10", "importlib-metadata>=8.5.0", "inflect>=7.4.0", "iniconfig>=2.0.0", "jaraco.classes>=3.4.0", "jaraco.collections>=5.1.0", "jaraco.context>=6.0.1", "jaraco.functools>=4.1.0", "jaraco.text>=4.0.0", "jeepney>=0.8.0", "jinja2>=3.1.4", "jwt>=1.3.1", "keyring>=25.5.0", "markdown-it-py>=3.0.0", "mccabe>=0.7.0", "mdurl>=0.1.2", "mergedeep>=1.3.4", "mitmproxy>=10.0.0","mkdocs>=1.6.1", "mkdocs-get-deps>=0.2.0", "mkdocs-material>=9.5.44", "mkdocs-material-extensions>=1.3.1", "more-itertools>=10.5.0", "mypy-extensions>=1.0.0", "packaging>=24.2", "paginate>=0.5.7", "pathspec>=0.12.1", "pip>=24.2", "platformdirs>=4.3.6", "pluggy>=1.5.0", "pycparser>=2.22", "pygments>=2.18.0", "pylint>=3.3.1", "pymdown-extensions>=10.12", "pytest-cov>=6.0.0", "python-dateutil>=2.9.0.post0", "pyyaml-env-tag>=0.1", "pyyaml>=6.0.1", "regex>=2024.11.6", "requests>=2.32.3", "rich>=13.9.4", "setuptools>=75.4.0", "simplejson>=3.19.3", "six>=1.16.0", "toml>=0.10.2", "tomli>=2.1.0", "tomlkit>=0.13.2", "typeguard>=4.4.1", "typing-extensions>=4.12.2", "urllib3>=2.2.3", "watchdog>=6.0.0", "wheel>=0.45.0", "zipp>=3.21.0",]

[build-system]
requires = [ "hatchling",]
build-backend = "hatchling.build"

[project.optional-dependencies]
test = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
"black>=23.0.0",
"isort>=5.0.0",
"mypy>=1.0.0",
]
dev = [ "black>=24.10.0", "isort>=5.13.2", "mypy>=1.13.0", "pytest>=8.3.3",]

[project.scripts]
guardian = "guardian.cli:cli"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
addopts = "--cov=guardian --cov-report=xml --cov-report=term-missing"
[project.urls]
Homepage = "https://github.com/None/-home-thatch-dev-guardian"
Documentation = "https://-home-thatch-dev-guardian.readthedocs.io/"
Repository = "https://github.com/None/-home-thatch-dev-guardian.git"

[tool.black]
line-length = 88
target-version = ['py38']
target-version = [ "py38",]

[tool.isort]
profile = "black"
Expand All @@ -45,10 +34,10 @@ warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true

[project.urls]
Homepage = "https://github.com/None/-home-thatch-dev-guardian"
Documentation = "https://-home-thatch-dev-guardian.readthedocs.io/"
Repository = "https://github.com/None/-home-thatch-dev-guardian.git"
[tool.pytest.ini_options]
testpaths = [ "tests",]
python_files = [ "test_*.py",]
addopts = "--cov=guardian --cov-report=xml --cov-report=term-missing"

[tool.hatch.build.targets.wheel]
packages = ["src/guardian"]
packages = [ "src/guardian",]
155 changes: 155 additions & 0 deletions src/guardian/auth/handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# src/guardian/auth/handlers.py
from typing import Dict, Optional, Type
from dataclasses import dataclass
from datetime import datetime, timedelta
import logging
import requests
from bs4 import BeautifulSoup

@dataclass
class AuthResult:
"""Authentication result with session data"""
success: bool
session_data: Optional[Dict] = None
expires_at: Optional[datetime] = None
refresh_token: Optional[str] = None
error: Optional[str] = None

class BaseAuthHandler:
"""Base handler with common functionality"""

def __init__(self, site_config: Dict):
self.config = site_config
self.session = requests.Session()
self.logger = logging.getLogger(self.__class__.__name__)

async def authenticate(self) -> AuthResult:
"""Main authentication method"""
raise NotImplementedError

def _extract_form_data(self, html: str, form_id: str) -> Dict:
"""Extract form fields including hidden ones"""
soup = BeautifulSoup(html, 'html.parser')
form = soup.find('form', id=form_id)
if not form:
return {}

data = {}
for input_field in form.find_all('input'):
if 'name' in input_field.attrs:
data[input_field['name']] = input_field.get('value', '')
return data

class OAuthHandler(BaseAuthHandler):
"""Generic OAuth 2.0 handler with site-specific extensions"""

SITE_SPECIFICS = {
'google.com': {
'auth_url': 'https://accounts.google.com/o/oauth2/v2/auth',
'token_url': 'https://oauth2.googleapis.com/token',
'extra_params': {'prompt': 'consent'},
},
'github.com': {
'auth_url': 'https://github.com/login/oauth/authorize',
'token_url': 'https://github.com/login/oauth/access_token',
'headers': {'Accept': 'application/json'},
}
}

async def authenticate(self) -> AuthResult:
site = self.config['domain']
specifics = self.SITE_SPECIFICS.get(site, {})

try:
token = await self._oauth_flow(
auth_url=specifics.get('auth_url', self.config['auth_url']),
token_url=specifics.get('token_url', self.config['token_url']),
extra_params=specifics.get('extra_params', {}),
headers=specifics.get('headers', {})
)

return AuthResult(
success=True,
session_data={'token': token['access_token']},
expires_at=datetime.now() + timedelta(seconds=token['expires_in']),
refresh_token=token.get('refresh_token')
)

except Exception as e:
self.logger.error(f"OAuth authentication failed for {site}: {e}")
return AuthResult(success=False, error=str(e))

class SessionHandler(BaseAuthHandler):
"""Form-based session handler with site-specific adjustments"""

SITE_SPECIFICS = {
'facebook.com': {
'form_id': 'login_form',
'success_cookies': ['c_user'],
'extra_headers': {'User-Agent': 'Mozilla/5.0...'},
},
'twitter.com': {
'form_id': 'signin-form',
'success_cookies': ['auth_token'],
'csrf_token': True,
}
}

async def authenticate(self) -> AuthResult:
site = self.config['domain']
specifics = self.SITE_SPECIFICS.get(site, {})

try:
# Get login page
response = await self._get_login_page(
headers=specifics.get('extra_headers', {})
)

# Extract form data
form_data = self._extract_form_data(
response.text,
specifics.get('form_id', 'login_form')
)

# Add credentials
form_data.update(self.config['credentials'])

# Handle CSRF if needed
if specifics.get('csrf_token'):
form_data['csrf_token'] = self._extract_csrf_token(response.text)

# Submit login
login_response = await self._submit_login(form_data)

# Check success based on site-specific cookies
success_cookies = specifics.get('success_cookies', ['sessionid'])
if any(c in self.session.cookies for c in success_cookies):
return AuthResult(
success=True,
session_data={'cookies': self.session.cookies.get_dict()}
)

return AuthResult(
success=False,
error="Login failed - required cookies not found"
)

except Exception as e:
self.logger.error(f"Session authentication failed for {site}: {e}")
return AuthResult(success=False, error=str(e))

class HandlerRegistry:
"""Registry of authentication handlers"""

_handlers: Dict[str, Type[BaseAuthHandler]] = {
'oauth': OAuthHandler,
'session': SessionHandler
}

@classmethod
def get_handler(cls, auth_type: str, site_config: Dict) -> BaseAuthHandler:
"""Get appropriate handler for site"""
handler_class = cls._handlers.get(auth_type)
if not handler_class:
raise ValueError(f"Unsupported auth type: {auth_type}")
return handler_class(site_config)
30 changes: 15 additions & 15 deletions src/guardian/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# src/guardian/cli/__init__.py
"""
Guardian CLI Entry Point
This module initializes the Guardian CLI application and registers all available
commands. It provides the main CLI group and context management.
"""
Expand All @@ -27,6 +26,7 @@ def __init__(self):
self.config = ConfigService()
self.repo = RepoService()
self.security = SecurityService()
self.cli = cli

@click.group()
@click.version_option(
Expand All @@ -36,25 +36,21 @@ def __init__(self):
)
@click.pass_context
def cli(ctx):
"""Guardian: Git Authentication & Development Assistant
A comprehensive tool for managing Git authentication, security,
and development workflows.
"""
# Initialize context with our services
"""Guardian: Git Authentication & Development Assistant"""
ctx.obj = Context()

# Import command groups
# Import all command groups
from guardian.cli.commands import (
auth,
config,
format_cmd,
hooks,
init,
docs,
deps,
proxy
)

# Add repo separately until it's fully implemented
from guardian.cli.commands.repo import repo
from guardian.cli.commands.repo import repo # Import repo separately

# Define available commands with descriptions
COMMANDS = [
Expand All @@ -63,15 +59,19 @@ def cli(ctx):
(hooks, "Git hooks management"),
(format_cmd, "Code formatting"),
(init, "Initialize Guardian"),
(repo, "Repository management"),
(docs, "Documentation generation"),
(deps, "Dependencies management"),
(proxy, "Proxy server management")
]

# Register each command
# Register core commands
for command, description in COMMANDS:
# Set the help text if not already set
if not command.help and description:
if not getattr(command, 'help', None) and description:
command.help = description
cli.add_command(command)

# Register repo command separately
cli.add_command(repo)

if __name__ == "__main__":
cli()
7 changes: 6 additions & 1 deletion src/guardian/cli/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,24 @@
This module exports all available CLI commands. Add new commands here
after implementing them in their respective modules.
"""

from guardian.cli.commands.auth import auth
from guardian.cli.commands.config import config
from guardian.cli.commands.format import format_cmd
from guardian.cli.commands.hooks import hooks
from guardian.cli.commands.init import init
from guardian.cli.commands.docs import docs # New!
from guardian.cli.commands.deps import deps # New!
from guardian.cli.commands.proxy import proxy

__all__ = [
"auth",
"config",
"format_cmd",
"hooks",
"init",
"docs", # New!
"deps",
"proxy"
]

# Note: repo command is imported separately in cli/__init__.py until fully implemented
Loading

0 comments on commit 29872ad

Please sign in to comment.