def get(self): redirect_uri = request.url flow = get_auth_flow(redirect_uri=redirect_uri) try: oauth_response = flow.step2_exchange(request.args['code']) except FlowExchangeError: return redirect('/?auth_error=true') scopes = oauth_response.token_response['scope'].split(',') if 'user:email' not in scopes: raise NotImplementedError # fetch user details github = GitHubClient(token=oauth_response.access_token) user_data = github.get('/user') identity_config = { 'access_token': oauth_response.access_token, 'refresh_token': oauth_response.refresh_token, 'scopes': scopes, 'login': user_data['login'], } email = user_data.get('email') # no primary/public email specified if not email: emails = github.get('/user/emails') email = next(( e['email'] for e in emails if e['verified'] and e['primary'] )) try: with db.session.begin_nested(): user = User( email=email, ) db.session.add(user) identity = Identity( user=user, external_id=str(user_data['id']), provider='github', config=identity_config, ) db.session.add(identity) user_id = user.id except IntegrityError: identity = Identity.query.filter( Identity.external_id == str(user_data['id']), Identity.provider == 'github', ).first() identity.config = identity_config db.session.add(identity) user_id = identity.user_id db.session.commit() # forcefully expire a session after permanent_session_lifetime # Note: this is enforced in zeus.auth auth.login_user(user_id) return redirect(url_for(self.complete_url))
def get(self): redirect_uri = request.url flow = get_auth_flow(redirect_uri=redirect_uri) try: oauth_response = flow.step2_exchange(request.args['code']) except FlowExchangeError: return redirect('/?auth_error=true') scopes = oauth_response.token_response['scope'].split(',') if 'user:email' not in scopes: raise NotImplementedError # fetch user details github = GitHubClient(token=oauth_response.access_token) user_data = github.get('/user') identity_config = { 'access_token': oauth_response.access_token, 'refresh_token': oauth_response.refresh_token, 'login': user_data['login'], } email_list = github.get('/user/emails') email_list.append({ 'email': '{}@users.noreply.github.com'.format(user_data['login']), 'verified': True, }) primary_email = user_data.get('email') # HACK(dcramer): capture github's anonymous email addresses when they're not listed # (we haven't actually confirmed they're not listed) if not primary_email: primary_email = next((e['email'] for e in email_list if e['verified'] and e['primary'])) try: # we first attempt to create a new user + identity with db.session.begin_nested(): user = User(email=primary_email, ) db.session.add(user) identity = Identity( user=user, external_id=str(user_data['id']), provider='github', scopes=scopes, config=identity_config, ) db.session.add(identity) user_id = user.id except IntegrityError: # if that fails, assume the identity exists identity = Identity.query.filter( Identity.external_id == str(user_data['id']), Identity.provider == 'github', ).first() # and if it doesnt, attempt to find a matching user, # as it means the failure above was due to that if not identity: user = User.query.filter(User.email == primary_email).first() assert user # this should not be possible unless we've got a race condition identity = Identity( user=user, external_id=str(user_data['id']), provider='github', scopes=scopes, config=identity_config, ) db.session.add(identity) user_id = user.id else: identity.config = identity_config identity.scopes = scopes db.session.add(identity) user_id = identity.user_id db.session.flush() for email in email_list: try: with db.session.begin_nested(): db.session.add( Email( user_id=user_id, email=email['email'], verified=email['verified'], )) except IntegrityError: pass db.session.commit() # forcefully expire a session after permanent_session_lifetime # Note: this is enforced in zeus.auth auth.login_user(user_id) # now lets try to update the repos they have access to based on whats # enabled user = auth.get_current_user() grant_access_to_existing_repos(user) return redirect(auth.get_redirect_target(clear=True) or '/')
def get(self): # TODO(dcramer): handle errors oauth = get_oauth_session(state=session.pop("oauth_state", None)) try: resp = oauth.fetch_token( GITHUB_TOKEN_URI, client_secret=current_app.config["GITHUB_CLIENT_SECRET"], authorization_response=request.url, ) except OAuth2Error: current_app.logger.exception("oauth.error") # redirect, as this is likely temporary based on server data return redirect(auth.get_redirect_target(clear=True) or "/") if resp is None or resp.get("access_token") is None: return Response("Access denied: reason=%s error=%s resp=%s" % (request.args["error"], request.args["error_description"], resp)) assert resp.get("token_type") == "bearer" scopes = resp["scope"][0].split(",") if "user:email" not in scopes: raise NotImplementedError # fetch user details client = GitHubClient(token=resp["access_token"]) user_data = client.get("/user") identity_config = { "access_token": resp["access_token"], "refresh_token": resp.get("refresh_token"), "login": user_data["login"], } email_list = client.get("/user/emails") email_list.append({ "email": "{}@users.noreply.github.com".format(user_data["login"]), "verified": True, }) primary_email = user_data.get("email") # HACK(dcramer): capture github's anonymous email addresses when they're not listed # (we haven't actually confirmed they're not listed) if not primary_email: primary_email = next((e["email"] for e in email_list if e["verified"] and e["primary"])) try: # we first attempt to create a new user + identity with db.session.begin_nested(): user = User(email=primary_email) db.session.add(user) identity = Identity( user=user, external_id=str(user_data["id"]), provider="github", scopes=scopes, config=identity_config, ) db.session.add(identity) user_id = user.id new_user = True except IntegrityError: # if that fails, assume the identity exists identity = Identity.query.filter( Identity.external_id == str(user_data["id"]), Identity.provider == "github", ).first() # and if it doesnt, attempt to find a matching user, # as it means the failure above was due to that if not identity: user = User.query.filter(User.email == primary_email).first() assert ( user ) # this should not be possible unless we've got a race condition identity = Identity( user=user, external_id=str(user_data["id"]), provider="github", scopes=scopes, config=identity_config, ) db.session.add(identity) user_id = user.id else: identity.config = identity_config identity.scopes = scopes db.session.add(identity) user_id = identity.user_id new_user = False db.session.flush() for email in email_list: try: with db.session.begin_nested(): db.session.add( Email( user_id=user_id, email=email["email"], verified=email["verified"], )) except IntegrityError: pass db.session.commit() # forcefully expire a session after permanent_session_lifetime # Note: this is enforced in zeus.auth auth.login_user(user_id) user = auth.get_current_user() if new_user: # update synchronously so the new user has a better experience sync_github_access(user_id=user.id) else: sync_github_access.delay(user_id=user.id) next_uri = auth.get_redirect_target(clear=True) or "/" if "/login" in next_uri or "/auth/github" in next_uri: next_uri = "/" return redirect(next_uri)
def post(self, org: Organization): """ Create a new repository. """ provider = (request.get_json() or {}).get('provider', 'native') if provider == 'github': schema = github_repo_schema elif provider == 'native': schema = repo_schema else: raise NotImplementedError result = self.schema_from_request(schema, partial=True) if result.errors: return self.respond(result.errors, 403) data = result.data if provider == 'github': # get their credentials identity = Identity.query.filter( Identity.provider == 'github', Identity.user_id == auth.get_current_user().id ).first() if 'repo' not in identity.config['scopes']: return self.respond( { 'needUpgrade': True, 'upgradeUrl': '/auth/github/upgrade' }, 401 ) assert identity # fetch repository details using their credentials github = GitHubClient(token=identity.config['access_token']) repo_data = github.get('/repos/{}'.format(data['github_name'])) repo, created = Repository.query.filter( Repository.provider == RepositoryProvider.github, Repository.external_id == str(repo_data['id']), ).first(), False if repo is None: # bind various github specific attributes repo, created = Repository( organization=org, backend=RepositoryBackend.git, provider=RepositoryProvider.github, status=RepositoryStatus.active, external_id=str(repo_data['id']), url=repo_data['clone_url'], data={'github': { 'full_name': repo_data['full_name'] }}, ), True db.session.add(repo) # generate a new private key for use on github key = ssh.generate_key() db.session.add( ItemOption( item_id=repo.id, name='auth.private-key', value=key.private_key, ) ) # register key with github github.post( '/repos/{}/keys'.format(repo.data['github']['full_name']), json={ 'title': 'zeus', 'key': key.public_key, 'read_only': True, } ) elif provider == 'native': repo, created = Repository( organization=org, status=RepositoryStatus.active, **data, ), True db.session.add(repo) db.session.flush() try: with db.session.begin_nested(): db.session.add( RepositoryAccess( organization=org, repository=repo, user=auth.get_current_user(), ) ) db.session.flush() except IntegrityError: raise pass db.session.commit() if created: import_repo.delay(repo_id=repo.id) return self.respond_with_schema(repo_schema, repo)