diff --git a/PhabricatorAuthProviderShibboleth.php b/PhabricatorAuthProviderShibboleth.php index 3068cf8..7d25100 100644 --- a/PhabricatorAuthProviderShibboleth.php +++ b/PhabricatorAuthProviderShibboleth.php @@ -1,330 +1,470 @@ 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)); + $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) { - $attributes = array( - 'method' => 'GET', - 'uri' => $this->getLoginURI(), - ); - return $this->renderStandardLoginButton($request, $mode, $attributes); + $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(<<addPadding(PHUI::PADDING_LARGE) + ->appendChild($remarkup); + $dialog->appendChild($frame); + return $dialog; } public function isLoginFormAButton() { - return true; + 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; - $adapter = $this->getAdapter(); - $env = array(); - $env_names = $adapter->getEnvNames(); - foreach ($env_names as $h) { - $env[$h] = $_SERVER[$h]; - } - if (! $adapter->setUserDataFromRequest($env)) { + 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($account_id, $adapter), $response); + return array($this->loadOrCreateAccount_custom($account_id, $request), $response); } - protected function loadOrCreateAccount($account_id, $adapter) { - $user = parent::loadOrCreateAccount($account_id); + 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); + + $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) { + } + } - if(!$adapter->getAdapter() || !strlen($adapter->getUserProject)) { - return $user; + 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 PhabricatorUserProfileEditor()) + ->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)) { - // Get project - $results = id(new PhabricatorProjectQuery()) - ->withIDs(array($adapter->getUserProject())) + $admin = id(new PhabricatorUser()) + ->loadOneWhere('isAdmin = 1'); + + $user_projects = id(new PhabricatorProjectQuery()) + ->setViewer($admin) + ->withMemberPHIDs(array($userPHID)) + ->withPHIDs($projects) ->execute(); - if ($results) { - $project = head($results); - } else { - $project = null; + $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; + } + $spec = array( + '+' => array($userPHID => $userPHID), + ); + $edge_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; + $xactions = array(); + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $edge_type) + ->setNewValue($spec); + $editor = id(new PhabricatorProjectTransactionEditor()) + ->setActor($admin) + ->setContentSource(new PhabricatorConsoleContentSource()) + ->setContinueOnNoEffect(true) + ->applyTransactions($project, $xactions); + } } + unset($unguarded); + } - // Add user to project - $spec = array( - $operation => array($user->getPHID() => $user->getPHID()), - ); - $edge_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; - $xactions = array(); - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $edge_type) - ->setNewValue($spec); - $editor = id(new PhabricatorProjectTransactionEditor()) - ->setActor($user) - ->setContentSource(PhabricatorContentSource::newConsoleSource()) - ->setContinueOnNoEffect(true) - ->applyTransactions($project, $xactions); - - return $user; + 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('%s: %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('%s: %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; } } diff --git a/PhutilAuthAdapterShibboleth.php b/PhutilAuthAdapterShibboleth.php index 6a12ae8..29eb93c 100644 --- a/PhutilAuthAdapterShibboleth.php +++ b/PhutilAuthAdapterShibboleth.php @@ -1,227 +1,266 @@ shibSessionIdField = $value; return $this; } public function setShibApplicationIdField($value) { $this->shibApplicationIdField = $value; return $this; } public function setUseridField($value) { $this->useridField = $value; return $this; } public function setUsernameField($value) { $this->usernameField = $value; return $this; } public function setRealnameField($value) { $this->realnameField = $value; return $this; } public function setEmailField($value) { $this->emailField = $value; return $this; } public function setFirstnameField($value) { $this->firstnameField = $value; return $this; } public function setLastnameField($value) { $this->lastnameField = $value; return $this; } + public function setOrgField($value) { + $this->orgField = $value; + return $this; + } + + public function setOrgCustomField($value) { + $this->orgCustomField = $value; + return $this; + } + + public function setOrgTypeField($value) { + $this->orgTypeField = $value; + return $this; + } + public function setPageURIPattern($value) { $this->pageURIPattern = $value; return $this; } public function setImageURIPattern($value) { $this->imageURIPattern = $value; return $this; } public function setIsGeneratedUsername($value) { $this->usernameFromRealname = $value; return $this; } public function setAddUserToProject($value) { $this->addUserToProject = $value; return $this; } public function setUserProject($value) { $this->userProject = $value; return $this; } // // Implementation of PhutilAuthAdapter interface. // User information getters. // public function getAccountID() { return $this->userid; } public function getAdapterType() { return 'shibboleth'; } public function getAdapterDomain() { return 'self'; } public function getAccountEmail() { return $this->email; } public function getAccountName() { return $this->username; } public function getAccountURI() { if (strlen($this->pageURIPattern)) { return sprintf($this->pageURIPattern, $this->username); } return null; } public function getAccountImageURI() { if (strlen($this->imageURIPattern)) { return sprintf($this->imageURIPattern, $this->username); } return null; } public function getAccountRealName() { return $this->realname; } public function getAddUserToProject() { return $this->addUserToProject; } public function getUserProject() { return $this->userProject; } + public function getOrg() { + return $this->org; + } + + public function getOrgType() { + return $this->orgType; + } + + public function getOrgCustom() { + return $this->orgCustomField; + } + // - // Extraction of user information from environement variables. + // Extraction of user information from environment variables. // public function getEnvNames() { return array( $this->shibSessionIdField, $this->shibApplicationIdField, $this->useridField, $this->usernameField, $this->realnameField, $this->firstnameField, $this->lastnameField, $this->emailField, + $this->orgField, + $this->orgTypeField, ); } public function setUserDataFromRequest($env) { $this->shibSessionId = $env[$this->shibSessionIdField]; $this->shibApplicationId = $env[$this->shibApplicationIdField]; $this->userid = $env[$this->useridField]; - $this->username = $env[$this->usernameField]; $this->realname = $env[$this->realnameField]; $this->firstname = $env[$this->firstnameField]; $this->lastname = $env[$this->lastnameField]; $this->email = $env[$this->emailField]; + $this->org = $env[$this->orgField]; + $this->orgType = $env[$this->orgTypeField]; if ($this->usernameFromRealname) { for ($len=0; $len < strlen($this->firstname); $len++) { $username = $this->generateUsername($len); $user_exists = id(new PhabricatorPeopleQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->setLimit(1) ->withUsernames(array($username)) ->execute(); $this->username = $username; if (!$user_exists) { break; } } + } else { + $this->username = $env[$this->usernameField]; } if(!strlen($this->realname) && strlen($this->firstname) && strlen($this->lastname)){ $this->realname = $this->firstname . ' ' . $this->lastname; } if (!strlen($this->shibSessionId) || !strlen($this->shibApplicationId) || !strlen($this->userid) || !strlen($this->username) || !strlen($this->realname) || !strlen($this->email) ) { phlog("SHIB ERROR"); phlog("SessionID: " . $this->shibApplicationId . " (" . strlen($this->shibApplicationId) . ")"); phlog("ApplicationID: " . $this->shibSessionId . " (" . strlen($this->shibSessionId) . ")"); phlog("UserID: " . $this->userid . " (" . strlen($this->userid) . ")"); phlog("Username: " . $this->username . " (" . strlen($this->username) . ")"); phlog("Realname: " . $this->realname . " (" . strlen($this->realname) . ")"); phlog("Firstname: " . $this->firstname . " (" . strlen($this->firstname) . ")"); phlog("Lastname: " . $this->lastname . " (" . strlen($this->lastname) . ")"); phlog("Email: " . $this->email . " (" . strlen($this->email) . ")"); + phlog("Org: " . $this->org . " (" . strlen($this->org) . ")"); + phlog("Org type: " . $this->orgType . " (" . strlen($this->orgType) . ")"); return false; } return $this; } private function generateUsername($len) { return $this->cleanName($this->lastname) . substr($this->cleanName($this->firstname), 0, $len); } private function cleanName($name) { - $clean = iconv('UTF-8', 'ASCII//TRANSLIT', $name); - $clean = preg_replace("/[ -]/", '', $clean); - $clean = strtolower(trim($clean)); - return $clean; + $clean = iconv('UTF-8', 'ASCII//TRANSLIT', $name); + $clean = preg_replace("/[ -]/", '', $clean); + $clean = strtolower(trim($clean)); + return $clean; } }