Skip to content

Commit

Permalink
add jupyter-authenticator (#17)
Browse files Browse the repository at this point in the history
* add jupyter-authenticator

* add task-plugin and spi
  • Loading branch information
cyjseagull authored Aug 27, 2024
1 parent c4c9755 commit 20849e8
Show file tree
Hide file tree
Showing 49 changed files with 1,816 additions and 1 deletion.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ ext {
springfoxVersion = "3.0.0"
guavaVersion = "32.0.1-jre"
bcprovVersion = "1.78.1"
googleAutoServiceVersion = "1.1.1"
}

// check.dependsOn integrationTest
Expand Down Expand Up @@ -135,6 +136,7 @@ allprojects {
implementation logger, spring, spring_boot

implementation("org.projectlombok:lombok:${lombokVersion}")
implementation("com.google.auto.service:auto-service:${googleAutoServiceVersion}")
implementation("org.apache.commons:commons-lang3:${apacheCommonLangVersion}")
implementation("io.springfox:springfox-boot-starter:${springfoxVersion}")
implementation("com.google.guava:guava:${guavaVersion}")
Expand All @@ -144,6 +146,7 @@ allprojects {
testImplementation ("junit:junit:${junitVersion}")

annotationProcessor("org.projectlombok:lombok:${lombokVersion}")
annotationProcessor("com.google.auto.service:auto-service:${googleAutoServiceVersion}")
}

clean.doLast {
Expand Down
17 changes: 16 additions & 1 deletion db/wedpr_ddl.sql
Original file line number Diff line number Diff line change
Expand Up @@ -279,4 +279,19 @@ CREATE TABLE IF NOT EXISTS wedpr_permission (
create_by VARCHAR(20) NOT NULL DEFAULT '',
update_by VARCHAR(20) NOT NULL DEFAULT '',
PRIMARY KEY (permission_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC;

-- the jupyter table
create table if not exists `wedpr_jupyter_table`(
`id` varchar(64) not null comment "Jupyter资源的ID",
`owner` varchar(255) not null comment "Jupyter属主",
`agency` varchar(255) not null comment "Jupyter所属机构",
`access_entrypoint` text comment "Jupyter访问入口",
`setting` longtext comment "Jupyter配置",
`status` int comment "Jupyter状态",
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP comment "创建时间",
`last_update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP comment "更新时间",
PRIMARY KEY (id),
index owner_index(`owner`(128)),
index status_index(`status`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC;
Empty file added python/jupyter/CHANGELOG.md
Empty file.
2 changes: 2 additions & 0 deletions python/jupyter/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include CHANGELOG.md
include *requirements.txt
Empty file.
8 changes: 8 additions & 0 deletions python/jupyter/authenticator/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# __version__ should be updated using tbump, based on configuration in
# pyproject.toml, according to instructions in RELEASE.md.
#
__version__ = "1.0.0.dev"

# version_info looks like (1, 2, 3, "dev") if __version__ is 1.2.3.dev
version_info = tuple(int(p) if p.isdigit()
else p for p in __version__.split("."))
63 changes: 63 additions & 0 deletions python/jupyter/authenticator/wedpr_identity_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""
A JupyterHub authenticator class for use with WeDPR as an identity provider.
"""
from jupyter_server.auth.identity import IdentityProvider
from jupyter_server.auth.identity import User
import jwt
from .wedpr_token_content import WeDPRTokenContent
from tornado import web
from traitlets import Unicode, default
import os


class WeDPRIdentityProvider(IdentityProvider):
"""Authenticate local UNIX users with WeDPR-Auth"""
AUTH_TOKEN_FIELD = "Authorization"
AUTH_ALGORITHMS = ["HS256", "HS512", "HS384"]

auth_secret = Unicode("<generated>",
help="auth_secret").tag(config=True)
cookie_secret_file = Unicode(
'jupyterlab_authorization_secret', help="""File in which to store the authorization secret."""
).tag(config=True)

@default("cookie_secret_file")
def _cookie_secret_file_default(self):
if os.getenv("JUPYTER_AUTH_SECRET"):
return os.getenv("JUPYTER_AUTH_SECRET")
return "jupyter_lab_secret"

@default("auth_secret")
def _auth_secret_default(self):
secret_file = os.path.abspath(
os.path.expanduser(self.cookie_secret_file))
if os.path.exists(secret_file):
self.log.info(f"init auth secret, load from: {secret_file}")
with open(secret_file) as f:
return f.read().strip()
return None

def get_user(self, handler: web.RequestHandler):
"""Authenticate with jwt token, and return the username if login is successful.
Return None otherwise.
"""
try:
token = handler.request.headers.get(
WeDPRIdentityProvider.AUTH_TOKEN_FIELD, "")
except KeyError as e:
self.log.warning(
f"WeDPR auth failed for no authorization inforamtion defined! error: {e}")
return None
try:
token_payload = jwt.decode(
token, self.auth_secret, WeDPRIdentityProvider.AUTH_ALGORITHMS)
user_info = WeDPRTokenContent.deserialize(token_payload)
except Exception as e:
self.log.warning(
f"WeDPR auth failed for jwt verify failed! error: {e}")
return None
if user_info is None:
return None
user_name = user_info.get_user_information().username
self.log.info(f"WeDPR auth success, username: {user_name}")
return User(username=user_name)
32 changes: 32 additions & 0 deletions python/jupyter/authenticator/wedpr_token_content.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
import json
from .wedpr_user_information import WeDPRUserInformation


class WeDPRTokenContent:
USER_TOKEN_CLAIM = "user"

def __init__(self, token_content):
self._token_content = token_content

def get_token_content(self):
return self._token_content

def set_token_content(self, _token_content):
self._token_content = _token_content

def get_token_content_by_key(self, key):
if key in self._token_content:
return self._token_content[key]
return None

def get_user_information(self) -> WeDPRUserInformation:
user_info = self.get_token_content_by_key(
WeDPRTokenContent.USER_TOKEN_CLAIM)
if user_info is None:
return None
return WeDPRUserInformation(**json.loads(user_info))

@staticmethod
def deserialize(token_payload):
return WeDPRTokenContent(token_payload)
25 changes: 25 additions & 0 deletions python/jupyter/authenticator/wedpr_user_information.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from dataclasses import dataclass
import dataclasses
from dataclass_wizard import JSONWizard
import inspect


@dataclass
class WeDPRGroupInfo(JSONWizard):
groupId: str
groupName: str
groupAdminName: str


@dataclass
class WeDPRUserInformation:
username: str
roleName: str
permissions: list

def __init__(self, **kwargs):
names = set([f.name for f in dataclasses.fields(self)])
for k, v in kwargs.items():
if k in names:
setattr(self, k, v)
87 changes: 87 additions & 0 deletions python/jupyter/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# autoflake is used for autoformatting Python code
#
# ref: https://github.com/PyCQA/autoflake#readme
#
[tool.autoflake]
ignore-init-module-imports = true
remove-all-unused-imports = true
remove-duplicate-keys = true
#remove-unused-variables = true


# isort is used for autoformatting Python code
#
# ref: https://pycqa.github.io/isort/
#
[tool.isort]
profile = "black"


# black is used for autoformatting Python code
#
# ref: https://black.readthedocs.io/en/stable/
#
[tool.black]
skip-string-normalization = true
# target-version should be all supported versions, see
# https://github.com/psf/black/issues/751#issuecomment-473066811
target_version = [
"py38",
"py39",
"py310",
"py311",
]


# pytest is used for running Python based tests
#
# ref: https://docs.pytest.org/en/stable/
#
[tool.pytest.ini_options]
addopts = "--verbose --color=yes --durations=10 --cov=authenticator"
asyncio_mode = "auto"
testpaths = [
"authenticator/tests"
]


# pytest-cov / coverage is used to measure code coverage of tests
#
# ref: https://coverage.readthedocs.io/en/stable/config.html
#
[tool.coverage.run]
omit = [
"authenticator/tests/**",
]


# tbump is used to simplify and standardize the release process when updating
# the version, making a git commit and tag, and pushing changes.
#
# ref: https://github.com/your-tools/tbump#readme
#
[tool.tbump]
github_url = "https://github.com/wedpr/incubator-wedpr"

[tool.tbump.version]
current = "1.0.0.dev"
regex = '''
(?P<major>\d+)
\.
(?P<minor>\d+)
\.
(?P<patch>\d+)
(?P<pre>((a|b|rc)\d+)|)
\.?
(?P<dev>(?<=\.)dev\d*|)
'''

[tool.tbump.git]
message_template = "Bump to {new_version}"
tag_template = "{new_version}"

[[tool.tbump.file]]
src = "setup.py"

[[tool.tbump.file]]
src = "authenticator/_version.py"
7 changes: 7 additions & 0 deletions python/jupyter/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
traitlets>=4.3.2
jupyter_server>=2.2
pyjwt>=2
tornado
PyJWT
dataclass_wizard

74 changes: 74 additions & 0 deletions python/jupyter/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env python
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
# -----------------------------------------------------------------------------
# Minimal Python version sanity check (from IPython/Jupyterhub)
# -----------------------------------------------------------------------------

import sys

from setuptools import find_packages, setup
from setuptools.command.bdist_egg import bdist_egg


class bdist_egg_disabled(bdist_egg):
"""Disabled version of bdist_egg
Prevents setup.py install from performing setuptools' default easy_install,
which it should never ever do.
"""

def run(self):
sys.exit(
"Aborting implicit building of eggs. Use `pip install .` to install from source."
)


setup_args = dict(
name='authenticator',
packages=find_packages(),
version="1.0.0.dev",
description="Authenticator: Authenticate JupyterHub users with wedpr providers",
long_description_content_type="text/markdown",
author="Jupyter Development Team",
author_email="[email protected]",
url="https://jupyter.org",
license="BSD",
platforms="Linux, Mac OS X",
keywords=['Interactive', 'Interpreter', 'Shell', 'Web'],
python_requires=">=3.8",
include_package_data=True,
entry_points={
'jupyter_server.IdentityProviders': [
'wedpr-identity-provider = authenticator.wedpr_identity_provider.WeDPRIdentityProvider'
],
},
classifiers=[
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'Intended Audience :: Science/Research',
'License :: OSI Approved :: Apache License',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
],
)

setup_args['cmdclass'] = {
'bdist_egg': bdist_egg if 'bdist_egg' in sys.argv else bdist_egg_disabled,
}

setup_args['install_requires'] = install_requires = []
with open('requirements.txt') as f:
for line in f.readlines():
req = line.strip()
if not req or req.startswith(('-e', '#')):
continue
install_requires.append(req)


def main():
setup(**setup_args)


if __name__ == '__main__':
main()
9 changes: 9 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ project(":wedpr-components-scheduler").projectDir=file("wedpr-components/schedul
include "wedpr-components-security"
project(":wedpr-components-security").projectDir=file("wedpr-components/security")

include ":wedpr-components-task-plugin-api"
project(":wedpr-components-task-plugin-api").projectDir=file("wedpr-components/task-plugin/api")

include ":wedpr-components-task-plugin-shell"
project(":wedpr-components-task-plugin-shell").projectDir=file("wedpr-components/task-plugin/shell")

include ":wedpr-components-spi"
project(":wedpr-components-spi").projectDir=file("wedpr-components/spi")

include "wedpr-components-user"
project(":wedpr-components-user").projectDir=file("wedpr-components/user")

Expand Down
15 changes: 15 additions & 0 deletions wedpr-components/spi/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Apply the java-library plugin to add support for Java Library
plugins {
id 'java'
id 'com.github.sherter.google-java-format'
}
dependencies{
compile project(":wedpr-core-utils")
}
googleJavaFormat {
//toolVersion = '1.7'
options style: 'AOSP'
source = sourceSets*.allJava
include '**/*.java'
//source = *.allJava
}
Loading

0 comments on commit 20849e8

Please sign in to comment.