Currently I have a Client login area. In this login area I was going to create a snippet that shows certain information for 'Admin' users.
I had a feeling I could use
<?php
if ($modx->getLoginUserType()) {
echo 'hi';
} else {}
?>
However, all User Types are web.
I was also looking around and found a script that prints out web-users of a web-group:
http://modxcms.com/forums/index.php/topic,43139.0.html
Any Advice?
$test = $modx->isMemberOfWebGroup( array(
'Editors'
));
if ($test === true) {
// Is a member of the group
} else {
// Is not a member of the group
}
see: http://wiki.modxcms.com/index.php/API:isMemberOfWebGroup
Related
CakePHP Version 3.5.5
The id is visible in the address bar for view and edit which for my application creates a security risk. Any logged in user at the same company can change the id in the address bar and view or edit the details
of users they are not allowed to.
IE: https://localhost/crm/users/edit/1378 can be manually changed in the address bar to https://localhost/crm/users/edit/1215 and entered. This would display the details of user 1215 which is not allowed.
To overcome this I am selecting the ids which the user is allowed to edit and checking that the id from the url is one of these ids with the following code:
public function view($id = null)
{
if ($this->request->is('get')) {
// Select the permitted ids.
if (superuser) { // example to explain only
$query = $this->Users->find()
->where(['companyid' => $cid])
->andWhere(['status' => 1])
->toArray();
}
elseif (manager) { // example to explain only
$query = $this->Users->find()
->where(['areaid' => $areaid])
->andWhere(['status' => 1])
->toArray();
}
elseif (team leader) { // example to explain only
$query = $this->Users->find()
->where(['teamid' => $teamid])
->andWhere(['status' => 1])
->toArray();
}
// Check if the edit id is in the array of permitted ids.
$ids = array_column($query, 'id');
$foundKey = array_search($id, $ids);
// If the edit id is not in the array of permitted ids redirect to blank.
if (empty($foundKey)) {
// Handle error.
}
$user = $this->Users->get($id);
$this->set('user', $user);
$this->set('_serialize', ['user']);
}
else {
// Handle error.
}
}
My question: Is the above code the best cake way of achieving this or is there a better way to do it?
This code does work but because it's to do with security I'd appreciate any input which would improve it or point out it's weakness/es.
/////////////////////////////////////////////////////////////////////////////
As requested by cgTag please see below.
My app has superusers, managers, team leaders and users.
Managers manage one area which can contain many teams.
Team Leaders lead one team and must belong to an area.
Users are assigned to an area or a team.
For example:
Area is UK
Team is England
Team is Scotland
Team is Wales
Area is USA
Team is Florida
Team is California
Team is Texas
On index - superusers see all the superusers, managers, team leaders and users in the company.
On index - managers see themself and users in their area, team leaders in their area and users in the teams.
On index - team leaders see themself and users in their team
My problem is say the manager of area UK clicks edit on one of the records and that record is displayed with a url of https://localhost/crm/users/edit/1378
Then say this disgruntled manager makes a guess and changes the url to https://localhost/crm/users/edit/1215 and submits it then this record is displayed. (This record could be anyone, a superuser, another manager, a team leader who is not in their area or a user not in their area.
This manager could then change say the email address and submit this and it's this type of situation that I need to protect against.
My fix is to reiterate the find for the superuser, manager and team leader I've done on index in the view and edit class. This ensures that say a manager can only view or edit someone in their area.
Hopefully I've explained it well enough but if not just let me know and I'll have another go.
Thanks. Z.
/////////////////////////////////////////////////////////////////////////////
Thanks cgTag, I feel a lot more confident with this approach but I cannot use this code because you have correctly assumed that I am using an id to select all the companies results but I'm using a 40 char string. I do this so I can make my sql queries more robust.
It's impossible for you to help me unless you have all the info required so I have posted an accurate representation below:
public function view($id = null)
{
if(!$this->request->is('get') || !$id) {
//throw new ForbiddenException();
echo 'in request is NOT get or id NOT set ' . '<hr />';
}
$user_id = $this->Auth->user('id');
// regular users can never view other users.
if($user_id !== $id) {
//throw new ForbiddenException();
echo 'in $user_id !== $id ' . '<hr />';
}
// Declare client id 1.
if ($this->cid1() === false) {
echo 'in throw exception ' . '<hr />';
}
else {
$c1 = null;
$c1 = $this->cid1();
}
$company_ids = $this->getCompanyIds($c1);
$area_ids = $this->getAreaIds($user_id, $c1);
$team_ids = $this->getTeamIds($user_id, $c1);
// company_id does not exist which will cause an unknown column error.
// The column I select by is cid_1 so I have changed this column to cid_1 as shown below.
$user = $this->Users->find()
->where([
'id' => $id,
'cid_1 IN' => $company_ids,
'area_id IN' => $area_ids,
'team_id IN' => $team_ids,
'status' => 1
])
->firstOrFail();
$this->set(compact('user'));
}
The functions:
public function cid1()
{
$session = $this->request->session();
if ($session->check('Cid.one')) {
$c1 = null;
$c1 = $session->read('Cid.one');
if (!is_string($c1) || is_numeric($c1) || (strlen($c1) !== 40)) {
return false;
}
return $c1;
}
return false;
}
public function getCompanyIds($c1 = null)
{
$query = $this->Users->find()
->where(['status' => 1])
->andWhere(['cid_1' => $c1]);
return $query;
}
public function getAreaIds($c1 = null, $user_id = null)
{
$query = $this->Users->find()
->where(['status' => 1])
->andWhere(['cid_1' => $c1])
->andWhere(['area_id' => $user_id]);
return $query;
}
public function getTeamIds($c1 = null, $user_id = null)
{
$query = $this->Users->find()
->where(['status' => 1])
->andWhere(['cid_1' => $c1])
->andWhere(['team_id' => $user_id]);
return $query;
}
With this code I get the following error:
Error: SQLSTATE[21000]: Cardinality violation: 1241 Operand should contain 1 column(s)
I don't know if your example will work with this new information but at least you have all the information now.
If it can be ammended great but if not I really don't mind. And I do appreciate the time you've put aside to try to help.
Thanks Z
/////////////////////////////////////////////////////////////////////////////
#tarikul05 - Thanks for the input.
Your suggestion is very similar to my first effort at addressing this security issue but I went for security through obscurity and hid the id in a 80 char string, example below.
// In a cell
public function display($id = null)
{
// Encrypt the id to pass with view and edit links.
$idArray = str_split($id);
foreach($idArray as $arrkey => $arrVal) {
$id0 = "$idArray[0]";
$id1 = "$idArray[1]";
$id2 = "$idArray[2]";
$id3 = "$idArray[3]";
}
// Generate string for the id to be obscured in.
$enc1 = null;
$enc1 = sha1(uniqid(mt_rand(), true));
$enc2 = null;
$enc2 = sha1(uniqid(mt_rand(), true));
$encIdStr = $enc1 . $enc2;
// Split the string.
$encIdArray = null;
$encIdArray = str_split($encIdStr);
// Generate the coded sequence.
$codedSequence = null;
$codedSequence = array(9 => "$id0", 23 => "$id1", 54 => "$id2", 76 => "$id3");
// Replace the id in the random string.
$idTemp = null;
$idTemp = array_replace($encIdArray, $codedSequence);
// Implode the array.
$encryptedId = null;
$encryptedId = implode("",$idTemp);
// Send the encrypted id to the view.
$this->set('encryptedId', $encryptedId);
}
And then decrypted with
// In function in the app controller
public function decryptTheId($encryptedId = null)
{
$idArray = str_split($encryptedId);
foreach($idArray as $arrkey => $arrVal) {
$id0 = "$idArray[9]";
$id1 = "$idArray[23]";
$id2 = "$idArray[54]";
$id3 = "$idArray[76]";
}
$id = null;
$id = $id0.$id1.$id2.$id3;
return $id;
}
The problem with this was that when testing I managed to get the script to error which revealed the array positions which would of undermined the security by obscurity principle and made it a lot easier for a hacker.
Your suggestion is neater than my obscurity method but I believe md5 has been cracked therefore it should not be used.
I'm no security expert but in my opinion checking the view and edit id against an array of permitted ids is the most secure way to address this.
Maybe I'm wrong but if I do it this way there's is no way a hacker no matter what they try in the address bar can see or edit data they are not meant to and it keeps the url cleaner.
What I was originally looking/hoping for was a Cake method/function which addressed this but I couldn't find anything in the cookbook.
Thanks anyway. Z.
I would simplify your code so that the SQL that fetches the user record only finds that record if the current user has permissions. When you're dependent upon associated data for those conditions. Follow this approach even if you have to use joins.
You create the SQL conditions and then call firstOrFail() on the query. This throws a NotFoundException if there is no match for the record.
public function view($id = null) {
if(!$this->request->is('get') || !$id) {
throw new ForbiddenException();
}
$user_id = $this->Auth->user('id');
// regular users can never view other users.
if($user_id !== $id) {
throw new ForbiddenException();
}
$company_ids = $this->getCompanyIds($user_id);
$area_ids = $this->getAreaIds($user_id);
$team_ids = $this->getTeamIds($user_id);
$user = $this->Users->find()
->where([
'id' => $id
'company_id IN' => $company_ids,
'area_id IN' => $area_ids,
'team_id IN' => $team_ids,
'status' => 1
])
->firstOrFail();
$this->set(compact('user'));
}
The above logic should be sound when a user belongsTo a hierarchical structure of data. Where by, they can view many users but only if those users belong to one of the upper associations they have access too.
It works because of the IN clause of the where conditions.
Note: The IN operator throws an error if the array is empty. When you have users who can see all "teams" just exclude that where condition instead of using an empty array.
The key here is to have functions which return an array of allowed parent associations such as; getCompanyIds($user_id) would return just the company IDs the current user is allowed access too.
I think if you implement it this way then the logic is easy to understand, the security is solid and a simple firstOrFail() prevents access.
I am pissed off with this problem from 2 days.
I am using MODx Revolution 2.2.5 (traditional) and want to login to modx from external server just to fetch some user details.
1) I know that runprocessor method works only if i am logged in to manager (unfortunately, that's the only way i know to login user in) So i tried IFRAME method to avoid (cross scripting) it worked perfectly but i am not able to read the data from IFRAME using javascript because of same issue, cross domain access policy.
When i try to post data using some other method like CURL, Ajax using
header("Access-Control-Allow-Origin: *");
I am able to login (I see $response->response['success'] == 1) but cant access any data and it says
Fatal error: Call to a member function get() on a non-object
Below is the snippet code i am using
if(isset($_POST) && count($_POST)){
$c = array(
'username' => $_POST['username'],
'password' => $_POST['password']
);
$response = $modx->runProcessor('security/login',$c);
if($response->response['success'] == 1){
$user['id'] = $modx->user->get('id');
$profile = $modx->user->getOne('Profile');
$user['fullname'] = $profile->get('fullname');
$user['email'] = $profile->get('email');
echo json_encode($user);
}else{
echo json_encode($response->response);
}
}
2) I can use login snippet but it doesnt return output what i expect. We have ready site and we are already using login plugin so i cant even modify login plugin to respond with expected data
How can i login to modx using api or any other method ??
You are really attacking this problem completely wrong in my opinion. If you want to access a server/webpage from another, you don't iFrame and do it the way you are. That is hacking, and this hole will most likely be fixed in a future version.
What you SHOULD do is connecting to the database and just gather the information from the user-table.
No hacking, no "tricks", won't stop working and much safer.
Well, I sorted out this today, Below is the complete come that worked perfectly.
Pay attention to
header("Access-Control-Allow-Origin: http://www.xyz.com");
Using above CORS specification you can allow 2 servers to communication.
header("Access-Control-Allow-Origin: http://www.xyz.com");
if(isset($_POST['username']) && isset($_POST['password'])){
// get username and password from POST array
$username = $modx->sanitizeString($_POST['username']);
$password = $modx->sanitizeString($_POST['password']);
if(trim($username) != "" and trim($password) != ""){
// Load lexicons to show proper error messages
if (!isset($modx->lexicon) || !is_object($modx->lexicon)) {
$modx->getService('lexicon','modLexicon');
}
$modx->lexicon->load('login');
$loginContext= isset ($scriptProperties['login_context']) ? $scriptProperties['login_context'] :
$modx->context->get('key');
$addContexts= isset ($scriptProperties['add_contexts']) && !empty($scriptProperties['add_contexts']) ? explode(',', $scriptProperties['add_contexts']) : array();
$mgrEvents = ($loginContext == 'mgr');
$givenPassword = $password;
/** #var $user modUser */
$user= $modx->getObjectGraph('modUser', '{"Profile":{},"UserSettings":{}}', array ('modUser.username' => $username));
if (!$user) {
$ru = $modx->invokeEvent("OnUserNotFound", array(
'user' => &$user,
'username' => $username,
'password' => $password,
'attributes' => array(
'loginContext' => $loginContext,
)
));
if (!empty($ru)) {
foreach ($ru as $obj) {
if (is_object($obj) && $obj instanceof modUser) {
$user = $obj;
break;
}
}
}
if (!is_object($user) || !($user instanceof modUser)) {
//echo "cant locate account";
echo $modx->toJSON($modx->error->failure($modx->lexicon('login_cannot_locate_account')));
exit;
}
}
if (!$user->get('active')) {
//echo "inactivated accout";
echo $modx->toJSON($modx->error->failure($modx->lexicon('login_user_inactive')));
exit;
}
if (!$user->passwordMatches($givenPassword)) {
if (!array_key_exists('login_failed', $_SESSION)) {
$_SESSION['login_failed'] = 0;
}
if ($_SESSION['login_failed'] == 0) {
$flc = ((integer) $user->Profile->get('failedlogincount')) + 1;
$user->Profile->set('failedlogincount', $flc);
$user->Profile->save();
$_SESSION['login_failed']++;
} else {
$_SESSION['login_failed'] = 0;
}
//echo "wrong username pass";
echo $modx->toJSON($modx->error->failure($modx->lexicon('login_username_password_incorrect')));
exit;
}
$fullname = $user->Profile->get('fullname');
echo '{"success":true,"message":"Welcome '.$fullname.'!"}';
}else{
echo '{"success":false,"message":"Please enter username and password"}';
}
}
I want to mark checkboxes if they have been selected via existing data from db or in the postdata. I have an array of all roles, $roles, and $user_roles contains the current roles.
foreach ($roles as $r) {
$checked = false;
if(isset($postdata['roles'][$r->id])){
$checked = true;
}
else{
foreach($user_roles as $ur){
if($ur->id == $r->id){
$checked = true;
}
}
}
<input type="checkbox" name="roles[<?php echo $r->id; ?>]" <?php if($checked){ ?>checked="checked"<?php } ?> value="<?php echo $r->id; ?>" />
The code is working but I wondered if I could tidy it up. I am using Kohana 3.2
$role_ids = $user_roles->as_array(NULL, 'id');
$checked = in_array($r->id, $role_ids) or Arr::path($postdata,"roles.$r->id");
echo Form::checkbox('roles['.$r->id.']', $r->id, $checked);
Assuming you are trying to update an existing user in the db...
foreach($roles as $role){
echo
Form::checkbox('roles[]', $role->id, in_array($role, $user_roles), array('id' => 'role-'.$role->id)),
Form::label('role-'.$role->id, $role->name);
}
The $user_roles variable is either an array of user roles from the db, using $user->roles->find_all(), or user roles that have been updated via POST data. If POST data exists, then I update the user roles:
$roles = $this->request->post('roles');
foreach(ORM::factory('role')->find_all() as $role)
{
if (in_array($role->id, $roles))
{
// Add roles relationship
$user->add('roles', new Model_Role(array('id' => $role->id)));
}
else
{
// Remove roles relationship
$user->remove('roles', new Model_Role(array('id' => $role->id)));
}
}
And then I still use $user->roles->find_all() for the user roles displayed in the view.
Doing it this way means I don't have to decide what to display in the view, (POST or DB data), as that conditional exists in the model or controller, and the user roles are always up to date.
In the last days, I'm working on the application which needs to search for users on Facebook. Since the FQL query for "username" was deprecated/canceled, I have decided to use common search API.
I use PHP so FB PHP SDK is the way I'd prefer. I have used it earlier for FQL queries, just like this:
// $api is already initialized, with access_key, app secret and so on
$users = $api(array(
'method' => 'fql.query',
'query' => "SELECT first_name,last_name FROM user WHERE uid='12345'",
));
I'd like to build the search query in the similar way. Especially, I don't want to urlencode the parameters, specify access key, app secret and all the stuff the SDK is supposed to do for me. However, I haven't been able to build this query using SDK yet. Is there any possibility to do it? If yes, how? I have found long list of sdk-supported "api calls" but I need to build the query for graph.facebook.com/search?arguments.
Thanks in advance.
EDIT: To make it clear, I don't want to build the string by myself. I know this solution works. But imho it's ugly when I have SDK:
$name = urlencode(trim($first_name . " " . $last_name_));
$users = $this->facebook->api("/search?q=$name&type=user&access_token=$key");
Searching User via Graph API using php-sdk 3.1.1
User will need to authorize your app before making a search for
users.
{
"error": {
"message": "A user access token is required to request this resource.",
"type": "OAuthException"
}
}
Php-skd 3.1.1 init.
<?php
require './src/facebook.php';
$facebook = new Facebook(array(
'appId' => 'your-app-id',
'secret' => 'your-app-secret',
));
$user = $facebook->getUser();
if ($user) {
try {
// Proceed knowing you have a logged in user who's authenticated.
$user_profile = $facebook->api('/me');
} catch (FacebookApiException $e) {
error_log($e);
$user = null;
}
}
/* */
if ($user) {
$logoutUrl = $facebook->getLogoutUrl();
} else {
$loginUrl = $facebook->getLoginUrl();
}
?>
Search includes, encoding search term, setting results limit, and
offset for paging.
<?php
/* Get Search parameter from url or post and urlencode it. */
$q = urlencode($_GET['qs']);
if(!$_GET['qs']){
$q = urlencode($_POST['qs']);
if(!$_POST['qs']){
/* Default Search Term */
$q = "Shawn+E+Carter";
}
}
/* Get Results Limit from url or set default. */
$limit = $_GET['limit'];
if (!$_GET['limit']){
$limit = 60;
}
/* Get Offset from url or set default for paging. */
$offset = $_GET['offset'];
if (!$_GET['offset']){
$offset = 0;
}
/* Make Graph API call to user */
$usersearch = 'search%3Fq='.$q.'%26type=user%26limit='.$limit.'%26offset='.$offset.'';
echo '<pre style="text-align: left;">';
print_r($usersearch);
echo '</pre>';
?>
Before posting comments a message is shown:
Login or register to post comments
I want to modify the output of the 2 links "login" and "register", mainly I want to add some classes to the links to format it nicely with some img. buttons of different colors
I really need to "tell" the output to put and , and by default doesn't have no class....
I know it can be done with some hook or something but I can't find no info about that ...
The piece responsible for that line is locate in theme_comment_post_forbidden in drupal_root/modules/comment/comment.module
it looks like this
function theme_comment_post_forbidden($node) {
global $user;
static $authenticated_post_comments;
if (!$user->uid) {
if (!isset($authenticated_post_comments)) {
// We only output any link if we are certain, that users get permission
// to post comments by logging in. We also locally cache this information.
$authenticated_post_comments = array_key_exists(DRUPAL_AUTHENTICATED_RID, user_roles(TRUE, 'post comments') + user_roles(TRUE, 'post comments without approval'));
}
if ($authenticated_post_comments) {
// We cannot use drupal_get_destination() because these links
// sometimes appear on /node and taxonomy listing pages.
if (variable_get('comment_form_location_'. $node->type, COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_SEPARATE_PAGE) {
$destination = 'destination='. rawurlencode("comment/reply/$node->nid#comment-form");
}
else {
$destination = 'destination='. rawurlencode("node/$node->nid#comment-form");
}
if (variable_get('user_register', 1)) {
// Users can register themselves.
return t('Login or register to post comments', array('#login' => url('user/login', array('query' => $destination)), '#register' => url('user/register', array('query' => $destination))));
}
else {
// Only admins can add new users, no public registration.
return t('Login to post comments', array('#login' => url('user/login', array('query' => $destination))));
}
}
}
}
I suggest modifying your theme's template.php and adding a new function phptemplate_comment_post_forbidden($node) where you'll copy the contents of theme_comment_post_comments and do the necessary modifications.