Page MenuHomec4science

PhabricatorAuthProviderShibboleth.php
No OneTemporary

File Metadata

Created
Tue, Apr 30, 17:40

PhabricatorAuthProviderShibboleth.php

<?php
final class PhabricatorAuthProviderShibboleth
extends PhabricatorAuthProvider {
private $adapter;
public function getProviderName() {
return pht('Switch AAI for Swiss Univercities');
}
public function getDescriptionForCreate() {
return pht(
'Configure a trust relationship for Shibboleth (Single Sign On) '.
'authenticated users to automatically log in to Phabricator.');
}
public function getLoginOrder(){
return '1-shibboleth';
}
public function getDefaultProviderConfig() {
return parent::getDefaultProviderConfig();
}
public function getAdapter() {
if (!$this->adapter) {
$conf = $this->getProviderConfig();
$adapter = id(new PhutilAuthAdapterShibboleth())
->setShibSessionIdField(
$conf->getProperty(self::KEY_SHIB_SESSION_ID_FIELD))
->setShibApplicationIdField(
$conf->getProperty(self::KEY_SHIB_APPLICATION_ID_FIELD))
->setUseridField(
$conf->getProperty(self::KEY_USERID_FIELD))
->setUsernameField(
$conf->getProperty(self::KEY_USERNAME_FIELD))
->setRealnameField(
$conf->getProperty(self::KEY_REALNAME_FIELD))
->setFirstnameField(
$conf->getProperty(self::KEY_FIRSTNAME_FIELD))
->setLastnameField(
$conf->getProperty(self::KEY_LASTNAME_FIELD))
->setEmailField(
$conf->getProperty(self::KEY_EMAIL_FIELD))
->setPageURIPattern(
$conf->getProperty(self::KEY_PAGE_URI_PATTERN))
->setImageURIPattern(
$conf->getProperty(self::KEY_IMAGE_URI_PATTERN))
->setAddUserToProject(
$conf->getProperty(self::KEY_ADD_USER_TO_PROJECT))
->setUserProject(
$conf->getProperty(self::KEY_USER_PROJECT))
->setIsGeneratedUsername(
$conf->getProperty(self::KEY_USERNAME_FROM_REALNAME))
->setOrgField(
$conf->getProperty(self::KEY_ORG_FIELD))
->setOrgCustomField(
$conf->getProperty(self::KEY_ORG_CUSTOM_FIELD))
->setOrgTypeField(
$conf->getProperty(self::KEY_ORG_TYPE_FIELD));
$this->adapter = $adapter;
}
return $this->adapter;
}
protected function renderLoginForm(AphrontRequest $request, $mode) {
$viewer = $request->getUser();
$dialog = id(new AphrontDialogView())
->setSubmitURI($this->getLoginURI())
->setUser($viewer)
->setTitle(pht('Login for Swiss Universities'))
->addSubmitButton(pht('Login or Register'));
$remarkup = new PHUIRemarkupView($viewer, pht(<<<EOCONTENT
Login using Switch AAI, if you have an account in a Swiss University.
(WARNING) Permission to create Repositories, Projects and Wiki pages.
EOCONTENT
));
$frame = id(new PHUIBoxView())
->addPadding(PHUI::PADDING_LARGE)
->appendChild($remarkup);
$dialog->appendChild($frame);
// Add CSP header if needed
//$static_response = CelerityAPI::getStaticResourceResponse();
//$static_response->addContentSecurityPolicyURI(
// 'form-action', 'https://idp.epfl.ch');
return $dialog;
}
public function isLoginFormAButton() {
return false;
}
private function populateAdapter() {
$adapter = $this->getAdapter();
$env = array();
$env_names = $adapter->getEnvNames();
foreach ($env_names as $h) {
if(array_key_exists($h, $_SERVER)) {
$env[$h] = $_SERVER[$h];
}
}
return $adapter->setUserDataFromRequest($env);
}
public function processLoginRequest(
PhabricatorAuthLoginController $controller) {
$adapter = $this->getAdapter();
$request = $controller->getRequest();
$response = null;
$account = null;
if (!$this->populateAdapter()) {
$response = $controller->buildProviderPageResponse(
$this,
id(new PHUIInfoView())
->setErrors(array(pht('Invalid Shibboleth session.')))
->addButton(id(new PHUIButtonView())
->setTag('a')
->setText(pht('Return to home'))
->setHref('/')
)
);
return array($account, $response);
}
$account_id = $adapter->getAccountID();
return array($this->loadOrCreateAccount_custom($account_id, $request), $response);
}
private function updateOrganization($userPHID, $adapter) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
if($adapter == null){
$adapter = $this->getAdapter();
}
if(strlen($adapter->getOrg()) &&
strlen($adapter->getOrgType()) &&
strlen($adapter->getOrgCustom())
) {
$user = id(new PhabricatorUser())
->loadOneWhere('phid = %s', $userPHID);
$admin = id(new PhabricatorUser())
->loadOneWhere('username = %s', 'admin');
$field_list = PhabricatorCustomField::getObjectFields(
$user, PhabricatorCustomField::ROLE_VIEW);
$field_list->readFieldsFromStorage($user);
$orgvalue = null;
foreach($field_list->getFields() as $field){
try {
if($field->getFieldKey()=='std:user:' . $adapter->getOrgCustom()){
$orgvalue = $field->getValueForStorage();
break;
}
} catch (Exception $e) {
}
}
// Create projects for organization
$org = $adapter->getOrg();
$proj = id(new PhabricatorProjectQuery())
->setViewer($user)
->withNames(array($org))
->executeOne();
// Create the project
if(!$proj) {
$proj = PhabricatorProject::initializeNewProject($user)
->setViewPolicy('users')
->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN);
$xactions = array();
$xactions[] = id(new PhabricatorProjectTransaction())
->setMetadataValue('c4s:hide', true)
->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE)
->setNewValue($org);
$xactions[] = id(new PhabricatorProjectTransaction())
->setMetadataValue('c4s:hide', true)
->setTransactionType(PhabricatorProjectIconTransaction::TRANSACTIONTYPE)
->setNewValue('organization');
$xactions[] = id(new PhabricatorProjectTransaction())
->setMetadataValue('c4s:hide', true)
->setTransactionType(PhabricatorProjectStatusTransaction::TRANSACTIONTYPE)
->setNewValue(PhabricatorProjectStatus::STATUS_ACTIVE);
$editor = id(new PhabricatorProjectTransactionEditor())
->setActor($admin)
->setContinueOnNoEffect(true)
->setContentSource(PhabricatorContentSource::newForSource('web'))
->applyTransactions($proj, $xactions);
}
// Add the user to the project
$xactions = array();
$type_member = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST;
$xactions[] = id(new PhabricatorProjectTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $type_member)
->setMetadataValue('c4s:hide', true)
->setNewValue(array('+' => array($userPHID => $userPHID)));
$editor = id(new PhabricatorProjectTransactionEditor())
->setActor($admin)
->setContentSource(PhabricatorContentSource::newForSource('web'))
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($proj, $xactions);
// Add organization to custom field in user profile
if(empty($orgvalue)) {
$xactions = array();
$xactions[] = id(new PhabricatorUserTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD)
->setMetadataValue('customfield:key', 'std:user:' . $adapter->getOrgCustom())
->setOldValue('')
->setNewValue($adapter->getOrg() . ' (' . $adapter->getOrgType() . ')');
$xactions[] = id(new PhabricatorUserTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD)
->setMetadataValue('customfield:key', 'user:title')
->setOldValue('')
->setNewValue(' ');
$xactions[] = id(new PhabricatorUserTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD)
->setMetadataValue('customfield:key', 'user:blurb')
->setOldValue('')
->setNewValue(' ');
id(new PhabricatorUserTransactionEditor())
->setActor($user)
->setContentSource(PhabricatorContentSource::newForSource('console'))
->setContinueOnNoEffect(true)
->applyTransactions($user, $xactions);
}
}
unset($unguarded);
}
private function updateProjects($userPHID, $adapter) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
if($adapter == null){
$adapter = $this->getAdapter();
}
$projects = $adapter->getUserProject();
if(count($projects)) {
$admin = id(new PhabricatorUser())
->loadOneWhere('username = %s', 'admin');
$user_projects = id(new PhabricatorProjectQuery())
->setViewer($admin)
->withMemberPHIDs(array($userPHID))
->withPHIDs($projects)
->execute();
$check_proj = array();
foreach($user_projects as $r){
$check_proj[] = $r->getPHID();
}
$results = id(new PhabricatorProjectQuery())
->setViewer($admin)
->withPHIDs($projects)
->execute();
// Add user to project
foreach($results as $project) {
if(in_array($project->getPHID(), $check_proj)){
continue;
}
$edge_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST;
$xactions = array();
$xactions[] = id(new PhabricatorProjectTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $edge_type)
->setMetadataValue('c4s:hide', true)
->setNewValue(array('+' => array($userPHID => $userPHID)));
$editor = id(new PhabricatorProjectTransactionEditor())
->setActor($admin)
->setContentSource(PhabricatorContentSource::newForSource('web'))
->setContinueOnNoEffect(true)
->applyTransactions($project, $xactions);
}
}
unset($unguarded);
}
protected function loadOrCreateAccount_custom($account_id, $request) {
$account = parent::loadOrCreateAccount($account_id);
$adapter = $this->getAdapter();
if(!$adapter) {
return $account;
}
if(!$account->getUserPHID()){
if ($account instanceof PhabricatorExternalAccount
&& $request->getViewer()->getPHID()) {
$this->updateProjects($request->getViewer()->getPHID(), $adapter);
}
// User account not yet created
return $account;
}
$this->updateOrganization($account->getUserPHID(), $adapter);
$this->updateProjects($account->getUserPHID(), $adapter);
return $account;
}
public function willRegisterAccount(PhabricatorExternalAccount $account) {
parent::willRegisterAccount($account);
//$this->updateOrganization($account); // no shib env variables here :/
$this->updateProjects($account->getUserPHID(), null);
}
const KEY_SHIB_SESSION_ID_FIELD = 'shibboleth:session_id_field';
const KEY_SHIB_APPLICATION_ID_FIELD = 'shibboleth:application_id_field';
const KEY_USERID_FIELD = 'shibboleth:userid_field';
const KEY_USERNAME_FIELD = 'shibboleth:username_field';
const KEY_REALNAME_FIELD = 'shibboleth:realname_field';
const KEY_FIRSTNAME_FIELD = 'shibboleth:firstname_field';
const KEY_LASTNAME_FIELD = 'shibboleth:lastname_field';
const KEY_EMAIL_FIELD = 'shibboleth:email_field';
const KEY_ORG_FIELD = 'shibboleth:org';
const KEY_ORG_TYPE_FIELD = 'shibboleth:org_type';
const KEY_ORG_CUSTOM_FIELD = 'shibboleth:org_custom';
const KEY_PAGE_URI_PATTERN = 'shibboleth:page_uri_pattern';
const KEY_IMAGE_URI_PATTERN = 'shibboleth:image_uri_pattern';
const KEY_USERNAME_FROM_REALNAME = 'shibboleth:username_from_realname';
const KEY_ADD_USER_TO_PROJECT = 'shibboleth:add_user_to_project';
const KEY_USER_PROJECT = 'shibboleth:user_project';
private function getPropertyKeys() {
return array(
self::KEY_SHIB_SESSION_ID_FIELD,
self::KEY_SHIB_APPLICATION_ID_FIELD,
self::KEY_USERID_FIELD,
self::KEY_USERNAME_FIELD,
self::KEY_REALNAME_FIELD,
self::KEY_FIRSTNAME_FIELD,
self::KEY_LASTNAME_FIELD,
self::KEY_EMAIL_FIELD,
self::KEY_ORG_FIELD,
self::KEY_ORG_CUSTOM_FIELD,
self::KEY_ORG_TYPE_FIELD,
self::KEY_PAGE_URI_PATTERN,
self::KEY_IMAGE_URI_PATTERN,
self::KEY_USERNAME_FROM_REALNAME,
self::KEY_ADD_USER_TO_PROJECT,
self::KEY_USER_PROJECT
);
}
private function getPropertyLabels() {
return array(
self::KEY_SHIB_SESSION_ID_FIELD => pht('Session ID'),
self::KEY_SHIB_APPLICATION_ID_FIELD => pht('Application ID'),
self::KEY_USERID_FIELD => pht('User ID'),
self::KEY_USERNAME_FIELD => pht('Username'),
self::KEY_REALNAME_FIELD => pht('Real name'),
self::KEY_FIRSTNAME_FIELD => pht('Firstname'),
self::KEY_LASTNAME_FIELD => pht('Lastname'),
self::KEY_EMAIL_FIELD => pht('User emailname'),
self::KEY_ORG_FIELD => pht('Organization'),
self::KEY_ORG_CUSTOM_FIELD => pht('Organization custom field'),
self::KEY_ORG_TYPE_FIELD => pht('Organization type'),
self::KEY_PAGE_URI_PATTERN => pht('User page URI pattern'),
self::KEY_IMAGE_URI_PATTERN => pht('User image URI pattern'),
);
}
public function readFormValuesFromProvider() {
$properties = array();
foreach ($this->getPropertyKeys() as $key) {
$properties[$key] = $this->getProviderConfig()->getProperty($key);
}
return $properties;
}
public function readFormValuesFromRequest(AphrontRequest $request) {
$values = array();
foreach ($this->getPropertyKeys() as $key) {
if($key == self::KEY_USER_PROJECT) {
$values[$key] = $request->getArr($key);
} else {
$values[$key] = $request->getStr($key);
}
}
return $values;
}
public function processEditForm(
AphrontRequest $request,
array $values) {
$errors = array();
$issues = array();
return array($errors, $issues, $values);
}
public function extendEditForm(
AphrontRequest $request,
AphrontFormView $form,
array $values,
array $issues) {
$labels = $this->getPropertyLabels();
$captions = array(
self::KEY_SHIB_SESSION_ID_FIELD => pht('Shibboleth Session ID, e.g.: Shib-Session-ID'),
self::KEY_SHIB_APPLICATION_ID_FIELD => pht('Shibboleth application id, e.g.: Shib-Application-ID'),
self::KEY_USERID_FIELD => pht('Unique user id for internal Phabricator use. e.g.: uniqueID'),
self::KEY_USERNAME_FIELD => pht('Visible username, can be left empty if you choose to autogenerate it. e.g.: username'),
self::KEY_REALNAME_FIELD => pht('Visible in the user profile. e.g.: displayName'),
self::KEY_FIRSTNAME_FIELD => pht('Use this only when you autogenerate username. e.g.: givenName'),
self::KEY_LASTNAME_FIELD => pht('Use this only when you autogenerate username. e.g.: surname'),
self::KEY_EMAIL_FIELD => pht('Unique email address. e.g.: email'),
self::KEY_ORG_FIELD => pht('Organization name. e.g.: homeOrganization'),
self::KEY_ORG_CUSTOM_FIELD => pht('Organization name custom field in Phabricator. e.g.: mycompany:org'),
self::KEY_ORG_TYPE_FIELD => pht('Organization type. e.g.: homeOrganizationType'),
self::KEY_PAGE_URI_PATTERN => pht('URI pattern to a user pag. Add %%s for replacement with the username'),
self::KEY_IMAGE_URI_PATTERN => pht('URI pattern to an image for the user. Add %%s for replacement with the username'),
self::KEY_USER_PROJECT => pht('Project ID to add the user to. e.g.: PHID-PROJ-itwdg3fgxutsrdnqjklb'),
);
// Text fields
foreach ($labels as $key => $label) {
$caption = idx($captions, $key);
$value = idx($values, $key);
$control = null;
$control = id(new AphrontFormTextControl())
->setName($key)
->setLabel($label)
->setCaption($caption)
->setValue($value);
$form->appendChild($control);
}
// Add to project
$form->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
self::KEY_ADD_USER_TO_PROJECT,
1,
hsprintf('<strong>%s:</strong> %s', "Add to default Project",
"Automatically add the new user to a default project"),
idx($values, self::KEY_ADD_USER_TO_PROJECT))
);
$projects = idx($values, self::KEY_USER_PROJECT, array());
$viewer = $request->getViewer();
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Projects'))
->setName(self::KEY_USER_PROJECT)
->setValue($projects)
->setUser($viewer)
//->setDisabled(!idx($values, self::KEY_ADD_USER_TO_PROJECT))
->setDatasource(new PhabricatorProjectDatasource())
);
// Generate username
$form->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
self::KEY_USERNAME_FROM_REALNAME,
1,
hsprintf('<strong>%s:</strong> %s', "Generated username",
"Create a unique username from the surname and firstname which complies with Phabricator policies."),
idx($values, self::KEY_USERNAME_FROM_REALNAME))
);
}
public function renderConfigPropertyTransactionTitle(
PhabricatorAuthProviderConfigTransaction $xaction) {
$author_phid = $xaction->getAuthorPHID();
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$key = $xaction->getMetadataValue(
PhabricatorAuthProviderConfigTransaction::PROPERTY_KEY);
$labels = $this->getPropertyLabels();
if (isset($labels[$key])) {
$label = $labels[$key];
if (!strlen($old)) {
return pht(
'%s set the "%s" value to "%s".',
$xaction->renderHandleLink($author_phid),
$label,
$new);
} else {
return pht(
'%s changed the "%s" value from "%s" to "%s".',
$xaction->renderHandleLink($author_phid),
$label,
$old,
$new);
}
}
return parent::renderConfigPropertyTransactionTitle($xaction);
}
public static function getShibbolethProvider() {
$providers = self::getAllEnabledProviders();
foreach ($providers as $provider) {
if ($provider instanceof PhabricatorAuthProviderShibboleth) {
return $provider;
}
}
return null;
}
}

Event Timeline