diff --git a/app/AvailablePlugin/PrivacyIdeaAuthenticator/Config/Schema/schema.xml b/app/AvailablePlugin/PrivacyIdeaAuthenticator/Config/Schema/schema.xml index ad6d648af..4892f3fcb 100644 --- a/app/AvailablePlugin/PrivacyIdeaAuthenticator/Config/Schema/schema.xml +++ b/app/AvailablePlugin/PrivacyIdeaAuthenticator/Config/Schema/schema.xml @@ -86,4 +86,36 @@ totp_token_id + + + + + + + + REFERENCES cm_privacy_idea_authenticators(id) + + + REFERENCES cm_co_people(id) + + + + + + REFERENCES cm_paper_tokens(id) + + + + + + + co_person_id + + + serial + + + paper_token_id + +
\ No newline at end of file diff --git a/app/AvailablePlugin/PrivacyIdeaAuthenticator/Controller/PaperTokensController.php b/app/AvailablePlugin/PrivacyIdeaAuthenticator/Controller/PaperTokensController.php new file mode 100644 index 000000000..b4fc2edd5 --- /dev/null +++ b/app/AvailablePlugin/PrivacyIdeaAuthenticator/Controller/PaperTokensController.php @@ -0,0 +1,225 @@ + array('Server') + ); + + /** + * Add action to be used when adding a PaperToke as part of an Enrollment Flow + * + * @since COmanage Registry v4.4.0 + */ + + public function add() { + $this->setAction('generate'); + } + + /** + * Generate a Paper Token (backup codes) + * + * @since COmanage Registry v4.4.0 + */ + + public function generate() { + + if(!$this->request->is('get')) { + throw new MethodNotAllowedException(); + } else { + + + $args = array(); + $args['conditions']['CoPerson.id'] = $this->viewVars['vv_co_person']['CoPerson']['id']; + + $ppt = $this->PaperToken->find('first', $args); + + + if($ppt) { + $this->set('title_for_layout', 'Token already Exists'); + $this->Flash->set(_txt('er.privacyideaauthenticator.singular'), array('key' => 'error')); + if(!empty($this->request->params['named']['onFinish'])) { + $this->set('vv_on_finish_url', $this->request->params['named']['onFinish']); + } + } else { + parent::add(); + + $this->set('title_for_layout', 'Generated Backup Codes'); + + if(!empty($this->request->params['named']['onFinish'])) { + $this->set('vv_on_finish_url', $this->request->params['named']['onFinish']); + } + + try { + $tokenInfo = $this->PrivacyIdea->createToken($this->viewVars['vv_authenticator']['PrivacyIdeaAuthenticator'], + $this->viewVars['vv_co_person']['CoPerson']['id']); + + $this->set('vv_token_info', $tokenInfo); + + $newdata = array( + 'PaperToken' => array( + 'co_person_id' => $this->viewVars['vv_co_person']['CoPerson']['id'], + 'serial' => $tokenInfo['serial'] + ) + ); + + $this->generateHistory('generate', $newdata, array()); + + if(!empty($tokenInfo['otps'])) { + $this->set('vv_otps', (array)$tokenInfo['otps']); + } + } + catch(Exception $e) { + $this->Flash->set($e->getMessage(), array('key' => 'error')); + } + } + } + } + + /** + * Callback before other controller methods are invoked or views are rendered. + * + * @since COmanage Registry v4.4.0 + */ + + public function beforeFilter() { + // We operate as a virtual controller, and tweak the settings to pull + // records for the token type that this instantiation is configured for + + $this->uses[] = 'PrivacyIdeaAuthenticator.PrivacyIdea'; + + parent::beforeFilter(); + } + + /** + * Perform any dependency checks required prior to a delete operation. + * This method is intended to be overridden by model-specific controllers. + * - postcondition: Session flash message updated (HTML) or HTTP status returned (REST) + * + * @since COmanage Registry v4.4.0 + * @param Array Current data + * @return boolean true if dependency checks succeed, false otherwise. + */ + + function checkDeleteDependencies($curdata) { + // Remove the Token from the server. This should really happen in + // PaperToken::beforeDelete(), but for some reason (probably some weird + // namespace management issue) that callback isn't being called. It's also + // slightly easier to make the API call from the controller (using the + // PrivacyIdea model). Ultimately, this will need to be rewritten for + // Cake 4. + + // We need the Serial ID, not the token ID + + if(empty($curdata['PaperToken']['serial'])) { + throw new InvalidArgumentException(_txt('er.notprov.id', array(_txt('pl.privacyideaauthenticator.fd.serial')))); + } + + $this->PrivacyIdea->deleteToken($this->viewVars['vv_authenticator']['PrivacyIdeaAuthenticator'], + $curdata['PaperToken']['serial']); + + return true; + } + + /** + * Generate history records for a transaction. This method is intended to be + * overridden by model-specific controllers, and will be called from within a + * try{} block so that HistoryRecord->record() may be called without worrying + * about catching exceptions. + * + * @since COmanage Registry v4.4.0 + * @param String Controller action causing the change + * @param Array Data provided as part of the action (for add/edit) + * @param Array Previous data (for delete/edit) + * @return boolean Whether the function completed successfully (which does not necessarily imply history was recorded) + */ + + public function generateHistory($action, $newdata, $olddata) { + // Build a change string + $cstr = ""; + $cop = null; + $act = null; + + switch($action) { + case 'generate': + $cstr = _txt('rs.generated-a2', array(_txt('ct.paper_tokens.1'), $newdata['PaperToken']['serial'])); + $cop = $newdata['PaperToken']['co_person_id']; + $act = PrivacyIDEActionEnum::TokenGenerated; + break; + case 'delete': + $cstr = _txt('rs.deleted-a2', array(_txt('ct.paper_tokens.1'), $olddata['PaperToken']['serial'])); + $cop = $olddata['PaperToken']['co_person_id']; + $act = PrivacyIDEActionEnum::TokenDeleted; + break; + } + + $this->Co->CoPerson->HistoryRecord->record($cop, + null, + null, + $this->Session->read('Auth.User.co_person_id'), + $act, + $cstr); + + return true; + } + + /** + * Authorization for this Controller, called by Auth component + * - precondition: Session.Auth holds data used for authz decisions + * - postcondition: $permissions set with calculated permissions + * + * @since COmanage Registry v4.4.0 + * @return Array Permissions + */ + + function isAuthorized() { + $roles = $this->Role->calculateCMRoles(); + + // Construct the permission set for this user, which will also be passed to the view. + $p = array(); + + // Determine what operations this user can perform + + // Merge in the permissions calculated by our parent + $p = array_merge($p, $this->calculateParentPermissions(true)); + + // Tokens can't be edited, only deleted + $p['edit'] = false; + + $p['generate'] = isset($p['manage']) ? $p['manage'] : false; + + $this->set('permissions', $p); + return($p[$this->action]); + } +} diff --git a/app/AvailablePlugin/PrivacyIdeaAuthenticator/Controller/PrivacyIdeasController.php b/app/AvailablePlugin/PrivacyIdeaAuthenticator/Controller/PrivacyIdeasController.php index 1647bd705..a951cb0b2 100644 --- a/app/AvailablePlugin/PrivacyIdeaAuthenticator/Controller/PrivacyIdeasController.php +++ b/app/AvailablePlugin/PrivacyIdeaAuthenticator/Controller/PrivacyIdeasController.php @@ -32,7 +32,8 @@ class PrivacyIdeasController extends SAMController { public $name = "PrivacyIdeas"; protected $pi_token_models = array( - PrivacyIDEATokenTypeEnum::TOTP => 'TotpToken' + PrivacyIDEATokenTypeEnum::TOTP => 'TotpToken', + PrivacyIDEATokenTypeEnum::Paper => 'PaperToken' ); /** diff --git a/app/AvailablePlugin/PrivacyIdeaAuthenticator/Lib/enum.php b/app/AvailablePlugin/PrivacyIdeaAuthenticator/Lib/enum.php index e6dd30e2f..a2c2b0cd6 100644 --- a/app/AvailablePlugin/PrivacyIdeaAuthenticator/Lib/enum.php +++ b/app/AvailablePlugin/PrivacyIdeaAuthenticator/Lib/enum.php @@ -31,9 +31,11 @@ class PrivacyIDEActionEnum const TokenConfirmed = 'MFAC'; const TokenDeleted = 'MFAD'; const TokenEdited = 'MFAE'; + const TokenGenerated = 'MFAG'; } class PrivacyIDEATokenTypeEnum { const TOTP = 'TO'; + const Paper = 'PP'; } diff --git a/app/AvailablePlugin/PrivacyIdeaAuthenticator/Lib/lang.php b/app/AvailablePlugin/PrivacyIdeaAuthenticator/Lib/lang.php index e41bd9246..f03096910 100644 --- a/app/AvailablePlugin/PrivacyIdeaAuthenticator/Lib/lang.php +++ b/app/AvailablePlugin/PrivacyIdeaAuthenticator/Lib/lang.php @@ -39,6 +39,8 @@ 'ct.privacy_ideas.pl' => 'PrivacyIDEA Tokens', 'ct.totp_tokens.1' => 'TOTP Token', 'ct.totp_tokens.pl' => 'TOTP Tokens', + 'ct.paper_tokens.1' => 'Backup Codes Token', + 'ct.paper_tokens.pl' => 'Backup Codes Tokens', // Enumerations 'pl.privacyideaauthenticator.en.action' => array( @@ -49,22 +51,32 @@ ), 'en.privacyideaauthenticator.token_type' => array( - PrivacyIDEATokenTypeEnum::TOTP => 'Time-Based OTP (TOTP)' + PrivacyIDEATokenTypeEnum::TOTP => 'Time-Based OTP (TOTP)', + PrivacyIDEATokenTypeEnum::Paper => 'Backup Codes' ), // Error messages 'er.privacyideaauthenticator.code' => 'Invalid code, please try again', 'er.privacyideaauthenticator.identifier' => 'No Identifier of type "%1$s" found for CO Person', + 'er.privacyideaauthenticator.singular' => 'A token already exists; please delete that one before generating a new one', // Plugin texts 'pl.privacyideaauthenticator.alt.google' => 'QR Code for Google Authenticator', + 'pl.privacyideaauthenticator.alt.paper' => 'Paper token list', 'pl.privacyideaauthenticator.fd.identifier_type' => 'Identifier Type', 'pl.privacyideaauthenticator.fd.realm' => 'PrivacyIDEA Realm', 'pl.privacyideaauthenticator.fd.serial' => 'Serial', 'pl.privacyideaauthenticator.fd.token_type' => 'Token Type', 'pl.privacyideaauthenticator.fd.validation_server' => 'Validation API Server', - 'pl.privacyideaauthenticator.status' => '%1$s token(s) registered, %2$s confirmed', + 'pl.privacyideaauthenticator.totpstatus' => '%1$s token(s) registered, %2$s confirmed', + 'pl.privacyideaauthenticator.paperstatus' => '%1$s token(s) registered', 'pl.privacyideaauthenticator.token.confirmed' => 'Token Confirmed', 'pl.privacyideaauthenticator.totp.step1' => 'First, scan the QR Code to add this token to Google Authenticator', - 'pl.privacyideaauthenticator.totp.step2' => 'Then, enter the current code from the Google Authenticator app to confirm' + 'pl.privacyideaauthenticator.totp.step2' => 'Then, enter the current code from the Google Authenticator app to confirm', + 'pl.privacyideaauthenticator.paper.intro' => 'Use the backup codes in order, one after the other. Mark off used values.', + 'pl.privacyideaauthenticator.paper.caution' => 'Backup codes are a weak second factor. Please assure no one has access to these values. Store them in a safe location', + 'pl.privacyideaauthenticator.paper.warning' => 'Before you leave this page, please confirm that you have copied your backup codes. YOU WILL NOT SEE THEM AGAIN.', + 'pl.privacyideaauthenticator.paper.dialog' => 'Before you leave this page you must save your backup codes by copying or printing.', + 'pl.privacyideaauthenticator.paper.dialog.btn' => 'I understand', + 'pl.privacyideaauthenticator.paper.continue' => 'Once you have copied your backup codes, you must continue to the next step', ); diff --git a/app/AvailablePlugin/PrivacyIdeaAuthenticator/Model/PaperToken.php b/app/AvailablePlugin/PrivacyIdeaAuthenticator/Model/PaperToken.php new file mode 100644 index 000000000..1be5a86d5 --- /dev/null +++ b/app/AvailablePlugin/PrivacyIdeaAuthenticator/Model/PaperToken.php @@ -0,0 +1,67 @@ + array('priority' => 5), + 'Provisioner'); + + // Association rules from this model to other models + public $belongsTo = array( + "PrivacyIdeaAuthenticator.PrivacyIdeaAuthenticator", + "CoPerson" + ); + + // Default display field for cake generated views + public $displayField = "password_type"; + + // Validation rules for table elements + public $validate = array( + 'privacy_idea_authenticator_id' => array( + 'rule' => 'numeric', + 'required' => true, + 'allowEmpty' => false + ), + 'co_person_id' => array( + 'rule' => 'numeric', + 'required' => true, + 'allowEmpty' => false + ), + 'serial' => array( + 'rule' => 'notBlank', + 'required' => true, + 'allowEmpty' => false + ) + ); +} \ No newline at end of file diff --git a/app/AvailablePlugin/PrivacyIdeaAuthenticator/Model/PrivacyIdea.php b/app/AvailablePlugin/PrivacyIdeaAuthenticator/Model/PrivacyIdea.php index 20582f657..074c72562 100644 --- a/app/AvailablePlugin/PrivacyIdeaAuthenticator/Model/PrivacyIdea.php +++ b/app/AvailablePlugin/PrivacyIdeaAuthenticator/Model/PrivacyIdea.php @@ -28,6 +28,7 @@ App::uses("Identifier", "Model"); App::uses("Server", "Model"); App::uses("TotpToken", "PrivacyIdeaAuthenticator.Model"); +App::uses("PaperToken", "PrivacyIdeaAuthenticator.Model"); class PrivacyIdea extends AppModel { // Define class name for cake @@ -164,18 +165,27 @@ public function createToken($privacyIdeaAuthenticator, $coPersonId) { $identifier = $this->lookupIdentifier($privacyIdeaAuthenticator['identifier_type'], $coPersonId); $Http = $this->connect($privacyIdeaAuthenticator['server_id']); - - // XXX For now we only set params for TOTP tokens. We'll need to refactor - // this section when we add support for additional token types. - + + $token_type = $privacyIdeaAuthenticator['token_type']; + $params = array( - 'type' => 'totp', 'user' => $identifier, 'realm' => $privacyIdeaAuthenticator['realm'], 'genkey' => '1', - 'optlen' => '6' ); - + + switch ($token_type) { + case PrivacyIDEATokenTypeEnum::TOTP: + $params['type'] = 'totp'; + $params['otplen'] = '6'; + break; + + case PrivacyIDEATokenTypeEnum::Paper: + $params['type'] = 'paper'; + $params['otplen'] = '8'; + break; + } + $response = $Http->post("/token/init", $params, $this->requestCfg); $jresponse = json_decode($response); @@ -188,15 +198,29 @@ public function createToken($privacyIdeaAuthenticator, $coPersonId) { 'privacy_idea_authenticator_id' => $privacyIdeaAuthenticator['id'], 'co_person_id' => $coPersonId, 'serial' => $jresponse->detail->serial, - 'confirmed' => false ); - - $TotpToken = new TotpToken(); - $TotpToken->save($token); - - // We don't persist the QR Data, but we do need to return it for rendering - $token['qr_data'] = $jresponse->detail->googleurl->img; - + + switch ($token_type) { + case PrivacyIDEATokenTypeEnum::TOTP: + $token['confirmed'] = false; + $TotpToken = new TotpToken(); + if (!$TotpToken->save($token)) { + throw new RuntimeException(_txt('er.db.save-a', array('TotpToken'))); + } + // We don't persist the QR Data, but we do need to return it for rendering + $token['qr_data'] = $jresponse->detail->googleurl->img; + break; + + case PrivacyIDEATokenTypeEnum::Paper: + $PaperToken = new PaperToken(); + if (!$PaperToken->save($token)) { + throw new RuntimeException(_txt('er.db.save-a', array('PaperToken'))); + } + // We don't persist the codes themselves but need to present them to the user for copying/printing + $token['otps'] = (array)$jresponse->detail->otps; + break; + } + return $token; } diff --git a/app/AvailablePlugin/PrivacyIdeaAuthenticator/Model/PrivacyIdeaAuthenticator.php b/app/AvailablePlugin/PrivacyIdeaAuthenticator/Model/PrivacyIdeaAuthenticator.php index 3f6b153d6..b3dcaa220 100644 --- a/app/AvailablePlugin/PrivacyIdeaAuthenticator/Model/PrivacyIdeaAuthenticator.php +++ b/app/AvailablePlugin/PrivacyIdeaAuthenticator/Model/PrivacyIdeaAuthenticator.php @@ -35,27 +35,28 @@ class PrivacyIdeaAuthenticator extends AuthenticatorBackend { // Required by COmanage Plugins public $cmPluginType = "authenticator"; - // Add behaviors + // Add behaviors public $actsAs = array('Containable'); // Document foreign keys public $cmPluginHasMany = array( - "CoPerson" => array("TotpToken") - ); + "CoPerson" => array("TotpToken", "PaperToken"), + ); - // Association rules from this model to other models - public $belongsTo = array( - "Authenticator", + // Association rules from this model to other models + public $belongsTo = array( + "Authenticator", "Server", "ValidationServer" => array( 'className' => 'Server', 'foreignKey' => 'validation_server_id' ) - ); + ); - public $hasMany = array( - "PrivacyIdeaAuthenticator.TotpToken" - ); + public $hasMany = array( + "PrivacyIdeaAuthenticator.TotpToken", + "PrivacyIdeaAuthenticator.PaperToken" + ); // Default display field for cake generated views public $displayField = "realm"; @@ -92,7 +93,7 @@ class PrivacyIdeaAuthenticator extends AuthenticatorBackend { 'allowEmpty' => false ), 'token_type' => array( - 'rule' => array('inList', array(PrivacyIDEATokenTypeEnum::TOTP)), + 'rule' => array('inList', array(PrivacyIDEATokenTypeEnum::TOTP, PrivacyIDEATokenTypeEnum::Paper)), 'required' => true, 'allowEmpty' => false ), @@ -125,7 +126,7 @@ public function cmPluginMenus() { return array(); } - /** + /** * Obtain current data suitable for passing to manage() and provisioners. * * @since COmanage Registry v4.0.0 @@ -134,16 +135,27 @@ public function cmPluginMenus() { * @param integer $coPersonId CO Person ID * @return Array As returned by find * @throws RuntimeException - */ + */ + + public function current($id, $backendId, $coPersonId) { - public function current($id, $backendId, $coPersonId) { $args = array(); - $args['conditions']['TotpToken.privacy_idea_authenticator_id'] = $backendId; + $args['conditions']['TotpToken.co_person_id'] = $coPersonId; + $args['conditions']['TotpToken.privacy_idea_authenticator_id'] = $backendId; $args['contain'] = false; - - return $this->TotpToken->find('all', $args); - } + $results = $this->TotpToken->find('all', $args); + + if(empty($results)) { + $args = array(); + $args['conditions']['PaperToken.co_person_id'] = $coPersonId; + $args['conditions']['PaperToken.privacy_idea_authenticator_id'] = $backendId; + $args['contain'] = false; + $results = $this->PaperToken->find('all', $args); + } + + return $results; + } /** * Perform backend specific actions on a lock operation. @@ -172,57 +184,79 @@ public function lock($id, $coPersonId) { return true; } - /** - * Reset Authenticator data for a CO Person. - * - * @since COmanage Registry v3.1.0 - * @param integer $coPersonId CO Person ID - * @param integer $actorCoPersonId Actor CO Person ID - * @return boolean true on success - */ + /** + * Reset Authenticator data for a CO Person. + * + * @since COmanage Registry v3.1.0 + * @param integer $coPersonId CO Person ID + * @param integer $actorCoPersonId Actor CO Person ID + * @return boolean true on success + */ public function reset($coPersonId, $actorCoPersonId) { - // It's not immediately obvious what we should do on a reset... so for now + // It's not immediately obvious what we should do on a reset... so for now // we return false until we have better requirements. - return false; - } + return false; + } - /** - * Obtain the current Authenticator status for a CO Person. - * - * @since COmanage Registry v4.0.0 - * @param integer $coPersonId CO Person ID - * @return Array Array with values - * status: AuthenticatorStatusEnum - * comment: Human readable string, visible to the CO Person - */ + /** + * Obtain the current Authenticator status for a CO Person. + * + * @since COmanage Registry v4.0.0 + * @param integer $coPersonId CO Person ID + * @return Array Array with values + * status: AuthenticatorStatusEnum + * comment: Human readable string, visible to the CO Person + */ - public function status($coPersonId) { - // We can have more than one Authenticator, but only of the type token_type. - // For now, we only work with TotpTokens. - + public function status($coPersonId) { + $status = AuthenticatorStatusEnum::NotSet; $comment = _txt('fd.set.not'); - + + $pcfg = $this->getConfig(); + + $token_type = $pcfg['PrivacyIdeaAuthenticator']['token_type']; + $piauth_id = $pcfg['PrivacyIdeaAuthenticator']['id']; + $args = array(); - $args['conditions']['TotpToken.co_person_id'] = $coPersonId; - $args['contain'] = false; - - $tokens = $this->TotpToken->find('all', $args); - - if(count($tokens) > 0) { - $confirmed = Hash::extract($tokens, '{n}.TotpToken[confirmed=true]'); - - $status = AuthenticatorStatusEnum::Active; - $comment = _txt('pl.privacyideaauthenticator.status', array(count($tokens), count($confirmed))); + + switch ($token_type) { + case PrivacyIDEATokenTypeEnum::TOTP: + $args['conditions']['TotpToken.co_person_id'] = $coPersonId; + $args['conditions']['TotpToken.privacy_idea_authenticator_id'] = $piauth_id; + $args['contain'] = false; + + $tokens = $this->TotpToken->find('all', $args); + + if(count($tokens) > 0) { + $confirmed = Hash::extract($tokens, '{n}.TotpToken[confirmed=true]'); + + $status = AuthenticatorStatusEnum::Active; + $comment = _txt('pl.privacyideaauthenticator.totpstatus', array(count($tokens), count($confirmed))); + } + break; + + case PrivacyIDEATokenTypeEnum::Paper: + $args['conditions']['PaperToken.co_person_id'] = $coPersonId; + $args['conditions']['PaperToken.privacy_idea_authenticator_id'] = $piauth_id; + $args['contain'] = false; + + $tokens = $this->PaperToken->find('all', $args); + + if(count($tokens) > 0) { + $status = AuthenticatorStatusEnum::Active; + $comment = _txt('pl.privacyideaauthenticator.paperstatus', array(count($tokens))); + } + break; } - - return array( - 'status' => $status, - 'comment' => $comment - ); - } - + + return array( + 'status' => $status, + 'comment' => $comment + ); + } + /** * Perform backend specific actions on an unlock operation. * diff --git a/app/AvailablePlugin/PrivacyIdeaAuthenticator/View/PaperTokens/fields.inc b/app/AvailablePlugin/PrivacyIdeaAuthenticator/View/PaperTokens/fields.inc new file mode 100644 index 000000000..924370312 --- /dev/null +++ b/app/AvailablePlugin/PrivacyIdeaAuthenticator/View/PaperTokens/fields.inc @@ -0,0 +1,123 @@ + +Form->hidden('privacy_idea_authenticator_id', + array('default' => $vv_authenticator['PrivacyIdeaAuthenticator']['id'])) . "\n"; + print $this->Form->hidden('co_person_id', array('default' => $vv_co_person['CoPerson']['id'])) . "\n"; + + if(!empty($vv_token_info['otps'])) { + print $this->Form->hidden('otps', array('default' => $vv_token_info['otps'])); + } + + if(!empty($vv_token_info['serial'])) { + print $this->Form->hidden('serial', array('default' => $vv_token_info['serial'])); + } + + if(!empty($vv_on_finish_url)) { + print $this->Form->hidden('onFinish', array('default' => $vv_on_finish_url)); + } + + // Add breadcrumbs + print $this->element("coCrumb", array('authenticator' => 'PrivacyIdea')); +?> + + +

+ +

+
+ info + +
+
+ warning + +
+ +
+ + + + + + + + + $otp): ?> + + + + + + + +
#OTP
+
+ +action == 'view'): ?> + + + diff --git a/app/AvailablePlugin/PrivacyIdeaAuthenticator/View/PaperTokens/generate.ctp b/app/AvailablePlugin/PrivacyIdeaAuthenticator/View/PaperTokens/generate.ctp new file mode 100644 index 000000000..95f7b9ce3 --- /dev/null +++ b/app/AvailablePlugin/PrivacyIdeaAuthenticator/View/PaperTokens/generate.ctp @@ -0,0 +1,149 @@ + +request->params['named']['onFinish'])) { + $params['topLinks'][] = $this->Html->link(_txt('op.skip'), + urldecode($this->request->params['named']['onFinish']), + array('class' => 'forwardbutton')); + } + + // Add breadcrumbs + print $this->element("coCrumb", array('authenticator' => 'PrivacyIdea')); +?> + + +

+ +

+
+ info + +
+
+ warning + + request->params['named']['onFinish'])): ?> + + +
+ +
+ + + + + + + + + $otp): ?> + + + + + + +
#OTP
+
+ request->params['named']['onFinish'])): ?> + Html->link(_txt('op.cont'), + urldecode($this->request->params['named']['onFinish']), + array('class' => 'btn btn-primary btn-lg')); + ?> + + + + +
+

+ + +

+
+request->params['named']['onFinish'])): ?> + Html->link(_txt('op.cont'), + urldecode($this->request->params['named']['onFinish']), + array('class' => 'btn btn-primary btn-lg')); + ?> +action == 'view'): ?> + + + + diff --git a/app/AvailablePlugin/PrivacyIdeaAuthenticator/View/PaperTokens/index.ctp b/app/AvailablePlugin/PrivacyIdeaAuthenticator/View/PaperTokens/index.ctp new file mode 100644 index 000000000..3e9a16cd7 --- /dev/null +++ b/app/AvailablePlugin/PrivacyIdeaAuthenticator/View/PaperTokens/index.ctp @@ -0,0 +1,125 @@ +element("coCrumb", array('authenticator' => 'PrivacyIdea')); + + // Add page title + $params = array(); + $params['title'] = $title_for_layout; + + // Add top links + $params['topLinks'] = array(); + + if($permissions['add'] && !$paper_tokens) { + $params['topLinks'][] = $this->Html->link( + _txt('op.generate', array($title_for_layout)), + array( + 'plugin' => 'privacy_idea_authenticator', // XXX can inflect from $vv_authenticator['Authenticator']['plugin'] + 'controller' => 'paper_tokens', + 'action' => 'generate', + 'authenticatorid' => $vv_authenticator['Authenticator']['id'], + 'copersonid' => $vv_co_person['CoPerson']['id'] + ), + array('class' => 'addbutton') + ); + } + + print $this->element("pageTitleAndButtons", $params); +?> + + + + + + + + + + + + + + + + + + + + +
Paginator->sort('token_type', _txt('pl.privacyideaauthenticator.fd.token_type')); ?>Paginator->sort('serial', _txt('pl.privacyideaauthenticator.fd.serial')); ?>
+ Html->link( + // $t['PaperToken']['serial'], + // array( + // 'plugin' => 'privacy_idea_authenticator', // XXX can inflect from $vv_authenticator['Authenticator']['plugin'] + // 'controller' => 'paper_tokens', + // 'action' => 'view', + // $t['PaperToken']['id'] + // ) + // ); + print $t['PaperToken']['serial']; + ?> + + Html->link( + // _txt('op.view'), + // array( + // 'plugin' => 'privacy_idea_authenticator', + // 'controller' => 'paper_tokens', + // 'action' => 'view', + // $t['PaperToken']['id'] + // ), + // array('class' => 'viewbutton') + // ) . "\n"; + // } + + if($permissions['delete']) { + print ''; + } + ?> +
+ +element("pagination"); diff --git a/app/Config/bootstrap.php b/app/Config/bootstrap.php index 81878787b..a3b093e52 100644 --- a/app/Config/bootstrap.php +++ b/app/Config/bootstrap.php @@ -128,12 +128,12 @@ */ App::uses('CakeLog', 'Log'); CakeLog::config('debug', array( - 'engine' => 'FileLog', + 'engine' => 'ConsoleLog', 'types' => array('notice', 'info', 'debug'), 'file' => 'debug', )); CakeLog::config('error', array( - 'engine' => 'FileLog', + 'engine' => 'ConsoleLog', 'types' => array('warning', 'error', 'critical', 'alert', 'emergency'), 'file' => 'error', )); diff --git a/app/Lib/lang.php b/app/Lib/lang.php index 66775d709..62d092d0b 100644 --- a/app/Lib/lang.php +++ b/app/Lib/lang.php @@ -2424,6 +2424,7 @@ 'rs.ev.ver-a' => 'Email Address %1$s verified by %2$s', 'rs.expunged' => 'Record Expunged', 'rs.found.cnt' => '%1$s Record(s) Found', + 'rs.generated-a2' => '%1$s "%2$s" Generated', 'rs.gr.added' => 'Added CO Group "%1$s"', 'rs.gr.deleted' => 'Deleted CO Group "%1$s"', 'rs.gr.reconcile.ok' => 'Reconciliation Successful', diff --git a/app/Plugin/LdapProvisioner/Model/CoLdapProvisionerTarget.php b/app/Plugin/LdapProvisioner/Model/CoLdapProvisionerTarget.php index 6705e26a2..a9398230d 100644 --- a/app/Plugin/LdapProvisioner/Model/CoLdapProvisionerTarget.php +++ b/app/Plugin/LdapProvisioner/Model/CoLdapProvisionerTarget.php @@ -220,6 +220,7 @@ protected function assembleAttributes($coProvisioningTargetData, $dns, $dnAttributes, $uam) { + // First see if we're working with a Group record or a Person record $person = isset($provisioningData['CoPerson']['id']); $group = isset($provisioningData['CoGroup']['id']); @@ -901,7 +902,16 @@ protected function assembleAttributes($coProvisioningTargetData, } } } - + if(!empty($provisioningData['PaperToken'])) { + foreach($provisioningData['PaperToken'] as $pt) { + if($attropts) { + $lrattr = $lattr . ";type-paper"; + $attributes[$lrattr][] = $pt['serial']; + } else { + $attributes[$attr][] = $pt['serial']; + } + } + } if(!$attropts && empty($attributes[$attr]) && !$modify) { // This is the same as the approach using $found, but without an extra variable unset($attributes[$attr]); diff --git a/app/webroot/css/co-base.css b/app/webroot/css/co-base.css index a79e38ff8..9eae1b174 100644 --- a/app/webroot/css/co-base.css +++ b/app/webroot/css/co-base.css @@ -474,6 +474,12 @@ form#notificationStatus { font-size: 1.6rem; margin: 0 0.25em 0 0; } +#content .co-info-topbox.warn-level-a { + background-color: #fcc; +} +#content .co-info-topbox.warn-level-a .material-icons { + color: #c00; +} .material-icons.error { color: #d00; }