Skip to content

Commit

Permalink
Acl: XDUser Changes (ubccr#146)
Browse files Browse the repository at this point in the history
- Bringing the format of XDUser up to speed before continuing with ACL
  modifications.

Adding models that support the Acl transition

- Adding a number of new models that act as:
    - a way to document and pin our code to a particular set of table columns
      for a particular code version
    - a shim between PDO result sets and the rest of our code as they have been
      built with the following use case in mind:
          $results = array();
          $rows = $db->query("SELECT * FROM some_table;");
          foreach($rows as $row) {
              $results []= new Model($row);
          }
      the model code takes care of mapping all of the columns to the correct
      properties. It works on a whitelist concept so any extraneous columns are
      ignored for the purposes property setting.
    - I added the @method annotations purely to make users lives a little easier
      as it explicitly lays out which functions are supported by each class.
      Meaning if they're new they don't need to dig into DBObject to find out
      what the $PROP_MAP does ( although they can obviously ). They can just
      rely on the class providing those methods.
- Added a 'service' class Acls ( service naming just being the plural of the
  model they support ) that provides a number of helper functions / encapsulates
  a great deal about the underlying table structure allowing the user to provide
  a few bits of information and let the service take care of the rest.

Porting XDUser changes from acls branch to this one

- These changes update the portions of the XDUser api to use the new acl tables
  / relations where possible. They make use of the new Model / Service classes
  that were added in previous commits.

Migrating isDeveloper to be based off of acls

- isDeveloper was previously based off of the roles array, now it's based off of
  acls.

Initializing _acls in the constructor of XDUser

- the _acls property needs to be initialized when XDUser is.

Updating the removeUser to use isPublicUser instead of active_role

- Just migrating this like the other instances of comparison to ROLE_ID_PUBLIC

While the return value cannot be false it could be empty

- to prevent accessing an array entry that doesn't exist added a conditional
  that only allow the access to occur if there is 1 or more records returned.

XDUserTest: tests the modified and newly added acl functions

- Huge caveat: to run these tests you need a fully functional xdmod install.
  Both database and generated classes.

- I have not included the generated coverage html but the % lines covered for
  the functions that were modified are as follows:
      - isDeveloper:                 100%
      - saveUser:                    95%
      - getToken:                    100%
      - getTokenExpiration:          100%
      - removeUser:                  96%
      - setInstitution:              90%
      - disassociateWithInstitution: 90%
      - setOrganizations:            90%
      - getRoles:                    100%
      - setRoles:                    100%
      - getPrimaryRole:              100%
      - getActiveRole:               100%
      - getAcls:                     100%
      - setAcls:                     100%
      - addAcl:                      100%
      - hasAcl:                      100%
      - hasAcls:                     100%
      - getUserByUserName:           100%

Namespace formatting fix

- Removed the extra line after the namespace.

Fixed invalid sql syntax

- The SQL syntax for 'deleteAcl' was invalid. You are not allowed to alias
  tables in a DELETE statement.

Simplifying the query / logic of addUserAcl

- Instead of issuing two database queries, one to find and another one to insert.
  We instead INSERT the results of a SELECT that will only return results if the
  record that would be inserted does not exist ( WHERE cur.user_acl_id IS NULL
  ).

Updating Acls function documentation

- Updated / Added documentation where it was missing / incorrect.

Updating XDUserTest to utilize pi

- Open XDMoD does not have the program officer user so use pi instead.
- Also update the default institution value to 0 as opposed to -1.

Clarifying getPublicUser

- minor code refactor to increase legibility per code review comment by
  @jpwhite4

Adding Component Test folder

- Adding a place for Component Tests to live. i.e. tests that require access to
  the database but are not doing so via the REST interface.

Adding sql script to create the Public User

- We now have the code in place to deal with an actual Public User, so we'll be
  adding it during the execution of the acl-import pipeline.

Adding component tests to shippable

Updated the XDUserTest namespace

- simple c/p mistake Updated so it reflects the current namespace.

Testing shippable changes to accommodate component tests

Add acl-import to be run during a fresh_install

Resolve Initial Acls not being set

- instead of directly assigning the incoming $role_set to _roles we instead
  utilize the function setRoles which will take care of keeping the acls in
  sync.
- remove the initialization of _acls to an empty array() as this would override
  the work done by setRoles.

- Also needed to remove the additional acl-import in
  integration_tests/scripts/bootstrap.sh as we need to see if the changes made
  to XDUser work as expected.

Adding some jobviewer tests to exercise the Acl code

- Added some integration tests to exercise the acl code via the single job
  viewer - job search end point.

Updating Usr Tests

- Missed the c/p from pi -> usr.

Removing JobViewerTests, these belong in the supremm module

Resolving spacing syntax issue

restoring portal_settings.ini from ini.old

- Adding a line per discussion with @jpwhite4 to restore the original
  portal_settings.ini ( from portal_settings.ini.old ).
  • Loading branch information
ryanrath authored and chakrabortyr committed Oct 16, 2017
1 parent 498cdbe commit d1a0f21
Show file tree
Hide file tree
Showing 14 changed files with 4,148 additions and 1,938 deletions.
156 changes: 156 additions & 0 deletions classes/Models/Acl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<?php namespace Models;

use CCR\DB;

/**
* Class Acl
*
* Represents a named grouping under which a selection of 'Assets' can be
* secured and to which a number of users can be belong. Data for this class
* is stored in the 'acls' table while the relationship of user to acl is stored
* in 'user_acls'.
*
* @package User
*
* The 'getters' and 'setters' for this class:
* @method integer getAclId()
* @method void setAclId($aclId)
* @method integer getModuleId()
* @method void setModuleId($moduleId)
* @method integer getAclTypeId()
* @method void setAclTypeId($aclTypeId)
* @method string getName()
* @method void setName($name)
* @method string getDisplay()
* @method void setDisplay($display)
* @method boolean getEnabled()
* @method void setEnabled($enabled)
* @method integer getUserId()
* @method void setUserId($userId)
*
*/
class Acl extends DBObject
{
protected $PROP_MAP = array(
'acl_id' => 'aclId',
'module_id' => 'moduleId',
'acl_type_id' => 'aclTypeId',
'name' => 'name',
'display' => 'display',
'enabled' => 'enabled',

// Needed for getParameters
'user_id' => 'userId'
);

/**
* @return array|null
* @throws \Exception
*/
public function getParameters()
{
$userId = $this->getUserId();
if (!isset($userId)) {
throw new \Exception('Acl has no user_id. Cannot retrieve parameters');
}
$aclId = $this->getAclId();
if (!isset($aclId)) {
throw new \Exception('Acl has no acl_id. Cannot retrieve parameters');
}

$db = DB::factory('database');

$query =<<< SQL
SELECT DISTINCT
uagbp.user_id,
uagbp.acl_id,
gb.name,
'=',
uagbp.value
FROM user_acl_group_by_parameters uagbp
JOIN user_acls ua
ON uagbp.user_id = ua.user_id
AND uagbp.acl_id = ua.acl_id
JOIN group_bys gb
ON gb.group_by_id = uagbp.group_by_id
WHERE
ua.user_id = :user_id
AND ua.acl_id = :acl_id
SQL;
$rows = $db->query($query, array(':user_id' => $userId, ':acl_id' => $aclId));
if (false !== $rows) {
$results = array_reduce($rows, function ($carry, $item) {
$carry[$item['name']] = $item['value'];
return $carry;
}, $rows);
return $results;
}

return null;
}

/**
* Determine if the user who has access to this acl
*
* @param $query_groupname
* @param null $realm_name
* @param null $group_by_name
* @param null $statistic_name
* @return bool
* @throws \Exception
*/
public function hasDataAccess($query_groupname, $realm_name = null, $group_by_name = null, $statistic_name = null)
{
$userId = $this->getUserId();
if (null === $userId) {
throw new \Exception('Acl has no user_id. Cannot check data access');
}

$params = array(
':user_id' => $userId
);

$hasRealm = isset($realm_name);
$hasGroupBy = isset($group_by_name);
$hasStatistic = isset($statistic_name);

$query =<<<SQL
SELECT
agb.*,
r.realm_id,
r.name AS realm,
gb.group_by_id,
gb.name AS group_by
FROM acl_group_bys agb
JOIN user_acls ua
ON agb.acl_id = ua.acl_id
LEFT JOIN realms r
ON agb.realm_id = r.realm_id
LEFT JOIN group_bys gb
ON agb.group_by_id = gb.group_by_id
LEFT JOIN statistics s
ON agb.statistic_id = s.statistic_id
WHERE
ua.user_id = :user_id
AND agb.visible = TRUE
AND agb.enabled = TRUE
SQL;
if (true === $hasRealm) {
$query.= " AND r.name = LOWER(:realm_name)\n";
$params[':realm_name'] = $realm_name;
}
if (true === $hasGroupBy) {
$query .= " AND gb.name = :group_by_name\n";
$params[':group_by_name'] = $group_by_name;
}
if (true === $hasStatistic) {
$query .= " AND s.name = :statistic_name\n";
$params[':statistic_hame'] = $statistic_name;
}

$db = DB::factory('database');
$rows = $db->query($query, $params);

return $rows !== false && count($rows) > 0;
}
}
73 changes: 73 additions & 0 deletions classes/Models/DBObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php namespace Models;

/**
* Class DBObject
*
* The intent of this class is to provide an easy way for child classes, which
* are meant to represent the data contained within one row of a table,
* an easy way of interacting with a PDO result set in which the rows have been
* returned as arrays. In particular, this allows the knowledge of what is
* expected / contained in these tables / classes to be defined at particular
* point in time (i.e. git commit ) as opposed to spread throughout the code
* utilizing these objects. It also allows the utilizing code to interact with
* the class and its associated properties / functions as opposed to a simple
* array.
*
* On a more technical note, it provides dynamic 'getter' and 'setter'
* support for calls that follow the form 'getCamelCasePropertyName()' and
* 'setCamelCasePropertyName($propertyName)' the property name is assumed to be
* in the form: lcfirst(CamelCase(column_name)) => columnName.
*
* And for those who enjoy working with their classes in an array type manner.
* ArrayAccess has been implemented such that 'offsetGet' corresponds to
* 'getCamelCasePropertyName()', 'offsetSet' corresponds to
* 'setCamelCasePropertyName($propertyName)' and 'offsetExists($offset)'
* ensures that the '$offset' is defined in the $PROP_MAP and that there
* is a property currently defined with a name that that matches '$offset';
*
* @author Ryan Rathsam <ryanrath@buffalo.edu>
*/
class DBObject
{

protected $PROP_MAP = array();

/**
* Default Constructor
*
* @param array $options the options used to configure this instance.
**/
public function __construct($options = array())
{
$properties = $this->PROP_MAP;

foreach ($properties as $property => $value) {
if (array_key_exists($property, $options)) {
$this->$value = $options[$property];
}
}
}

/**
* @inheritDoc
**/
public function __call($name, $arguments)
{
/* The following block of code dynamically generates 'getters' and
* 'setters' based on the properties the class currently supports.
* This frees child classes from needing to clutter their class space
* with boiler plate functions.
*/

$var = lcfirst(substr($name, 3));
if ((strncasecmp($name, 'get', 3) === 0) &&
property_exists($this, $var)
) {
return $this->$var;
} elseif ((strncasecmp($name, 'set', 3) === 0) &&
(property_exists($this, $var))
) {
$this->$var = $arguments[0];
}
}
}
57 changes: 57 additions & 0 deletions classes/Models/GroupBy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php namespace Models;

/**
* Class GroupBy
*
* @method integer getGroupById()
* @method void setGroupById($groupById)
* @method integer getModuleId()
* @method void setModuleId($moduleId)
* @method integer getRealmId()
* @method void setRealmId($realmId)
* @method string getName()
* @method void setName($name)
* @method string getDisplay()
* @method void setDisplay($display)
* @method string getDescription()
* @method void setDescription($description)
* @method string getSchemaName()
* @method void setSchemaName($schemaName)
* @method string getTableName()
* @method void setTableName($tableName)
* @method string getAlias()
* @method void setAlias($alias)
* @method string getIdColumn()
* @method void setIdColumn($idColumn)
* @method string getNameColumn()
* @method void setNameColumn($nameColumn)
* @method string getShortnameColumn()
* @method void setShortnameColumn($shortnameColumn)
* @method string getOrderIdColumn()
* @method void setOrderIdColumn($orderIdColumn)
* @method string getFkColumn()
* @method void setFkColumn($fkColumn)
* @method string getClazz()
* @method void setClazz($clazz)
*/
class GroupBy extends DBObject
{

protected $PROP_MAP = array(
'group_by_id'=> 'groupById',
'module_id' => 'moduleId',
'realm_id' => 'realmId',
'name' => 'name',
'display' => 'display',
'description' => 'description',
'schema_name'=> 'schemaName',
'table_name' => 'tableName',
'alias'=> 'alias',
'id_column' => 'idColumn',
'name_column' => 'nameColumn',
'shortname_column' => 'shortnameColumn',
'order_id_column' => 'orderIdColumn',
'fk_column' => 'fkColumn',
'class' => 'clazz'
);
}
30 changes: 30 additions & 0 deletions classes/Models/Realm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php namespace Models;

/**
* Class Realm
*
* the 'getters' and 'setters' for this class:
* @method integer getRealmId()
* @method void setRealmId($realmId)
* @method integer getModuleId()
* @method void setModuleId($moduleId)
* @method string getName()
* @method void setName($name)
* @method string getDisplay()
* @method void setDisplay($display)
* @method string getTableName()
* @method void setTableName($tableName)
* @method string getSchemaName()
* @method void setSchemaName($schemaName)
*/
class Realm extends DBObject
{
protected $PROP_MAP = array(
'realm_id'=> 'realmId',
'module_id' => 'moduleId',
'name' => 'name',
'display'=> 'display',
'schema_name' => 'schemaName',
'table_name' => 'tableName'
);
}
Loading

0 comments on commit d1a0f21

Please sign in to comment.