1: <?php
2:
3: /*****************************************************************************************
4: * X2Engine Open Source Edition is a customer relationship management program developed by
5: * X2Engine, Inc. Copyright (C) 2011-2016 X2Engine Inc.
6: *
7: * This program is free software; you can redistribute it and/or modify it under
8: * the terms of the GNU Affero General Public License version 3 as published by the
9: * Free Software Foundation with the addition of the following permission added
10: * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11: * IN WHICH THE COPYRIGHT IS OWNED BY X2ENGINE, X2ENGINE DISCLAIMS THE WARRANTY
12: * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13: *
14: * This program is distributed in the hope that it will be useful, but WITHOUT
15: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16: * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
17: * details.
18: *
19: * You should have received a copy of the GNU Affero General Public License along with
20: * this program; if not, see http://www.gnu.org/licenses or write to the Free
21: * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22: * 02110-1301 USA.
23: *
24: * You can contact X2Engine, Inc. P.O. Box 66752, Scotts Valley,
25: * California 95067, USA. or at email address contact@x2engine.com.
26: *
27: * The interactive user interfaces in modified source and object code versions
28: * of this program must display Appropriate Legal Notices, as required under
29: * Section 5 of the GNU Affero General Public License version 3.
30: *
31: * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32: * these Appropriate Legal Notices must retain the display of the "Powered by
33: * X2Engine" logo. If the display of the logo is not reasonably feasible for
34: * technical reasons, the Appropriate Legal Notices must display the words
35: * "Powered by X2Engine".
36: *****************************************************************************************/
37:
38: /**
39: * RBAC auth manager for X2Engine
40: *
41: *
42: *
43: * @package application.components
44: * @author Demitri Morgan <demitri@x2engine.com>
45: */
46: class X2AuthManager extends CDbAuthManager {
47:
48: public $caching = true;
49:
50: /**
51: * Stores auth data in the scope of the current request
52: *
53: * @var type
54: */
55: private $_access;
56:
57: /**
58: * Internal "cache" of user names
59: * @var type
60: */
61: private $_assignments = array();
62: protected $_usernames = array();
63:
64: /**
65: * Access check function.
66: *
67: * Checks access and attempts to speed up all future access checks using
68: * caching and storage of the variable within {@link _access}.
69: *
70: * Note, only if parameters are empty will permissions caching or storage
71: * in {@link _access} be effective, because parameters (i.e. the assignment
72: * of a record based on the value of its assignedTo field) are expected to
73: * vary. For example, in record-specific permission items checked for
74: * multiple records. That is why $params be empty for any shortcuts to be
75: * taken.
76: *
77: * @param string $itemName Name of the auth item for which access is being checked
78: * @param integer $userId ID of the user for which to check access
79: * @param array $params Parameters to pass to business rules
80: * @return boolean
81: */
82: public function checkAccess($itemName, $userId, $params = array()) {
83: if (!isset($params['userId']))
84: $params['userId'] = $userId;
85: if (!isset($this->_access))
86: $this->_access = array();
87:
88: if (isset($this->_access[$userId][$itemName]) &&
89: !empty($this->_access[$userId][$itemName])) {
90:
91: $checkParams = $this->getCacheParams($params);
92: if ($checkParams !== false) {
93: $checkParams = json_encode ($checkParams);
94:
95: // Shortcut 1: return data stored in the component's property
96: if (isset ($this->_access[$userId][$itemName][$checkParams])) {
97: return $this->_access[$userId][$itemName][$checkParams];
98: }
99: }
100: } else if ($this->caching) {
101:
102: // Shortcut 2: load the auth cache data and return if a result was found
103: if (!isset($this->_access[$userId])) {
104: $this->_access[$userId] = Yii::app()->authCache->loadAuthCache($userId);
105: }
106: if (isset($this->_access[$userId][$itemName]) &&
107: !empty($this->_access[$userId][$itemName])) {
108:
109: $checkParams = $this->getCacheParams($params);
110:
111: if ($checkParams !== false) {
112: $checkParams = json_encode ($checkParams);
113:
114: if (isset ($this->_access[$userId][$itemName][$checkParams])) {
115: return $this->_access[$userId][$itemName][$checkParams];
116: }
117: }
118: }
119: }
120:
121: if (!isset($this->_access[$userId]))
122: $this->_access[$userId] = array();
123: if (!isset($this->_access[$userId][$itemName]))
124: $this->_access[$userId][$itemName] = array();
125:
126: // Get assignments via roles.
127: //
128: // In X2Engine's system, x2_auth_assignment doesn't refer to users, but
129: // to roles. Hence, the ID of each role is sent to
130: // parent::getAuthAssignments rather than a user ID, which would be
131: // meaningless in light of how x2_auth_assignment stores roles.
132: if (isset($this->_assignments[$userId])) {
133: $assignments = $this->_assignments[$userId];
134: } else {
135: $roles = Roles::getUserRoles($userId);
136: $assignments = array();
137: foreach ($roles as $roleId) {
138: $assignments = array_merge($assignments, parent::getAuthAssignments($roleId));
139: }
140: $this->_assignments[$userId] = $assignments;
141: }
142:
143: // Prepare the username for the session-agnostic permissions check:
144: if (!isset($this->_usernames[$userId])) {
145: if ($userId == Yii::app()->getSuId())
146: $user = Yii::app()->getSuModel();
147: else
148: $user = User::model()->findByPk($userId);
149: if ($user instanceof User)
150: $this->_usernames[$userId] = $user->username;
151: else
152: $this->_usernames[$userId] = 'Guest';
153: }
154:
155:
156: // Get whether the user has access:
157: $hasAccess = parent::checkAccessRecursive($itemName, $userId, $params, $assignments);
158:
159: // Store locally.
160: $cacheParams = $this->getCacheParams($params);
161: if ($cacheParams !== false) {
162: $this->_access[$userId][$itemName][json_encode ($cacheParams)] = $hasAccess;
163:
164: // Cache
165: if ($this->caching) {
166: Yii::app()->authCache->addResult($userId, $itemName, $hasAccess, $cacheParams);
167: }
168: }
169:
170: return $hasAccess;
171: }
172:
173: protected function getCacheParams(array $params) {
174: $ret = false;
175: unset($params['userId']);
176: if ($params == array ()) {
177: return array ();
178: } elseif (isset($params['X2Model']) && count ($params) === 1 &&
179: $params['X2Model']->asa('permissions') != null) {
180:
181: $ret = array ();
182: $ret['modelType'] = get_class($params['X2Model']);
183: $assignmentAttr = $params['X2Model']->getAssignmentAttr();
184: if($assignmentAttr){
185: $ret[$assignmentAttr] = $params['X2Model']->$assignmentAttr;
186: }
187: } else {
188: $simpleParamFlag = true;
189: foreach ($params as $param) {
190: if (!is_scalar ($param)) {
191: $simpleParamFlag = false;
192: break;
193: }
194: }
195: if ($simpleParamFlag) {
196: $ret = $params;
197: }
198: }
199: return $ret;
200: }
201:
202: /**
203: * Checks for admin access on a specific named module.
204: *
205: * Originally written as a kludge to bypass checking for overall admin access when
206: * performing a generic admin action that is specific to a module. Specifically, it
207: * was written for exporting models as a fix for 4.1.6, wherein otherwise a user would
208: * need full admin rights and not just contact module admin rights to export contacts.
209: *
210: * Note, since this starts its own chain of recursive access checking, extreme caution
211: * should be used when using this method inside of a business rule, because infinite
212: * loops could potentially occur.
213: *
214: * @param array $params An associative array that is presumed to contain a "userId"
215: * element that refers to the user ID (as if $params is as within a business rule),
216: * and also expects a model (or module) parameter.
217: */
218: public function checkAdminOn($params) {
219: if (!isset($params['userId']))
220: return false;
221:
222: // Look in the $_GET superglobal for 'model' if the 'model' parameter is not available
223: $modelName = isset($params['model']) ? ($params['model'] instanceof X2Model ? get_class($params['model']) : $params['model']) : (isset($_GET['model']) ? $_GET['model'] : null);
224:
225: // Determine the module on which admin access will be checked, based on a model class:
226: if (empty($params['module']) && !empty($modelName)) {
227: if (($staticModel = X2Model::model($modelName)) instanceof X2Model) {
228: if (($lb = $staticModel->asa('X2LinkableBehavior')) instanceof X2LinkableBehavior) {
229: $module = !empty($lb->module) ? $lb->module : null;
230: }
231: }
232: }
233: if (!isset($module)) // Check if module parameter is specified and use it if so:
234: $module = isset($params['module']) ? $params['module'] : null;
235:
236: if (!empty($module)) {
237: // Perform a check for the existence of the item name (because, per the original
238: // design of X2Engine's permissions, for backwards compatibility: if no auth
239: // item exists, permission will be granted by default).
240: $itemName = ucfirst($module) . 'AdminAccess';
241: if (!(bool) $this->getAuthItem($itemName))
242: return false;
243: } else {
244: // Use the generic administrator auth item if there is no module specified:
245: $itemName = 'administrator';
246: }
247: //AuxLib::debugLogR(compact('params','itemName','userId','module','modelName'));
248: return $this->checkAccess($itemName, $params['userId'], $params);
249: }
250:
251: /**
252: * Assignment check function for business rules. Note that this method does not check for
253: * assignment to "Anyone". At the time of this writing, checkAssignment is used exclusively
254: * for checking permissions related to private access.
255: * @param array $params
256: * @return boolean
257: */
258: public function checkAssignment($params) {
259: return isset($params['X2Model']) && $params['X2Model']->asa('permissions') && $params['X2Model']->isAssignedTo($this->_usernames[$params['userId']], true);
260: }
261:
262: /**
263: * Visibility check function for business rules
264: *
265: * @param array $params
266: * @return boolean
267: */
268: public function checkVisibility($params) {
269: return isset($params['X2Model']) && $params['X2Model']->asa('permissions') && $params['X2Model']->isVisibleTo($this->_usernames[$params['userId']]);
270: }
271:
272: /**
273: * This method is Copyright (c) 2008-2014 by Yii Software LLC
274: * http://www.yiiframework.com/license/
275: */
276: // protected function checkAccessRecursive($itemName,$userId,$params,$assignments)
277: // {
278: // if(($item=$this->getAuthItem($itemName))===null)
279: // return false;
280: // Yii::trace('Checking permission "'.$item->getName().'"','system.web.auth.CDbAuthManager');
281: // if(!isset($params['userId']))
282: // $params['userId'] = $userId;
283: // if($this->executeBizRule($item->getBizRule(),$params,$item->getData()))
284: // {
285: // if(in_array($itemName,$this->defaultRoles))
286: // return true;
287: // if(isset($assignments[$itemName]))
288: // {
289: // $assignment=$assignments[$itemName];
290: // if($this->executeBizRule($assignment->getBizRule(),$params,$assignment->getData()))
291: // return true;
292: // }
293: // $parents=$this->db->createCommand()
294: // ->select('parent')
295: // ->from($this->itemChildTable)
296: // ->where('child=:name', array(':name'=>$itemName))
297: // ->queryColumn();
298: // foreach($parents as $parent)
299: // {
300: // if($this->checkAccessRecursive($parent,$userId,$params,$assignments))
301: // return true;
302: // }
303: // }
304: // return false;
305: // }
306: }
307:
308: ?>
309: