Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CO-2677 - Add capability to generate, print, and delete Privacy Idea backup codes as an authenticator for a CO Person #525

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,36 @@
<col>totp_token_id</col>
</index>
</table>

<table name="paper_tokens">
<field name="id" type="I">
<key />
<autoincrement />
</field>
<field name="privacy_idea_authenticator_id" type="I">
<constraint>REFERENCES cm_privacy_idea_authenticators(id)</constraint>
</field>
<field name="co_person_id" type="I">
<constraint>REFERENCES cm_co_people(id)</constraint>
</field>
<field name="serial" type="C" size="80" />
<field name="created" type="T" />
<field name="modified" type="T" />
<field name="paper_token_id" type="I">
<constraint>REFERENCES cm_paper_tokens(id)</constraint>
</field>
<field name="revision" type="I" />
<field name="deleted" type="L" />
<field name="actor_identifier" type="C" size="256" />

<index name="paper_tokens_i1">
<col>co_person_id</col>
</index>
<index name="paper_tokens_i2">
<col>serial</col>
</index>
<index name="paper_tokens_i3">
<col>paper_token_id</col>
</index>
</table>
</schema>
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
<?php
/**
* COmanage Registry Paper Token Controller
*
* Portions licensed to the University Corporation for Advanced Internet
* Development, Inc. ("UCAID") under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* UCAID licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @link http://www.internet2.edu/comanage COmanage Project
* @package registry-plugin
* @since COmanage Registry v4.4.0
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/

App::uses("SAMController", "Controller");

class PaperTokensController extends SAMController {
// Class name, used by Cake
public $name = "PaperTokens";

public $requires_person = true;

public $view_contains = array(
'PrivacyIdeaAuthenticator' => 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]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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'
);

/**
Expand Down
2 changes: 2 additions & 0 deletions app/AvailablePlugin/PrivacyIdeaAuthenticator/Lib/enum.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
18 changes: 15 additions & 3 deletions app/AvailablePlugin/PrivacyIdeaAuthenticator/Lib/lang.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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',
);
67 changes: 67 additions & 0 deletions app/AvailablePlugin/PrivacyIdeaAuthenticator/Model/PaperToken.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php
/**
* COmanage Registry PaperToken Model
*
* Portions licensed to the University Corporation for Advanced Internet
* Development, Inc. ("UCAID") under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* UCAID licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @link http://www.internet2.edu/comanage COmanage Project
* @package registry-plugin
* @since COmanage Registry v4.2.0
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/

class PaperToken extends AppModel {
// Define class name for cake
public $name = "PaperToken";

// Current schema version for API
public $version = "1.0";

// Add behaviors
public $actsAs = array('Containable',
'Changelog' => 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
)
);
}
Loading