Coder Social home page Coder Social logo

jupyterhub / oauthenticator Goto Github PK

View Code? Open in Web Editor NEW
404.0 20.0 360.0 1.58 MB

OAuth + JupyterHub Authenticator = OAuthenticator

Home Page: https://oauthenticator.readthedocs.io

License: BSD 3-Clause "New" or "Revised" License

Python 100.00%
jupyterhub authenticator oauth-client github-oauth jupyter globus

oauthenticator's Introduction

OAuth + JupyterHub Authenticator = OAuthenticator ❤️

Documentation build status GitHub Workflow Status - Test Latest PyPI version Latest conda-forge version GitHub Discourse Gitter

OAuth is a token based login mechanism that doesn't rely on a username and password mapping. In order to use this login mechanism with JupyerHub the login handlers need to be overridden. OAuthenticator overrides these handlers for the common OAuth2 identity providers allowing them to be plugged in and used with JupyterHub.

The following authentication services are supported through their own authenticator: Auth0, Azure AD, Bitbucket, CILogon, FeiShu, GitHub, GitLab, Globus, Google, MediaWiki, OpenShift.

There is also a GenericAuthenticator that can be configured with any OAuth 2.0 identity provider or can be used to create a new authenticator class when additional customization is needed.

Installation

The installation guide can be found in the docs.

The docs also provide example setups for different OAuth2 identity providers.

Running tests

To run the tests locally, first setup a development environment as described in CONTRIBUTING.md, and then do:

pytest -v ./oauthenticator/tests/

Or you run a specific test file with:

pytest -v ./oauthenticator/tests/<test-file-name>

oauthenticator's People

Contributors

amit1rrr avatar athornton avatar benjimin avatar betatim avatar briedel avatar consideratio avatar dependabot[bot] avatar georgianaelena avatar grahamdumpleton avatar jbweston avatar jpolchlo avatar linkcd avatar manics avatar mike-barry-gmo avatar minrk avatar missingcharacter avatar mklan avatar nickolausds avatar pre-commit-ci[bot] avatar rgbkrk avatar satra avatar stevenwuyinze avatar sunnielyu avatar thomafred avatar user1m avatar vindvaki avatar willingc avatar wseaton avatar yuvipanda avatar zh3w4ng avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

oauthenticator's Issues

Gitlab authenticator cannot be specified in jupyterhub config file

I am having an issue getting the jupyterhub config file setup to use my locally hosted Gitlab CE for authentication.

# Authenticate users with GitHub OAuth
c.JupyterHub.authenticator_class = 'oauthenticator.gitlab.GitlabOAuthenticator'
#c.GitLabOAuthenticator.oauth_callback_url = os.environ['OAUTH_CALLBACK_URL']
#c.GitlabOAuthenticator.client_id = os.environ['GITLAB_CLIENT_ID']
#c.GitlabOAuthenticator.client_secret = os.environ['GITLAB_CLIENT_SECRET']

I am installing the master branch of oauthenticator since the tags all seem to exclude gitlab:

pip install git+https://github.com/jupyterhub/oauthenticator.git

Results in:

In [4]: oauthenticator.__version__
Out[4]: '0.6.0dev'

The error I get when running jupyter hub is:

jupyterhub        | [C 2017-02-21 18:18:00.084 JupyterHub application:90] Bad config encountered during initialization:
jupyterhub        | [C 2017-02-21 18:18:00.084 JupyterHub application:91] The 'authenticator_class' trait of <jupyterhub.app.JupyterHub object at 0x7f98b4855c50> instance must be a type, but 'oauthenticator.gitlab.GitlabOAuthenticator' could not be imported

If I try just importing oauthenticator.gitlab.GitlabOAuthenticator in an ipython shell I get a similar error. Trying to figure out why it's not importable...

In [1]: import oauthenticator

In [2]: import oauthenticator.gitlab

In [3]: import oauthenticator.gitlab.GitLabOAuthenticator
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-3-7b154d3365a5> in <module>()
----> 1 import oauthenticator.gitlab.GitLabOAuthenticator

ImportError: No module named 'oauthenticator.gitlab.GitLabOAuthenticator'; 'oauthenticator.gitlab' is not a package

Edit: revisiting, not being able to import the class isn't a problem. However, I still cannot get the config file to not throw an error when trying to use gitlab.

GoogleOAuthenticator AuthError

When authenticating with the GoogleOAuthenticator I get a 500 Server error caused by the following:

HTTPResponse(_body=None,buffer=None,code=599,effective_url='https://accounts.google.com/o/oauth2/token',error=SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:645)')

Still trying to determine the root cause, but happy to get any pointers to make debugging easier.

Can't generate "Authorized JavaScript origins" with "/hub/oauth_callback"

Hi ! I have a problem when i try to generated the "Authorized JavaScript origins" with "/hub/oauth_callback". At the moment to use the url http[s]://[your-host]/hub/oauth_callback this return error 404.

(I installed oauthenticator with pip and pip3)

This is the log of Jupyterhub:

[I 2017-01-18 01:32:46.854 JupyterHub app:724] Loading cookie_secret from /data/hub_progra/cookie_secret
[W 2017-01-18 01:32:46.895 JupyterHub app:365]
    Generating CONFIGPROXY_AUTH_TOKEN. Restarting the Hub will require restarting the proxy.
    Set CONFIGPROXY_AUTH_TOKEN env or JupyterHub.proxy_auth_token config to avoid this message.

[W 2017-01-18 01:32:46.991 JupyterHub app:864] No admin users, admin interface will be unavailable.
[W 2017-01-18 01:32:46.991 JupyterHub app:865] Add any administrative users to `c.Authenticator.admin_users` in config.
[I 2017-01-18 01:32:46.992 JupyterHub app:892] Not using whitelist. Any authenticated user will be allowed.
[I 2017-01-18 01:32:47.008 JupyterHub app:1456] Hub API listening on http://127.0.0.1:8081/hub/
[W 2017-01-18 01:32:47.012 JupyterHub app:1177] Running JupyterHub without SSL.  I hope there is SSL termination happening somewhere else...
[I 2017-01-18 01:32:47.013 JupyterHub app:1179] Starting proxy @ http://*:8027/
01:32:47.210 - info: [ConfigProxy] Proxying http://*:8027 to http://127.0.0.1:8081
01:32:47.215 - info: [ConfigProxy] Proxy API at http://127.0.0.1:8028/api/routes
[I 2017-01-18 01:32:47.318 JupyterHub app:1488] JupyterHub is now running at http://127.0.0.1:8027/
[I 2017-01-18 01:32:53.029 JupyterHub log:100] 302 GET / (@190.96.59.193) 1.60ms
[I 2017-01-18 01:32:53.056 JupyterHub log:100] 302 GET /hub (@190.96.59.193) 0.48ms
[I 2017-01-18 01:32:53.083 JupyterHub log:100] 302 GET /hub/ (@190.96.59.193) 1.00ms
[I 2017-01-18 01:32:53.106 JupyterHub log:100] 302 GET /login (@190.96.59.193) 0.66ms
[I 2017-01-18 01:32:53.159 JupyterHub log:100] 200 GET /hub/login (@190.96.59.193) 29.34ms
[I 2017-01-18 01:32:55.489 JupyterHub log:100] 302 GET / (@190.96.59.193) 1.53ms
[I 2017-01-18 01:32:55.507 JupyterHub log:100] 302 GET /hub (@190.96.59.193) 0.42ms
[I 2017-01-18 01:32:55.532 JupyterHub log:100] 302 GET /hub/ (@190.96.59.193) 0.76ms
[I 2017-01-18 01:32:55.552 JupyterHub log:100] 302 GET /login (@190.96.59.193) 0.75ms
[I 2017-01-18 01:32:55.575 JupyterHub log:100] 200 GET /hub/login (@190.96.59.193) 1.59ms
[W 2017-01-18 01:32:57.961 JupyterHub log:100] 404 GET /hub/oauth_callback (@190.96.59.193) 8.28ms

And this my jupyterhub_config.py:

c = get_config()

import os
pjoin = os.path.join

runtime_dir = os.path.join('/data/hub_progra')

# Port to public
c.JupyterHub.port = 8027
c.JupyterHub.confirm_no_ssl = True

# JupyterHub cookie secret and state db
c.JupyterHub.cookie_secret_file = pjoin(runtime_dir, 'cookie_secret')
c.JupyterHub.db_url = pjoin(runtime_dir, 'jupyterhub.sqlite')

# put the log file in /var/log
c.JupyterHub.extra_log_file = '/data/hub_progra/log/jupyterhub.log'


#c.JupyterHub.authenticator_class = 'oauthenticator.GoogleOAuthenticator'
#c.GoogleOAuthenticator.client_id = '571090182695-drsa31bg1l3ib0dlr23bb9hkp6qaaer8.apps.googleusercontent.com'
#c.GoogleOAuthenticator.client_secret = '3aaIWT9vYXcVF8bXB5C2-cBm'
#c.GoogleOAuthenticator.oauth_callback_url = 'http://student.cnf.cl'


# Force the proxy to only listen to connections to 127.0.0.1
#c.JupyterHub.ip = '127.0.0.1'

Thanks !

Add a MediawikiOAuthenticator

Hello! I've been using https://github.com/yuvipanda/mwoauthenticator at Wikimedia's test installation, and would like to get it merged 'upstream' (here). Is this repo accepting new such authenticators, or should it continue to live in a separate repo? If this is acceptable here I'll do the work in integrating it and send a PR. Wanted to ask before I expend effort :)

Thanks!

Make GITHUB_HOST configurable in jupyterhub_config.py

When trying to run jupyterhub as a service, environment variables get stripped on Ubuntu. It would be nice where the github_host setting is an additional setting in the jupyterhub_config.py file, e.g.

c.GitHubOAuthenticator.github_host = 'githubenterprise.org.com'

Add team whitelisting for GitHubOAuthenticator and GitLabOAuthenticator

BitBucketOAuthenticator has team whitelisting, so it would seem reasonable to also add this for Gitlab and Github.

I am already working towards submitting a pull request, but am creating this issue here to serve as a centre for discussion and to avoid spamming the jupyterhub gitter channel.

"oauth callback made without a token" on attempting to log in with Google OAuth

I am successfully seeing the Google OAuth permission page, where I allow the application access to my google profile and e-mail address. When I select 'allow', the browser returns to Jupyterhub server and displays 400: bad request and "oauth callback made without a token". I have oauthenticator-0.5.1, installed from github.

Edit: Callback URL clearly shows a code:

https://<domain>/jupyter/hub/oauth_callback?code=4/ljNDF1aRSYYjmWs6qf0BgncHtbLDxlZk5I5-PERaoQA&authuser=0&session_state=7059f12686a635b1d2da35e21189f371f6f11a13..dfe6&prompt=consent#

I see same behavior with oauthenticator 0.6.0.dev and jupyterhub 0.8.0.dev.

Any ideas where I might look for a problem?

Allowing multiple hosted domain subdomains

I manage a JupyterHub at George Washington University and we currently use GitHub oauth for logins but we're looking at switching to Google oauth and using the university's google apps accounts.

Due to some historical quirk, students are assigned a gwmail.gwu.edu address while faculty/staff have email.gwu.edu -- we want faculty and students to both be able to access the hub. Are there security concerns I'm missing if I change https://github.com/jupyterhub/oauthenticator/blob/master/oauthenticator/google.py#L111 and remove the @ from the endswith condition?

I'm not sure if this is a particularly common problem, so I'm happy to maintain a fork for our needs, but if it would be useful I'm also happy to contribute it back in, be it the potential solution above or something else.

github oauth redirect loop

I am using github oauth and from time to time users are stuck at the following page of authorization. When users click "Authorize application", it just loops back to the same page.
Affected browsers include firefox of windows and chrome of mac.

screen shot 2016-09-07 at 10 56 25 am

And surprisingly, it will work again if a user removes all the cookies of the browser or use the incognito mode of the browser.

Here is the log of jupyterhub (domain name and IP censored)

[I 2016-09-07 10:55:23.271 JupyterHub oauth2:37] oauth redirect: 'https://<my-domain>/hub/oauth_callback'
[I 2016-09-07 10:55:23.285 JupyterHub log:100] 302 GET /hub/oauth_login (@141.114.xxx.xxx) 7.04ms
[I 2016-09-07 10:55:34.592 JupyterHub log:100] 302 GET /hub/oauth_callback?code=208c6d5e429e6563d0cd (@141.114.xxx.xxx) 341.57ms
[I 2016-09-07 10:55:34.651 JupyterHub log:100] 302 GET /hub/home (@141.114.xxx.xxx) 2.25ms
[I 2016-09-07 10:55:34.660 JupyterHub oauth2:37] oauth redirect: 'https://<my-domain>/hub/oauth_callback'
[I 2016-09-07 10:55:34.665 JupyterHub log:100] 302 GET /hub/oauth_login?next=%2Fhub%2Fhome (@141.114.xxx.xxx) 2.03ms
[I 2016-09-07 10:55:35.240 JupyterHub log:100] 302 GET /hub/oauth_callback?code=d9a437a41948e257046c (@141.114.xxx.xxx) 300.78ms
[I 2016-09-07 10:55:35.271 JupyterHub log:100] 302 GET /hub/home (@141.114.xxx.xxx) 1.62ms
[I 2016-09-07 10:55:35.281 JupyterHub oauth2:37] oauth redirect: 'https://<my-domain>/hub/oauth_callback'
[I 2016-09-07 10:55:35.287 JupyterHub log:100] 302 GET /hub/oauth_login?next=%2Fhub%2Fhome (@141.114.xxx.xxx) 2.70ms
[I 2016-09-07 10:55:35.987 JupyterHub log:100] 302 GET /hub/oauth_callback?code=c7a62bfc8444e8c18ac9 (@141.114.xxx.xxx) 338.74ms
[I 2016-09-07 10:55:36.110 JupyterHub log:100] 302 GET /hub/home (@141.114.xxx.xxx) 1.89ms
[I 2016-09-07 10:55:36.120 JupyterHub oauth2:37] oauth redirect: 'https://<my-domain>/hub/oauth_callback'
[I 2016-09-07 10:55:36.125 JupyterHub log:100] 302 GET /hub/oauth_login?next=%2Fhub%2Fhome (@141.114.xxx.xxx) 2.21ms
[I 2016-09-07 10:55:36.760 JupyterHub log:100] 302 GET /hub/oauth_callback?code=ee1da425ea07b62dcb35 (@141.114.xxx.xxx) 370.93ms
[I 2016-09-07 10:55:36.840 JupyterHub log:100] 302 GET /hub/home (@141.114.xxx.xxx) 1.79ms
[I 2016-09-07 10:55:36.855 JupyterHub oauth2:37] oauth redirect: 'https://<my-domain>/hub/oauth_callback'
[I 2016-09-07 10:55:36.862 JupyterHub log:100] 302 GET /hub/oauth_login?next=%2Fhub%2Fhome (@141.114.xxx.xxx) 2.76ms
[I 2016-09-07 10:55:37.529 JupyterHub log:100] 302 GET /hub/oauth_callback?code=76426adc089baf3de134 (@141.114.xxx.xxx) 368.36ms
[I 2016-09-07 10:55:37.573 JupyterHub log:100] 302 GET /hub/home (@141.114.xxx.xxx) 1.97ms
[I 2016-09-07 10:55:37.582 JupyterHub oauth2:37] oauth redirect: 'https://<my-domain>/hub/oauth_callback'
[I 2016-09-07 10:55:37.588 JupyterHub log:100] 302 GET /hub/oauth_login?next=%2Fhub%2Fhome (@141.114.xxx.xxx) 2.38ms
[I 2016-09-07 10:55:38.268 JupyterHub log:100] 302 GET /hub/oauth_callback?code=31bacb183a4f70259f25 (@141.114.xxx.xxx) 362.89ms
[I 2016-09-07 10:55:38.284 JupyterHub log:100] 302 GET /hub/home (@141.114.xxx.xxx) 1.94ms
[I 2016-09-07 10:55:38.293 JupyterHub oauth2:37] oauth redirect: 'https://<my-domain>/hub/oauth_callback'
[I 2016-09-07 10:55:38.298 JupyterHub log:100] 302 GET /hub/oauth_login?next=%2Fhub%2Fhome (@141.114.xxx.xxx) 2.00ms
[I 2016-09-07 10:55:38.946 JupyterHub log:100] 302 GET /hub/oauth_callback?code=cae3bc82b4a1b82e3766 (@141.114.xxx.xxx) 299.67ms
[I 2016-09-07 10:55:39.037 JupyterHub log:100] 302 GET /hub/home (@141.114.xxx.xxx) 2.05ms
[I 2016-09-07 10:55:39.047 JupyterHub oauth2:37] oauth redirect: 'https://<my-domain>/hub/oauth_callback'
[I 2016-09-07 10:55:39.053 JupyterHub log:100] 302 GET /hub/oauth_login?next=%2Fhub%2Fhome (@141.114.xxx.xxx) 2.90ms
[I 2016-09-07 10:55:39.698 JupyterHub log:100] 302 GET /hub/oauth_callback?code=7ad464b1ce538dd23f8b (@141.114.xxx.xxx) 383.34ms
[I 2016-09-07 10:55:39.712 JupyterHub log:100] 302 GET /hub/home (@141.114.xxx.xxx) 1.97ms
[I 2016-09-07 10:55:39.736 JupyterHub oauth2:37] oauth redirect: 'https://<my-domain>/hub/oauth_callback'
[I 2016-09-07 10:55:39.744 JupyterHub log:100] 302 GET /hub/oauth_login?next=%2Fhub%2Fhome (@141.114.xxx.xxx) 4.29ms
[I 2016-09-07 10:55:40.486 JupyterHub log:100] 302 GET /hub/oauth_callback?code=8729856d5658216b0c28 (@141.114.xxx.xxx) 360.27ms
[I 2016-09-07 10:55:40.601 JupyterHub log:100] 302 GET /hub/home (@141.114.xxx.xxx) 2.20ms
[I 2016-09-07 10:55:40.613 JupyterHub oauth2:37] oauth redirect: 'https://<my-domain>/hub/oauth_callback'
[I 2016-09-07 10:55:40.619 JupyterHub log:100] 302 GET /hub/oauth_login?next=%2Fhub%2Fhome (@141.114.xxx.xxx) 2.22ms
[I 2016-09-07 10:55:41.399 JupyterHub log:100] 302 GET /hub/oauth_callback?code=a0720a4900cb6d0df85b (@141.114.xxx.xxx) 339.07ms
[I 2016-09-07 10:55:41.437 JupyterHub log:100] 302 GET /hub/home (@141.114.xxx.xxx) 1.75ms
[I 2016-09-07 10:55:41.447 JupyterHub oauth2:37] oauth redirect: 'https://<my-domain>/hub/oauth_callback'
[I 2016-09-07 10:55:41.451 JupyterHub log:100] 302 GET /hub/oauth_login?next=%2Fhub%2Fhome (@141.114.xxx.xxx) 1.66ms

hard-coded gitlab api version 'v3' will need to handle switch to 'v4'

Just flagging that 'v3' of the gitlab api is hard-coded here.

From here, 'v3' was deprecated earlier this year and support for it was removed since version 9.5 of Gitlab release in August 2017. Apparently, only 'v4' will be handled from version 9.5 onwards. Having said that, calls against the v3 api still appear to work for gitlab.com which is running version 9.5.4.

Users that are using a self-hosted gitlab instances may have both old and newer versions of gitlab, so perhaps parameterising the version using something like GITLAB_API_VERSION may make sense, with the default set to 4 of v4.

Default callback_url seems to clash with OAuth implementation in 0.8

Currently authenticators that are authenticating to external services are asked to use /hub/oauth_callback as the URL for external service to call back to us. However, with JupyterHub 0.8, this seems to be the same URL that we use for singleuser servers calling in to the hub to Authenticate. This means that when the callback from external service happens, OAuthenticator's handler isn't called, and you get a 400 error with OAuth callback made without a code

Issue with Gitlab : trait of instance must be a type ... could not be imported

docker version 1.12
jupyterhub:0.6.1
python3.5
oauthenticator; 0.4.1
dockerspawner: 0.4.0

Trying to get Gitlab to work, unable to get past:
[C 2016-08-11 20:54:16.140 JupyterHub application:78] The 'authenticator_class' trait of <jupyterhub.app.JupyterHub object at 0x7f53c17f2b00> instance must be a type, but oauthenticator.gitlab.GitLabOAuthenticator' could not be imported

jupyter config:
# Configuration file for JupyterHub
import os

c = get_config()

# We rely on environment variables to configure JupyterHub so that we
# avoid having to rebuild the JupyterHub container every time we change a
# configuration parameter.

# Spawn single-user servers as Docker containers
c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'
# Spawn containers from this image
c.DockerSpawner.container_image = os.environ['DOCKER_NOTEBOOK_IMAGE']
# JupyterHub requires a single-user instance of the Notebook server, so we
# default to using the `start-singleuser.sh` script included in the
# jupyter/docker-stacks *-notebook images as the Docker run command when
# spawning containers.  Optionally, you can override the Docker run command
# using the DOCKER_SPAWN_CMD environment variable.
spawn_cmd = os.environ.get('DOCKER_SPAWN_CMD', "start-singleuser.sh")
c.DockerSpawner.extra_create_kwargs.update({ 'command': spawn_cmd })
# Connect containers to this Docker network
network_name = os.environ['DOCKER_NETWORK_NAME']
c.DockerSpawner.use_internal_ip = True 
c.DockerSpawner.network_name = network_name
# Pass the network name as argument to spawned containers
c.DockerSpawner.extra_host_config = { 'network_mode': network_name }
# Explicitly set notebook directory because we'll be mounting a host volume to
# it.  Most jupyter/docker-stacks *-notebook images run the Notebook server as
# user `jovyan`, and set the notebook directory to `/home/jovyan/work`.
# We follow the same convention.
notebook_dir = os.environ.get('DOCKER_NOTEBOOK_DIR') or '/home/jovyan/work'
c.DockerSpawner.notebook_dir = notebook_dir
# Mount the real user's Docker volume on the host to the notebook user's
# notebook directory in the container
c.DockerSpawner.volumes = { 'jupyterhub-user-{username}': notebook_dir }
c.DockerSpawner.extra_create_kwargs.update({ 'volume_driver': 'datascience' })
# Remove containers once they are stopped
c.DockerSpawner.remove_containers = True
# For debugging arguments passed to spawned containers
c.DockerSpawner.debug = True

# User containers will access hub by container name on the Docker network
c.JupyterHub.hub_ip = 'jupyterhub'
c.JupyterHub.hub_port = 8080

# TLS config
c.JupyterHub.port = 443
c.JupyterHub.ssl_key = os.environ['SSL_KEY']
c.JupyterHub.ssl_cert = os.environ['SSL_CERT']

# Authenticate users with GitHub OAuth
#c.JupyterHub.authenticator_class = 'oauthenticator.GitHubOAuthenticator'
c.JupyterHub.authenticator_class = 'oauthenticator.gitlab.GitLabOAuthenticator'
#c.GitHubOAuthenticator.oauth_callback_url = os.environ['OAUTH_CALLBACK_URL']
c.GitLabOAuthenticator.oauth_callback_url = os.environ['OAUTH_CALLBACK_URL']
c.GitLabOAuthenticator.client_id = os.environ['GITLAB_CLIENT_ID']
c.GitLabOAuthenticator.client_secret = os.environ['GITLAB_CLIENT_SECRET']

# Persist hub data on volume mounted inside container
data_dir = os.environ.get('DATA_VOLUME_CONTAINER', '/data')
c.JupyterHub.db_url = os.path.join('sqlite:///', data_dir, 'jupyterhub.sqlite')
c.JupyterHub.cookie_secret_file = os.path.join(data_dir,
    'jupyterhub_cookie_secret')

# Whitlelist users and admins
c.Authenticator.whitelist = whitelist = set()
c.Authenticator.admin_users = admin = set()
c.LocalAuthenticator.create_system_users = True 
c.JupyterHub.admin_access = True
pwd = os.path.dirname(__file__)
with open(os.path.join(pwd, 'userlist')) as f:
    for line in f:
        if not line:
            continue
        parts = line.split()
        name = parts[0]
        whitelist.add(name)
        if len(parts) > 1 and parts[1] == 'admin':
            admin.add(name)


Auth0 authenticator is not accessible

I notice that the oauthenticator.auth0.Auth0OAuthenticator is not accessible in the latest release of oauthenticator.

I cloned from master to make sure it was not just the latest release. Confirmed the oauth submodule does not show up.

Are there plans to expose that class in the next release?

Unable to logout (GitHub OAuth authenticator)

I am not sure if it's a problem of my setup or something else, but I am apparently unable to logout.
I performed setup of the GitHub application, everything works correctly, logged in.
When I try to logout, I get redirected correctly, and then get logged in again

[I 2017-03-06 11:30:52.002 JupyterHub login:19] User logged out: stefanoborini
[I 2017-03-06 11:30:52.010 JupyterHub log:100] 302 GET /simphony-remote/hub/logout (stefanoborini@***) 5.27ms
[W 2017-03-06 11:30:52.040 JupyterHub pages:32] Disallowing redirect outside JupyterHub: ''
[I 2017-03-06 11:30:52.044 JupyterHub log:100] 302 GET /simphony-remote/hub/ (@***) 1.18ms
[I 2017-03-06 11:30:52.077 JupyterHub log:100] 302 GET /simphony-remote/oauth_login (@***) 0.90ms
[I 2017-03-06 11:30:52.109 JupyterHub oauth2:37] oauth redirect: 'https://***/simphony-remote/hub/oauth_callback'
[I 2017-03-06 11:30:52.113 JupyterHub log:100] 302 GET /simphony-remote/hub/oauth_login (@***) 1.29ms
[I 2017-03-06 11:30:53.578 JupyterHub log:100] 302 GET /simphony-remote/hub/oauth_callback?code=*** (@***) 1098.77ms
[I 2017-03-06 11:31:30.375 JupyterHub log:100] 302 GET /simphony-remote/hub (@***) 0.59ms

Is this known behavior?

OAuthenticator version 0.5.1
JHub version 0.7.0 dev

Implicit oauth2

Hi, I've been trying to implement my own authenticator against an API that uses implicit Oauth2, it means that I don't have a service to request the token, but instead, when I request the authorize service, my own redirect (I'm using the specified on the README: localhost:8000/hub/oauth_callback) comes with the access_token. To be more explicit:

Authorize url:
https://oauth2server/authorize?response_type=token&redirect_uri=http://localhost:8000/hub/oauth_callback&client_id=the_clientid&scope=read

This is the response header for that request:
http://localhost:8000/hub/oauth_callback#access_token=theaccesstoken&token_type=bearer&expires_in=108

My question is: Is there a way to implement my own oauthenticator with this type of oauth2 grant? The authenticators you already (github, google, bitbucket) always use the explicit way. Thanks in advance

Working configuration for generic authenticator with Keycloak

I have tried to configure Jupyterhub to use the generic OAuth2 authentication mechanism with Keycloak as OAuth2 sever. However, it never redirects to it for authentication. Could you please support me and provide a sample configuration that is supposed to work? Or is there anything that I'm obviously doing wrong?

Thank you very much!
Dennis

OAuthenticator configuration in jupyterhub_config.py:

from oauthenticator.generic import GenericOAuthenticator
c.JupyterHub.authenticator_class = GenericOAuthenticator

c.GenericOAuthenticator.oauth_callback_url = 'https://example.com:8443/auth/realms/testrealm/protocol/openid-connect/auth'
c.GenericOAuthenticator.client_id = 'oauth-secret'
c.GenericOAuthenticator.client_secret = 'asfdsdf-dfdf-fhhfh-aed7-asdafsdfsaf'
c.GenericOAuthenticator.token_url = 'https://example.com:8443/auth/realms/testrealm/protocol/openid-connect/token'
c.GenericOAuthenticator.userdata_url = 'https://141.72.18.234:8443/auth/realms/testrealm/protocol/openid-connect/userinfo'

Keycloak Endpoints:

{
	"issuer": "https://example.com:8443/auth/realms/testrealm",
	"authorization_endpoint": "https://example.com:8443/auth/realms/testrealm/protocol/openid-connect/auth",
	"token_endpoint": "https://example.com:8443/auth/realms/testrealm/protocol/openid-connect/token",
	"token_introspection_endpoint": "https://example.com:8443/auth/realms/testrealm/protocol/openid-connect/token/introspect",
	"userinfo_endpoint": "https://example.com:8443/auth/realms/testrealm/protocol/openid-connect/userinfo",
	"end_session_endpoint": "https://example.com:8443/auth/realms/testrealm/protocol/openid-connect/logout",
	"jwks_uri": "https://example.com:8443/auth/realms/testrealm/protocol/openid-connect/certs",
	"check_session_iframe": "https://example.com:8443/auth/realms/testrealm/protocol/openid-connect/login-status-iframe.html",
	"grant_types_supported": ["authorization_code", "implicit", "refresh_token", "password", "client_credentials"],
	"response_types_supported": ["code", "none", "id_token", "token", "id_token token", "code id_token", "code token", "code id_token token"],
	"subject_types_supported": ["public", "pairwise"],
	"id_token_signing_alg_values_supported": ["RS256"],
	"userinfo_signing_alg_values_supported": ["RS256"],
	"request_object_signing_alg_values_supported": ["none", "RS256"],
	"response_modes_supported": ["query", "fragment", "form_post"],
	"registration_endpoint": "https://example.com:8443/auth/realms/testrealm/clients-registrations/openid-connect",
	"token_endpoint_auth_methods_supported": ["private_key_jwt", "client_secret_basic", "client_secret_post"],
	"token_endpoint_auth_signing_alg_values_supported": ["RS256"],
	"claims_supported": ["sub", "iss", "auth_time", "name", "given_name", "family_name", "preferred_username", "email"],
	"claim_types_supported": ["normal"],
	"claims_parameter_supported": false,
	"scopes_supported": ["openid", "offline_access"],
	"request_parameter_supported": true,
	"request_uri_parameter_supported": true
}

Make oauth scopes configurable

OAuth scopes should be a configurable trait on OAuthenticator classes, so that deployments can request specific permissions when passing auth state to spawners.

It should start by adding the scopes as a configurable:

class OAuthenticator():
    ....
    scopes = List(Unicode(), config=True)

Then the scopes will need to be passed to the LoginHandler.

404 during callback

GitHub is returning a 404 during the auth procedure.

Following the example here: http://jupyterhub.readthedocs.io/en/latest/config-examples.html#example-with-github-oauth

c.JupyterHub.authenticator_class = 'oauthenticator.LocalGitHubOAuthenticator'
c.GitHubOAuthenticator.oauth_callback_url = 'https://jupyter.mydomain.com/hub/oauth_callback'
c.GitHubOAuthenticator.client_id = '----client_id-----'
c.GitHubOAuthenticator.client_secret = '-----client_secret-----'

I get the GitHub auth page, but once I've put in a username and password I get a GitHub 404 page.

Potential solution here: https://gist.github.com/technoweenie/419219#gistcomment-1920651

Feb 25 19:41:13 jupyter.mydomain.com jupyterhub[2880]: [I 2017-02-25 19:41:13.411 JupyterHub log:100] 302 GET / (@aaa.bbb.ccc.ddd) 6.87ms
Feb 25 19:41:13 jupyter.mydomain.com jupyterhub[2880]: [I 2017-02-25 19:41:13.437 JupyterHub log:100] 302 GET /hub (@aaa.bbb.ccc.ddd) 1.18ms
Feb 25 19:41:13 jupyter.mydomain.com jupyterhub[2880]: [I 2017-02-25 19:41:13.476 JupyterHub log:100] 302 GET /hub/ (@aaa.bbb.ccc.ddd) 2.09ms
Feb 25 19:41:13 jupyter.mydomain.com jupyterhub[2880]: [I 2017-02-25 19:41:13.512 JupyterHub log:100] 302 GET /oauth_login (@aaa.bbb.ccc.ddd) 1.97ms
Feb 25 19:41:13 jupyter.mydomain.com jupyterhub[2880]: [I 2017-02-25 19:41:13.546 JupyterHub oauth2:37] oauth redirect: 'https://jupyter.mydomain.com/hub/oauth_callback'
Feb 25 19:41:13 jupyter.mydomain.com jupyterhub[2880]: [I 2017-02-25 19:41:13.554 JupyterHub log:100] 302 GET /hub/oauth_login (@aaa.bbb.ccc.ddd) 2.73ms

Default email address username normalization for GoogleOAuthenticator is security risk

GoogleOAuthenticator simply removes the Google Domain from the email to produce the returned username. This is a security risk because it allows users with the same username in different domains (if domain is unrestricted) to access the same notebook, or even access the Jupyterhub admin console.

e.g.
[email protected] is an admin user with access, [email protected] will be able to log in with the same user name.

IIUC solution is to use Authenticator.normalize_username and Authenticator.validate_username instead. Allowing displayed usernames to differ from the usernames used in proxy tables.

Ubuntu users should use pip3 and python3

I was trying to setup a hub using the example config at
https://github.com/jupyter/jupyterhub/blob/master/docs/authenticators.md
on aws with an ubuntu instance.

I had to use the command

sudo python3 setup.py install

to get oathenticator setup to run correctly. Maybe I got myself in to a weird corner case by using sudo where I shouldn't (or vice versa) in earlier steps, and maybe that command would have been obvious to a more experienced linux user, but it took me awhile to understand the The 'authenticator_class' trait of <jupyterhub.app.JupyterHub object at 0x7fca2aa31240> instance must be a type, but 'oauthenticator.LocalGitHubOAuthenticator' could not be imported error I was getting when trying to use the config file.

Default scope is insufficient for private org membership

The way we determine membership in the org whitelist is to, as an authenticated user, ask for /orgs/[org]/members. The issue is, even if you're authenticated, unless you have read:org scope, you only get the members whose membership is public.

This is, to be charitable, unclear from the GitHub API documentation and took me quite a while to figure out.

This appears to be a conscious design decision on GitHub's part, since /user/orgs also requires read:org (or user, though I haven't tested that) scope (and would require a second API call as well). It may be that the best we can do is document this thoroughly and fix the base class so scope is a configurable.

GithubOAuthenticator gets 403

I attempted to get oauthenticator up and running and ran into a few issues. I had to change my settings as the examples provided didn't work (I addressed this in PR #7 ). After the settings change I was able to hit my jupyterhub server and log in through github. I was properly forwarded to GitHub and authorized my server for logins and forwarded back to my callback url but I recieved a 403.

The url was:

hub/oauth_callback?code=280e308d237c1849e446

I saw in my server log something about a missing auth_token key during one run through but now all I get is:

[W 2015-07-25 02:05:00.725 JupyterHub log:100] 403 GET /hub/oauth_callback?code=280e308d237c1849e446 311.85ms

I hope this is making some sense. I dont know enough to try to troubleshoot any further.

delete CILogon staged certificate

@minrk I am wondering whether it would be useful to delete the staged certificate and only keep the encrypted version.

While the user is connected, poll is probably continuously using the certificate.
Is there a way to detect if the user is not connected, maybe with a timeout, and delete the staged certificate at that point?

Problems with CILogon parameters

This is partially a follow up on issue #114 (only partially, so I'm opening a new issue instead of continuing there), and involves usernames (as #114) but also skins and identity providers.

Let's start from usernames. As the comment says in this code snippet

if "sub" not in resp_json or not resp_json["sub"]:
return None
username = resp_json["sub"]
# username is now the CILogon "sub" claim. This is not ideal.
this is not ideal. At the moment this returns something like http://cilogon.org/servera/users/NNNN which is far from the actual username (NNNN is probably a UID on some machine, but not the text username). This could be probably fixed by using eppn instead of sub in the above snipped. However, other people might want to use something different, e.g. email, so why not leaving that end-user-configurable and getting its value from an environment variable such as CILOGON_CLAIM? When not set, let's use sub (and when not found, before returning None, let's spit a message in the logs about what was looked for and not found).


Somewhat related to this, I'd like to configure the skin and identity provider which seem to be possible (or at least thought as possible) looking at

if self.authenticator.idp:
extra_params["selected_idp"] = self.authenticator.idp
if self.authenticator.skin:
extra_params["skin"] = self.authenticator.skin
return super().authorize_redirect(*args, **kwargs)

However (and this could be my ignorance) I can't figure out how to set self.authenticator.skin and idp. If that part is still missing, how again using environmental variables CILOGON_SKIN and CILOGON_IDP while preserving current behavior when not set? From a user's perspective (who is not actually calling cilogon.py, but starting the jupyterhub server with various configurations) the environmental variables seem a better solution, and people are already setting CILOGON_CLIENT_ID and CILOGON_CLIENT_SECRET anyway, so this is not something different from what they are already doing.


If this sounds like a good approach, I'll be happy to implement these changes and make a PR myself if necessary.

Thanks @eshook @jbasney

AttributeError: get_authenticated_username

Hi,

I just tried this module and am getting the following error:

May 13 23:34:06 mysql jupyterhub: Traceback (most recent call last):
May 13 23:34:06 mysql jupyterhub: File "/usr/lib64/python3.4/site-packages/tornado/web.py", line 1445, in _execute
May 13 23:34:06 mysql jupyterhub: result = yield result
May 13 23:34:06 mysql jupyterhub: File "/usr/lib/python3.4/site-packages/oauthenticator/google.py", line 56, in get
May 13 23:34:06 mysql jupyterhub: username = yield self.authenticator.get_authenticated_username(self, None)
May 13 23:34:06 mysql jupyterhub: AttributeError: 'GoogleOAuthenticator' object has no attribute 'get_authenticated_username'
May 13 23:34:06 mysql jupyterhub: [E 2016-05-13 23:34:06.174 JupyterHub log:99] {
May 13 23:34:06 mysql jupyterhub: "Referer": "https://accounts.google.com/o/oauth2/auth?response_type=code&scope=openid+email&redirect_uri=https%3A%2F%2Fmysql.hessvo-uva.vm.surfsara.nl%3A8000%2Fhub%2Foauth_callback&client_id=875300024863-44scqeh9pgr26gog3m7fbtbt6tadgelp.apps.googleusercontent.com",
May 13 23:34:06 mysql jupyterhub: "Dnt": "1",
May 13 23:34:06 mysql jupyterhub: "X-Forwarded-For": "84.83.204.215",
May 13 23:34:06 mysql jupyterhub: "X-Forwarded-Port": "8000",
May 13 23:34:06 mysql jupyterhub: "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
May 13 23:34:06 mysql jupyterhub: "Cookie": "surfsaranl_cookiecontrol=yes",
May 13 23:34:06 mysql jupyterhub: "Connection": "close",
May 13 23:34:06 mysql jupyterhub: "Host": "mysql.hessvo-uva.vm.surfsara.nl:8000",
May 13 23:34:06 mysql jupyterhub: "Accept-Language": "en-us",
May 13 23:34:06 mysql jupyterhub: "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/601.5.17 (KHTML, like Gecko) Version/9.1 Safari/601.5.17",
May 13 23:34:06 mysql jupyterhub: "Accept-Encoding": "gzip, deflate",
May 13 23:34:06 mysql jupyterhub: "X-Forwarded-Proto": "https"
May 13 23:34:06 mysql jupyterhub: }

Login successful but bad config during initialization trying to start my server

I created a module okta.py which looks similar to /oauthenticator/auth0.py. This helped me login to JupyterHub from Okta. But when I click on "Start my server" I get this error:

JupyterHub base:345] User logged in: mayur
JupyterHub log:122] 302 GET /hub/oauth_callback?code=fk06WF4m0r1y-2bl93KT&state=eyJuZXh0X3VybCI6ICIiLCAic3RhdGVfaWQiOiAiNTZmYzVhMDA1ZTJmNGZhNWFkODI1NjFkMWFhZDRiZTgifQ%3D%3D → /hub/ (@::ffff:63.118.133.42) 370.54ms
JupyterHub log:122] 302 GET /hub/ → /hub/home (mayur@::ffff:63.118.133.42) 1.69ms
JupyterHub log:122] 200 GET /hub/home (mayur@::ffff:63.118.133.42) 6.20ms
JupyterHub log:122] 302 GET /hub/spawn → /user/mayur/ (mayur@::ffff:63.118.133.42) 2.60ms
JupyterHub log:122] 302 GET /user/mayur/ → /hub/user/mayur/ (@::ffff:63.118.133.42) 0.45ms
JupyterHub spawner:978] Spawning jupyterhub-singleuser --port=60097
Single-user server for JupyterHub. Extends the Jupyter Notebook server.

Meant to be invoked by JupyterHub Spawners, and not directly.

Then a bunch of options 

SingleUserNotebookApp application:90] Bad config encountered during initialization:
SingleUserNotebookApp application:91] The 'ip' trait of a SingleUserNotebookApp instance must be a unicode string, but a value of None <class 'NoneType'> was specified.
JupyterHub web:1587] 500 GET /hub/user/mayur/ (::ffff:63.118.133.42): Spawner failed to start [status=1]. The logs for mayur may contain details.

JupyterHub log:122] 500 GET /hub/user/mayur/ (mayur@::ffff:63.118.133.42) 10059.42ms
JupyterHub user:458] mayur's server never showed up at http://127.0.0.1:60097/user/mayur/ after 30 seconds. Giving up
JupyterHub gen:914] Exception in Future <tornado.concurrent.Future object at 0x7f1e79c15e48> after timeout

My 'jupyterhub_config.py' looks like this:

c = get_config()
from oauthenticator.okta import *

c.JupyterHub.authenticator_class = 'oauthenticator.okta.OktaOAuthenticator'
c.OktaOAuthenticator.create_system_users = True
c.OktaOAuthenticator.whitelist = {'[email protected]', 'mayur'}
c.OktaOAuthenticator.admin_users = {'mayur'}

I am trying to figure out what the "Bad configuration" message is.

Can anyone please help me understand if I am doing anything wrong when starting my server?

CookieError Illegal key with CILogon auth

hi,
I'm using Jupyterhub 0.8 and oauthenticator 0.7.1.

With CILogon, the usernames are URLs, it looks like this is not accepted as a cookie key so the single-user server throws an exception.

Any suggestion on a workaround? See traceback below


    Traceback (most recent call last):
File "/opt/conda/lib/python3.6/site-packages/tornado/web.py", line 1509, in _execute                                          
        result = method(*self.path_args, **self.path_kwargs)                                                                              File "/opt/conda/lib/python3.6/site-packages/tornado/web.py", line 2886, in wrapper                                           
        url = self.get_login_url()                                                                                                        
File "/opt/conda/lib/python3.6/site-packages/jupyterhub/services/auth.py", line 690, in get_login_url                         
        state = self.hub_auth.set_state_cookie(self, next_url=self.request.uri)                                                           File "/opt/conda/lib/python3.6/site-packages/jupyterhub/services/auth.py", line 562, in set_state_cookie                      
        **kwargs                                                                                                                          
File "/opt/conda/lib/python3.6/site-packages/tornado/web.py", line 619, in set_secure_cookie                                  
        expires_days=expires_days, **kwargs)                                                                                              
File "/opt/conda/lib/python3.6/site-packages/tornado/web.py", line 548, in set_cookie                                         
        self._new_cookie[name] = value                                                                                                    
File "/opt/conda/lib/python3.6/http/cookies.py", line 521, in __setitem__                                                     
        self.__set(key, rval, cval)                                                                                                       
File "/opt/conda/lib/python3.6/http/cookies.py", line 511, in __set                                                           
        M.set(key, real_value, coded_value)                                                                                               
File "/opt/conda/lib/python3.6/http/cookies.py", line 380, in set
        raise CookieError('Illegal key %r' % (key,))                                                                                    
http.cookies.CookieError: Illegal key 'user-http%3A//cilogon.org/servera/users/12345-oauth-state'

issues using generic authenticator

Hi I am trying to use generic authenticator and getting following exception. Can you please help me with that?

c.JupyterHub.authenticator_class = 'oauthenticator.generic.GenericOAuthenticator'
c.OAuthenticator.client_id = '<ID>'
c.OAuthenticator.client_secret = '<Secret>'
c.GenericOAuthenticator.token_url = '<URL>'
c.GenericOAuthenticator.oauth_callback_url = 'https://<HOST>:<PORT>/hub/oauth_callback'
c.GenericOAuthenticator.userdata_url  = '<URL>'
c.GenericOAuthenticator.userdata_method = 'POST'
c.GenericOAuthenticator.username_key = 'user_id'

Exception we are getting is

[E <TIME> JupyterHub web:1524] Uncaught exception GET /hub/oauth_callback?code=<CODE> (127.0.0.1)
    HTTPServerRequest(protocol='http', host='<LOCALHOST:PORT>', method='GET', uri='/hub/oauth_callback?code=<CODE>&state=<STATE>', version='HTTP/1.1', remote_ip='127.0.0.1', headers={'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'Upgrade-Insecure-Requests': '1', 'Accept-Language': 'en-US,en;q=0.8', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36', 'Cookie': 'oauthenticator-state="<XXXXXX>"', 'Referer': 'https://<LOCALHOST:PORT>/hub/login', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'close', 'Host': '<LOCALHOST:PORT>'})
    Traceback (most recent call last):
      File "/etc/lib/python3.5/site-packages/tornado/web.py", line 1445, in _execute
        result = yield result
      File "/etc/lib/python3.5/site-packages/oauthenticator/oauth2.py", line 183, in get
        user = yield self.login_user()
      File "/etc/lib/python3.5/site-packages/oauthenticator/oauth2.py", line 166, in _login_user_pre_08
        user_info = yield self.authenticator.get_authenticated_user(self, None)
      File "/etc/lib/python3.5/site-packages/jupyterhub/auth.py", line 128, in get_authenticated_user
        username = yield self.authenticate(handler, data)
      File "/etc/lib/python3.5/site-packages/oauthenticator/generic.py", line 101, in authenticate
        resp = yield http_client.fetch(req)
      File "/etc/lib/python3.5/site-packages/tornado/stack_context.py", line 314, in wrapped
        ret = fn(*args, **kwargs)
      File "/etc/lib/python3.5/site-packages/tornado/simple_httpclient.py", line 290, in _on_timeout
        raise HTTPError(599, "Timeout")
    tornado.httpclient.HTTPError: HTTP 599: Timeout

Unable to login through GitLab oathenticator

I have local gitlab setup on my local machine and followed the Read me.md for setup. when i open the jupyter page i am able to see the Sign in with gitlab button. But when i click on it, it redirects to the gitlab.com.
Even though i have hostname as GITLAB_HOST= http://ip_address in system environment.
below is the configuration file which i have used

# Configuration file for jupyterhub.
import os
#------------------------------------------------------------------------------
# Application(SingletonConfigurable) configuration
#------------------------------------------------------------------------------

# This is an application.

# The date format used by logging formatters for %(asctime)s
#c.Application.log_datefmt = '%Y-%m-%d %H:%M:%S'
c.DockerSpawner.hub_ip_connect = '10.1.34.89'
# The Logging format template
#c.Application.log_format = '[%(name)s]%(highlevel)s %(message)s'
c.DockerSpawner.container_ip = '0.0.0.0'
# Set the log level by value or name.
#c.Application.log_level = 30
c.JupyterHub.spawner_class = 'dockerspawner.SystemUserSpawner'
#------------------------------------------------------------------------------
# JupyterHub(Application) configuration
#------------------------------------------------------------------------------
c.SystemUserSpawner.host_homedir_format_string = '/home/{username}'
# An Application for starting a Multi-User Jupyter Notebook server.
#c.LDAPAuthenticator.bind_dn_template = 'uid={username},CN=Ldap AD,OU=Service_Accounts,OU=Aviator,OU=Sites,DC=mu-sigma,DC=local'
# Grant admin users permission to access single-user servers.
#  
#  Users should be properly informed if this is enabled.
#c.JupyterHub.admin_access = False

# DEPRECATED, use Authenticator.admin_users instead.
#c.JupyterHub.admin_users = set()

# Answer yes to any questions (e.g. confirm overwrite)
#c.JupyterHub.answer_yes = False

# Dict of token:username to be loaded into the database.
#  
#  Allows ahead-of-time generation of API tokens for use by services.
#c.JupyterHub.api_tokens = {}

# Class for authenticating users.
#  
#  This should be a class with the following form:
#  
#  - constructor takes one kwarg: `config`, the IPython config object.
#  
#  - is a tornado.gen.coroutine
#  - returns username on success, None on failure
#  - takes two arguments: (handler, data),
#    where `handler` is the calling web.RequestHandler,
#    and `data` is the POST form data from the login page.
#c.JupyterHub.authenticator_class = 'jupyterhub.auth.PAMAuthenticator'

# The base URL of the entire application
#c.JupyterHub.base_url = '/'

# Whether to shutdown the proxy when the Hub shuts down.
#  
#  Disable if you want to be able to teardown the Hub while leaving the proxy
#  running.
#  
#  Only valid if the proxy was starting by the Hub process.
#  
#  If both this and cleanup_servers are False, sending SIGINT to the Hub will
#  only shutdown the Hub, leaving everything else running.
#  
#  The Hub should be able to resume from database state.
#c.JupyterHub.cleanup_proxy = True

# Whether to shutdown single-user servers when the Hub shuts down.
#  
#  Disable if you want to be able to teardown the Hub while leaving the single-
#  user servers running.
#  
#  If both this and cleanup_proxy are False, sending SIGINT to the Hub will only
#  shutdown the Hub, leaving everything else running.
#  
#  The Hub should be able to resume from database state.
#c.JupyterHub.cleanup_servers = True

# The config file to load
#c.JupyterHub.config_file = 'jupyterhub_config.py'

# Confirm that JupyterHub should be run without SSL. This is **NOT RECOMMENDED**
#  unless SSL termination is being handled by another layer.
c.JupyterHub.confirm_no_ssl = True

# Number of days for a login cookie to be valid. Default is two weeks.
#c.JupyterHub.cookie_max_age_days = 14

# The cookie secret to use to encrypt cookies.
#  
#  Loaded from the JPY_COOKIE_SECRET env variable by default.
#c.JupyterHub.cookie_secret = b''

# File in which to store the cookie secret.
#c.JupyterHub.cookie_secret_file = 'jupyterhub_cookie_secret'

# The location of jupyterhub data files (e.g. /usr/local/share/jupyter/hub)
#c.JupyterHub.data_files_path = '/usr/local/share/jupyter/hub'

# Include any kwargs to pass to the database connection. See
#  sqlalchemy.create_engine for details.
#c.JupyterHub.db_kwargs = {}

# url for the database. e.g. `sqlite:///jupyterhub.sqlite`
#c.JupyterHub.db_url = 'sqlite:///jupyterhub.sqlite'

# log all database transactions. This has A LOT of output
#c.JupyterHub.debug_db = False

# show debug output in configurable-http-proxy
#c.JupyterHub.debug_proxy = False

# Send JupyterHub's logs to this file.
#  
#  This will *only* include the logs of the Hub itself, not the logs of the proxy
#  or any single-user servers.
#c.JupyterHub.extra_log_file = ''

# Extra log handlers to set on JupyterHub logger
#c.JupyterHub.extra_log_handlers = []

# Generate default config file
#c.JupyterHub.generate_config = False

# The ip for this process
c.JupyterHub.hub_ip = '0.0.0.0'

# The port for this process
#c.JupyterHub.hub_port = 8081

# The prefix for the hub server. Must not be '/'
#c.JupyterHub.hub_prefix = '/hub/'

# The public facing ip of the whole application (the proxy)
c.JupyterHub.ip = '*'

# Supply extra arguments that will be passed to Jinja environment.
#c.JupyterHub.jinja_environment_options = {}

# Interval (in seconds) at which to update last-activity timestamps.
#c.JupyterHub.last_activity_interval = 300

# Specify path to a logo image to override the Jupyter logo in the banner.
#c.JupyterHub.logo_file = ''

# File to write PID Useful for daemonizing jupyterhub.
#c.JupyterHub.pid_file = ''

# The public facing port of the proxy
#c.JupyterHub.port = 8000

# The ip for the proxy API handlers
c.JupyterHub.proxy_api_ip = '0.0.0.0'

# The port for the proxy API handlers
#c.JupyterHub.proxy_api_port = 0

# The Proxy Auth token.
#  
#  Loaded from the CONFIGPROXY_AUTH_TOKEN env variable by default.
#c.JupyterHub.proxy_auth_token = ''

# Interval (in seconds) at which to check if the proxy is running.
#c.JupyterHub.proxy_check_interval = 30

# The command to start the http proxy.
#  
#  Only override if configurable-http-proxy is not on your PATH
#c.JupyterHub.proxy_cmd = ['configurable-http-proxy']

# Purge and reset the database.
#c.JupyterHub.reset_db = False

# The class to use for spawning single-user servers.
#  
#  Should be a subclass of Spawner.
#c.JupyterHub.spawner_class = 'jupyterhub.spawner.LocalProcessSpawner'

# Path to SSL certificate file for the public facing interface of the proxy
#  
#  Use with ssl_key
#c.JupyterHub.ssl_cert = ''

# Path to SSL key file for the public facing interface of the proxy
#  
#  Use with ssl_cert
#c.JupyterHub.ssl_key = ''

# Host to send statds metrics to
#c.JupyterHub.statsd_host = ''

# Port on which to send statsd metrics about the hub
#c.JupyterHub.statsd_port = 8125

# Prefix to use for all metrics sent by jupyterhub to statsd
#c.JupyterHub.statsd_prefix = 'jupyterhub'

# Run single-user servers on subdomains of this host.
#  
#  This should be the full https://hub.domain.tld[:port]
#  
#  Provides additional cross-site protections for javascript served by single-
#  user servers.
#  
#  Requires <username>.hub.domain.tld to resolve to the same host as
#  hub.domain.tld.
#  
#  In general, this is most easily achieved with wildcard DNS.
#  
#  When using SSL (i.e. always) this also requires a wildcard SSL certificate.
#c.JupyterHub.subdomain_host = ''

# Paths to search for jinja templates.
#c.JupyterHub.template_paths = []

# Extra settings overrides to pass to the tornado application.
#c.JupyterHub.tornado_settings = {}

#------------------------------------------------------------------------------
# Spawner(LoggingConfigurable) configuration
#------------------------------------------------------------------------------

# Base class for spawning single-user notebook servers.
#  
#  Subclass this, and override the following methods:
#  
#  - load_state - get_state - start - stop - poll

# Extra arguments to be passed to the single-user server
#c.Spawner.args = []

# The command used for starting notebooks.
#c.Spawner.cmd = ['jupyterhub-singleuser']

# Enable debug-logging of the single-user server
#c.Spawner.debug = False

# The default URL for the single-user server.
#  
#  Can be used in conjunction with --notebook-dir=/ to enable  full filesystem
#  traversal, while preserving user's homedir as landing page for notebook
#  
#  `%U` will be expanded to the user's username
#c.Spawner.default_url = ''

# Disable per-user configuration of single-user servers.
#  
#  This prevents any config in users' $HOME directories from having an effect on
#  their server.
#c.Spawner.disable_user_config = False

# Whitelist of environment variables for the subprocess to inherit
#c.Spawner.env_keep = ['PATH', 'PYTHONPATH', 'CONDA_ROOT', 'CONDA_DEFAULT_ENV', 'VIRTUAL_ENV', 'LANG', 'LC_ALL']

# Environment variables to load for the Spawner.
#  
#  Value could be a string or a callable. If it is a callable, it will be called
#  with one parameter, which will be the instance of the spawner in use. It
#  should quickly (without doing much blocking operations) return a string that
#  will be used as the value for the environment variable.
#c.Spawner.environment = {}

# Timeout (in seconds) before giving up on a spawned HTTP server
#  
#  Once a server has successfully been spawned, this is the amount of time we
#  wait before assuming that the server is unable to accept connections.
#c.Spawner.http_timeout = 30

# The IP address (or hostname) the single-user server should listen on
c.Spawner.ip = '0.0.0.0'

# The notebook directory for the single-user server
#  
#  `~` will be expanded to the user's home directory `%U` will be expanded to the
#  user's username
#c.Spawner.notebook_dir = ''

# An HTML form for options a user can specify on launching their server. The
#  surrounding `<form>` element and the submit button are already provided.
#  
#  For example:
#  
#      Set your key:
#      <input name="key" val="default_key"></input>
#      <br>
#      Choose a letter:
#      <select name="letter" multiple="true">
#        <option value="A">The letter A</option>
#        <option value="B">The letter B</option>
#      </select>
#c.Spawner.options_form = ''

# Interval (in seconds) on which to poll the spawner.
#c.Spawner.poll_interval = 30

# Timeout (in seconds) before giving up on the spawner.
#  
#  This is the timeout for start to return, not the timeout for the server to
#  respond. Callers of spawner.start will assume that startup has failed if it
#  takes longer than this. start should return when the server process is started
#  and its location is known.
#c.Spawner.start_timeout = 60

#------------------------------------------------------------------------------
# LocalProcessSpawner(Spawner) configuration
#------------------------------------------------------------------------------

# A Spawner that just uses Popen to start local processes as users.
#  
#  Requires users to exist on the local system.
#  
#  This is the default spawner for JupyterHub.

# Seconds to wait for process to halt after SIGINT before proceeding to SIGTERM
#c.LocalProcessSpawner.INTERRUPT_TIMEOUT = 10

# Seconds to wait for process to halt after SIGKILL before giving up
#c.LocalProcessSpawner.KILL_TIMEOUT = 5

# Seconds to wait for process to halt after SIGTERM before proceeding to SIGKILL
#c.LocalProcessSpawner.TERM_TIMEOUT = 5

#------------------------------------------------------------------------------
# Authenticator(LoggingConfigurable) configuration
#------------------------------------------------------------------------------

# A class for authentication.
#  
#  The primary API is one method, `authenticate`, a tornado coroutine for
#  authenticating users.

# set of usernames of admin users
#  
#  If unspecified, only the user that launches the server will be admin.
#c.Authenticator.admin_users = set()

# Dictionary mapping authenticator usernames to JupyterHub users.
#  
#  Can be used to map OAuth service names to local users, for instance.
#  
#  Used in normalize_username.
#c.Authenticator.username_map = {}

# Regular expression pattern for validating usernames.
#  
#  If not defined: allow any username.
#c.Authenticator.username_pattern = ''

# Username whitelist.
#  
#  Use this to restrict which users can login. If empty, allow any user to
#  attempt login.
#c.Authenticator.whitelist = set()

#------------------------------------------------------------------------------
# LocalAuthenticator(Authenticator) configuration
#------------------------------------------------------------------------------

# Base class for Authenticators that work with local Linux/UNIX users
#  
#  Checks for local users, and can attempt to create them if they exist.

# The command to use for creating users as a list of strings.
#  
#  For each element in the list, the string USERNAME will be replaced with the
#  user's username. The username will also be appended as the final argument.
#  
#  For Linux, the default value is:
#  
#      ['adduser', '-q', '--gecos', '""', '--disabled-password']
#  
#  To specify a custom home directory, set this to:
#  
#      ['adduser', '-q', '--gecos', '""', '--home', '/customhome/USERNAME',
#  '--disabled-password']
#  
#  This will run the command:
#  
#  adduser -q --gecos "" --home /customhome/river --disabled-password river
#  
#  when the user 'river' is created.
#c.LocalAuthenticator.add_user_cmd = []

# If a user is added that doesn't exist on the system, should I try to create
#  the system user?
c.JupyterHub.authenticator_class = 'oauthenticator.gitlab.LocalGitLabOAuthenticator'
c.LocalAuthenticator.create_system_users = True
c.LocalGitLabOAuthenticator.oauth_callback_url = 'http://10.1.34.89:8000/hub/oauth_callback'
c.LocalGitLabOAuthenticator.client_id = 'Client ID'
c.LocalGitLabOAuthenticator.client_secret = 'Client secret'
#c.LocalGitLabOAuthenticator.host= True
# Automatically whitelist anyone in this group.
#c.LocalAuthenticator.group_whitelist = set()

#------------------------------------------------------------------------------
# PAMAuthenticator(LocalAuthenticator) configuration
#------------------------------------------------------------------------------

# Authenticate local Linux/UNIX users with PAM

# The encoding to use for PAM
#c.PAMAuthenticator.encoding = 'utf8'

# Whether to open PAM sessions when spawners are started.
#  
#  This may trigger things like mounting shared filsystems, loading credentials,
#  etc. depending on system configuration, but it does not always work.
#  
#  It can be disabled with::
#  
#      c.PAMAuthenticator.open_sessions = False
#c.PAMAuthenticator.open_sessions = True

# The PAM service to use for authentication.
#c.PAMAuthenticator.service = 'login'

Possible Issue Logging into Jupyterhub With Globus OAuthenticator

I have successfully used OAuthenticator with Globus in the past, but lately I have been having problems. I am using jupyterhub 0.8.0 and oauthenticator 0.7.1. I am using Globus oauth via XSEDE. When I try to login with my XSEDE credentials, I seem to get past the XSEDE login page but then I get a "Jupyter 500 : Internal Server Error". Here is what I see in jupyterhub.log:

  File "/opt/conda/lib/python3.6/site-packages/tornado/web.py", line 1511, in _execute
    result = yield result
  File "/opt/conda/lib/python3.6/site-packages/oauthenticator/globus.py", line 52, in get
    user = self.user_from_username(username)
  File "/opt/conda/lib/python3.6/site-packages/jupyterhub/handlers/base.py", line 240, in user_from_username
    user = self.find_user(username)
  File "/opt/conda/lib/python3.6/site-packages/jupyterhub/handlers/base.py", line 235, in find_user
    orm_user = orm.User.find(db=self.db, name=name)
  File "/opt/conda/lib/python3.6/site-packages/jupyterhub/orm.py", line 171, in find
    return db.query(cls).filter(cls.name == name).first()
  File "/opt/conda/lib/python3.6/site-packages/sqlalchemy/orm/query.py", line 2755, in first
    ret = list(self[0:1])
  File "/opt/conda/lib/python3.6/site-packages/sqlalchemy/orm/query.py", line 2547, in __getitem__
    return list(res)
  File "/opt/conda/lib/python3.6/site-packages/sqlalchemy/orm/query.py", line 2855, in __iter__
    return self._execute_and_instances(context)
  File "/opt/conda/lib/python3.6/site-packages/sqlalchemy/orm/query.py", line 2878, in _execute_and_instances
    result = conn.execute(querycontext.statement, self._params)                                                                                                                File "/opt/conda/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 945, in execute
    return meth(self, multiparams, params)
  File "/opt/conda/lib/python3.6/site-packages/sqlalchemy/sql/elements.py", line 263, in _execute_on_connection
    return connection._execute_clauseelement(self, multiparams, params)
  File "/opt/conda/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1053, in _execute_clauseelement
    compiled_sql, distilled_params
  File "/opt/conda/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1189, in _execute_context
    context)
  File "/opt/conda/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1402, in _handle_dbapi_exception
    exc_info
  File "/opt/conda/lib/python3.6/site-packages/sqlalchemy/util/compat.py", line 203, in raise_from_cause
    reraise(type(exception), exception, tb=exc_tb, cause=cause)
  File "/opt/conda/lib/python3.6/site-packages/sqlalchemy/util/compat.py", line 186, in reraise
    raise value.with_traceback(tb)
  File "/opt/conda/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1182, in _execute_context
    context)
  File "/opt/conda/lib/python3.6/site-packages/sqlalchemy/engine/default.py", line 470, in do_execute
    cursor.execute(statement, parameters)
sqlalchemy.exc.InterfaceError: <unprintable InterfaceError object>

Google auth on hosted domain breaking in 0.7.0 if no name in claim

Hi,

0.7.0 broke Google authentication for me. Works fine in 0.6.1. The issue appears to be that my Google account (which is a G Suite account) does not return the name claim in the ID Token without additional scope. So when the auth_state makes its way to jupyterhub/auth.py, it chokes on get_authenticated_user, which checks to see if name is present (line 226).

One possible workaround is to ask for the profile scope, which should include name. Trouble with that is, Google says it doesn't have to include name.

I'm not entirely sure what the best way around this is--I guess that depends on why jupyterhub/auth.py needs to have name. I feel like it should be using username (derived from email, which I think should always be present in a hosted_domain).

Here's a (slightly obscured) traceback:


      File "/opt/conda/lib/python3.5/site-packages/tornado/web.py", line 1511, in _execute

        result = yield result

      File "/opt/conda/lib/python3.5/site-packages/oauthenticator/oauth2.py", line 182, in get

        user = yield self.login_user()

      File "/opt/conda/lib/python3.5/site-packages/jupyterhub/handlers/base.py", line 327, in login_user

        authenticated = yield self.authenticate(data)

      File "/opt/conda/lib/python3.5/site-packages/jupyterhub/auth.py", line 226, in get_authenticated_user

        raise ValueError("user missing a name: %r" % authenticated)

    ValueError: user missing a name: {'username': 'charles.forelle', 'auth_state': {'google_user': {'given_name': '', 'name': '', 'email': 'charles.forelle@XXXXXXXXX', 'family_name': '', 'id': '105xxxxxxxxxxxxx', 'hd': 'XXXXXXX', 'picture': 'https://lh3.googleusercontent.com/XXXXX/photo.jpg', 'verified_email': True}, 'access_token': 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'}}

Implement auth_state on authenticators

Authenticators should be returning

{
  'name': 'username',
  'auth_state': {state},
}

so that state can be persisted. The auth state should include the JSON reply from the upstream authenticator as-is, if possible.

Github IDs with Capital Letters

I’m using login with Github option and having some issues with a few students logging into Jupyterhub. I’ve just tested and I think it is because of capital letters. Unix doesn’t like the capital letters but i’ve tried it both ways (giving all lower case or forcing all upper case) and doesn't seem to allow either way.

Is this option able to handle logging in with GitHub ID's that contain capital letters?

cilogon uses email address instead of username

It looks like oauthenticator/cilogon.py is setting the jupyterhub username to the email address the user has registered (with '@' replaced by '.')

Just to be clear, I'm talking about the code block starting here

It looks like this code is pulling the user's email address out of the CILogon certificate subjectAltName. I'm using cilogon for letting (selected) people with XSEDE credentials login into the jupyter service I'm building. For my own reasons, I create accounts manually. When I started, I thought this was a minor nuisance, but this is becoming a growing problem for me, for several reasons:

  • (medium) people do not remember the email address they associated with XSEDE. So I can't simply create an account for them when they ask (giving me their XSEDE username): they either have to check that email (and apparently everybody gets it wrong, not sure why) or they have to attempt login and I have to look at the logs, identify their email, and create an username with that email address (with '@' replaced by '.') -- a 10 minutes affair becomes a multi-day back-and-forth email conversation, explain this and that, digging into the logs, huge waste of time

  • (major): if users need to change the email address associated with XSEDE, their account on my system because inaccessible.

  • (minor): creating usernames with "." is discouraged on linux (however --force-badname allows them to exist and POSIX do not forbid them in IEEE Std 1003.1-2001)

  • (minor, unless actually encountered, when it will become major): users with email [email protected] and [email protected] would both resolve to the same username john.smith.domain.com

It looks like the code block starting here should get the eduPersonPrincipalName instead, which is always [email protected] for XSEDE users (and then the @xsede.org part could be either removed or made into .xsede.org as it is now)

For the (extremely limited) tests I have done, this could be accomplished by replacing line 184 with

  if ext.get_short_name().decode('ascii', 'replace') == '1.3.6.1.4.1.5923.1.1.1.6':

however that seems too much hard-coded numbers to be generic and I don't know much about CILogon or OAuth to know if that's a valid thing to do (otherwise this would be a pull request :) )

I was also told:

The better long-term fix is to update oauthenticator/cilogon.py to use OAuth2 rather than OAuth1. Then it won't need to mess with certificates at all, because CILogon's OAuth2 interface (http://www.cilogon.org/oidc) provides all the user info in JSON.

but I don't know anything about it.

check_whitelist() signature bug in BitbucketOAuthentication

Hi,

I am using oauth to authenticate against a bitbucket team. Using the version of oauthenticator installed with pip (0.5.0) I get the following exception:

Traceback (most recent call last): File "/opt/conda/lib/python3.5/site-packages/tornado/web.py", line 1469, in _execute result = yield result File "/opt/conda/lib/python3.5/site-packages/oauthenticator/oauth2.py", line 51, in get username = yield self.authenticator.get_authenticated_user(self, None) File "/opt/conda/lib/python3.5/site-packages/jupyterhub/auth.py", line 135, in get_authenticated_user if self.check_whitelist(username): TypeError: check_whitelist() missing 1 required positional argument: 'headers'

I tracked the issue down to the definition of BitbucketOAuthenticator.check_whitelist, which had a required second headers argument. I found that by moving the headers definition from the authenticate method to the class level and referencing it with self in the appropriate places that integration works correctly.

I have forked the repo and commited the fix, should I create a pull request for review?

Logout from jupyterhub does not work properly with GithubOauth

Hello,

Expected behaviour:
When the user clicks the logout button, the /hub/login page should be displayed.

Issue:
Jupyterhub can't logout unless it logouts from github prevoiously. If the use is in the home page (/user/xxx/tree) and the logout button is pushed, jupyterhub is redirected to /hub and, after some redirections, it returns to the same page /user/xxx/tree

Jupyterhub version: 0.7.1
Oauth Authenticator vesion tested: 0.5.1/0.6.0

from oauthenticator.github import LocalGitHubOAuthenticator
c.JupyterHub.authenticator_class = LocalGitHubOAuthenticator
c.LocalGitHubOAuthenticator.oauth_callback_url = ''
c.LocalGitHubOAuthenticator.client_id = ''
c.LocalGitHubOAuthenticator.client_secret = ''
c.NotebookApp.open_browser = False
c.NotebookApp.ip='*'
c.NotebookApp.port = 8888
c.NotebookApp.token = u''
c.Authenticator.admin_users = {'hadoop'}
c.LocalAuthenticator.create_system_users = True
[I 2017-08-09 17:41:30.065 JupyterHub login:19] User logged out: xxxx
[I 2017-08-09 17:41:30.077 JupyterHub log:100] 302 GET /hub/logout ([email protected]) 12.40ms
[I 2017-08-09 17:41:30.437 JupyterHub log:100] 302 GET /hub/ (@yy.yyy.yyy.yyy) 1.75ms
[I 2017-08-09 17:41:30.680 JupyterHub log:100] 302 GET /oauth_login (@yy.yyy.yyy.yyy) 1.08ms

Thanks in advance,
Leandro

Does gitlab have to be installed in system to use GITLAB_HOST ?

I created system variable with GITLAB_HOST="http://[ip]"
and $GITLAB_HOST show its ok.
But when I try to login I get redirected to gitlab.com instead of custom server.
Is there any log that shows jupyterhub trying to get the env variable?
Maybe I need gitlab installed on system? I found that GITLAB_HOST is variable used by gitlab.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.