Skip to content

Commit

Permalink
Worked through pre-auth
Browse files Browse the repository at this point in the history
  • Loading branch information
athornton committed Jul 5, 2023
1 parent 2776f9f commit 969b860
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 35 deletions.
9 changes: 8 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ USER root
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
apt-get install -y git make

# First, rebuild the image. The pypi tag is very stale
# First, rebuild the image. The pypi tag is very stale and the released
# version does not work (but master does)
ARG SW=/opt/lsst/software
ARG V="0.5.1dev0"

Expand All @@ -16,6 +17,12 @@ RUN mkdir -p $SW && \
rm -rf /app
RUN git clone https://github.com/datopian/giftless /app && \
echo $V > /app/VERSION
# This is from an issue that was never turned into a PR, even though the
# issue contains the fix. IDK, man.
COPY patch/schema_hash_algo.diff /app
RUN cd /app && \
patch -p1 < schema_hash_algo.diff

RUN cd /app && \
make requirements.txt && \
pip install --upgrade --force-reinstall /app
Expand Down
24 changes: 24 additions & 0 deletions patch/schema_hash_algo.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
diff --git a/giftless/schema.py b/giftless/schema.py
--- a/giftless/schema.py
+++ b/giftless/schema.py
@@ -16,6 +16,12 @@
download = 'download'


+class HashAlgo(Enum):
+ """Batch operations
+ """
+ sha256 = 'sha256'
+
+
class RefSchema(ma.Schema): # type: ignore
"""ref field schema
"""
@@ -48,6 +54,7 @@
transfers = fields.List(fields.String, required=False, missing=['basic'])
ref = fields.Nested(RefSchema, required=False)
objects = fields.Nested(ObjectSchema, validate=validate.Length(min=1), many=True, required=True)
+ hash_algo = EnumField(HashAlgo, required=False)


batch_request_schema = BatchRequest()
103 changes: 69 additions & 34 deletions src/giftless_github_proxy_auth/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,23 @@
from .identity import Identity


class GiftlessGitHubPreAuth(giftless.auth.PreAuthorizedActionAuthenticator):
"""Set up action authorization; called by the Authenticator."""

def get_authz_query_token(
self,
identity: Identity,
org: str = "",
repo: str = "",
actions: Optional[Set[str]] = None,
oid: Optional[str] = None,
lifetime: Optional[int] = None,
) -> Dict[str, str]:
"""The rest of the backend does the auth work with GCS."""
return {}

def get_authz_query_params(
self, *args: Any, **kwargs: Any
) -> Dict[str, str]:
return {}


class GiftlessGitHubProxyAuthenticator(giftless.auth.Authenticator):
class GiftlessGitHubProxyAuthenticator(
giftless.auth.Authenticator, giftless.auth.PreAuthorizedActionAuthenticator
):
"""When a request is received, check to see whether that request is
authenticated with a personal access token that would give write access
to the repository the request is for.
authenticated with a GitHub personal access token that would give write
access to the repository the request is for.
Leave room for authenticating with a Gafaelfawr token.
"""

def __init__(self, *args: Any, **kwargs: Any) -> None:
self._expiration = datetime.timedelta(minutes=5)
if "expiration" in kwargs:
self._expiration = kwargs.pop("expiration")
self._identity: Optional[Identity] = None
if "identity" in kwargs:
self._identity = kwargs.pop("identity")
super().__init__(*args, **kwargs)
self._cache = AuthenticationCache()
self._loop = asyncio.new_event_loop()
Expand All @@ -52,36 +38,54 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
self._logger.setLevel(logging.DEBUG)
else:
self._logger.setLevel(logging.INFO)
self.preauth_handler = GiftlessGitHubPreAuth()
self.preauth_handler = self

def __call__(self, request: Request) -> Optional[Identity]:
# Get the repo name
parts = request.path.split("/")
org = parts[1]
repo_name = parts[2]
repo_path = org + "/" + repo_name
token: Optional[str] = None
if request.authorization is None:
self._logger.warning(
f"Request to {repo_path} has no Authorization header"
)
raise giftless.auth.Unauthorized("Authorization required")
# We have an Authorization header...
token = request.authorization.password
if self._identity is None:
self._logger.warning(
f"Request to {repo_path} has no Authorization header"
)
raise giftless.auth.Unauthorized("Authorization required")
else:
token = self._identity.token
else:
token = request.authorization.password
if token is None:
self._logger.warning(f"Request to {repo_path} has no auth token")
raise giftless.auth.Unauthorized("Authorization token required")
auth = github.Auth.Token(token)
# Check the cache
identity = asyncio.run(self._cache.check(token))
if (
self._identity is not None
and identity is not None
and identity.id != self._identity.id
):
self._logger.warning(
f"Token is for {identity.id}, not {self._identity.id}"
)
return None
if identity is not None:
# We found the token in our cache.
id_state = identity.check_repo(repo_path)
self._identity = identity
if id_state is False:
self._logger.warning(f"Token not authorized for {repo_path}")
return None
if id_state is True:
elif id_state is True:
self._logger.debug(f"Token authorized for {repo_path}")
return identity
# See whether token is valid
else:
# id_state is None
self._logger.debug("Token for {identity.id} not in cache")
# Token is not in cache; see whether it is valid
self._logger.info(f"Checking validity for token for {repo_path}")
if token.startswith("gt-"):
# Gafaelfawr token
Expand All @@ -99,9 +103,10 @@ def __call__(self, request: Request) -> Optional[Identity]:
return None
if identity is None:
# We have a valid token for a user
identity = Identity(name=login)
identity = Identity(name=login, token=token)
self._logger.debug(f"Storing token for {login}")
asyncio.run(self._cache.add(token, identity))
self._identity = identity
# Get correct repository
repo = gh.get_repo(repo_path)
# See whether token gives us write access
Expand All @@ -121,6 +126,36 @@ def __call__(self, request: Request) -> Optional[Identity]:
identity.deauthorize_for_repo(repo_path)
return None

def get_authz_header(
self,
identity: Optional[Identity] = None,
org: str = "",
repo: str = "",
actions: Optional[Set[str]] = None,
oid: Optional[str] = None,
lifetime: Optional[int] = None,
) -> Dict[str, str]:
if identity is None:
if self._identity is None:
self._logger.warning("No identity found for authorization")
return {}
identity = self._identity
"""We can use this as a pre-auth class too."""
return {"Authorization": f"Bearer {identity.token}"}

def get_authz_query_params(
self,
identity: Optional[Identity] = None,
org: str = "",
repo: str = "",
actions: Optional[Set[str]] = None,
oid: Optional[str] = None,
lifetime: Optional[int] = None,
) -> Dict[str, str]:
return self.get_authz_header(
identity, org, repo, actions, oid, lifetime
)


def factory(**options: Any) -> GiftlessGitHubProxyAuthenticator:
"""Allow read/write via proxy auth class."""
Expand Down
2 changes: 2 additions & 0 deletions src/giftless_github_proxy_auth/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ class Identity(giftless.auth.Identity):
def __init__(
self,
name: str,
token: str,
expiration: datetime.timedelta = datetime.timedelta(minutes=15),
) -> None:
self.name = name
self.id = name
self.token = token
self._expiration = expiration
self._auth: Dict[str, RepoAccess] = {}
self._logger = logging.getLogger(__name__)
Expand Down

0 comments on commit 969b860

Please sign in to comment.