I am currently in the process of developing a fairly large and complex user management system using sfDoctrineGuard
I have created 4 groups, editors, moderators, admins and superadmins.
What I'm looking to do, is restrict certain users in the admin to be able to create/view/edit other users in the sfGuardUser admin module.
So for example a superadmins user can create editors, moderators, admins and other superadmins, but a moderator can only create editors.
Is this possible in sfDoctrineGuard, if so, could someone give me an insight on how I'd achieve this?
Thanks
First of all you can set credentials in generator.yml to show/hide links to actions and object actions based on credentials. For example:
config:
list:
object_actions:
_delete:
confirm: Вы уверены, что хотите удалить пользователя?
credentials: superuser
actions:
_new:
credentails: moderator
Next, configure your forms with custom table methods for doctrine choice widgets of groups:
class sfGuardUserForm extends PluginsfGuardUserForm
{
public function configure()
{
//groups_list
$this->getWidget('groups_list')->setOption('expanded', true);
$this->getWidget('groups_list')->setOption('table_method', 'getListForAdmin');
$this->getValidator('groups_list')->setOption('query', Doctrine::getTable('sfGuardGroup')->getListForAdmin());
}
}
class sfGuardGroupTable extends PluginsfGuardGroupTable
{
/**
* Builds list query based on credentials
*
*/
public function getListForAdmin()
{
$user = sfContext::getInstance()->getUser();
$q = $this->createQuery('g');
if (!$user->isSuperAdmin() && $user->hasCredential('moderator'))
{
$q->addWhere('g.name IN (?)', array('editor'));
}
else if ($user->hasCredential('editor'))
{
$q->addWhere('g.name IN (?)', array('editor'));
}
return $q;
}
}
A couple of enhancements: get rid of singletone call by passing user instance from action (in preExecute) and load group names form app.yml with sfConfig::get instead of hardcoding in it in code.
Related
Given a system with static permissions like posts.create, category.edit and such, and roles that can be created at runtime, both stored in a database but permissions can't (technically shouldn't) be modified; And the relations are N:M for users to roles and for roles to permissions:
Looking at the ACL package, at first sight it looks like I'd have to build up the ACL graph by querying my database roles on each request and adding them to the ACL instance and the allowed permissions like:
// Some class like AclService.php that should be called in Module.php
// ...
$roles = // query db for all roles and their permissions
foreach ($roles as $role) {
$acl->addRole($role->getName());
foreach ($role->getPermissions() as $permission) {
$acl->allow($role->getName(), null, $permission->getName());
}
}
Up to this point, in my controller's action (or a middleware, if they'd exist) I'd check if the user is allowed to execute the action:
// CategoryController
public function createAction() {
$user = // retrieve user from identity
if (! $acl->isAllowed($user->getRoles()->first(), null, 'categories.create')) {
// throw a 403 exception
}
}
What I don't get quite yet is, where does a Resource fits in this schema? Am I missing something here?
Or maybe are resources fit for when the permissions are not as granular as categories.create but just create?
You are using $acl->addRole($role->getName()); but as per the documentionation it should be defined in resources.
use Zend\Permissions\Acl\Acl;
use Zend\Permissions\Acl\Role\GenericRole as Role;
use Zend\Permissions\Acl\Resource\GenericResource as Resource;
$acl = new Acl();
$acl->addRole(new Role('guest'))
->addRole(new Role('member'))
->addRole(new Role('admin'));
$parents = array('guest', 'member', 'admin');
$acl->addRole(new Role('someUser'), $parents);
$acl->addResource(new Resource('someResource'));
$acl->deny('guest', 'someResource');
$acl->allow('member', 'someResource');
echo $acl->isAllowed('someUser', 'someResource') ? 'allowed' : 'denied';
If this doesn't help then let me know I will try to help.
Does anyone have any suggested strategies for preventing users from editing page names?
I'm developing a site on Umbraco where various partners have their own specific page they can edit exclusively. Access to this page is controlled through the standard Umbraco permissions. However we've found that some of these users have been editing the page title, but we'd like to limit them to only being able to edit the content.
I can't see any obvious way to control this through the built-in permissions.
Perhaps it's possible to plug in some page pre-save code that checks if the user has certain permissions, and if they don't the page name is set to it's pre-edit state?
Any suggestions / pointers are greatly appreciated.
Yes, you can hook up to Umbraco ContentService events and check if Name has changed and do or not do something with this specific node. You can also add some additional checks to determine if user is permitted to change the name (e.g. you can control this by role or anything else whay you need).
Sample code will look like this:
public class UmbracoEvents : ApplicationEventHandler
{
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
ContentService.Saving += ContentService_Saving;
}
private void ContentService_Saving(IContentService sender, Umbraco.Core.Events.SaveEventArgs<Umbraco.Core.Models.IContent> e)
{
foreach (var changedItem in e.SavedEntities)
{
var currentVersion = sender.GetById(changedItem.Id);
if (!currentVersion.Name.InvariantEquals(changedItem.Name))
{
// Additional checks here (or in the above condition) - role / property / etc...
item.Name = currentVersion.Name;
}
}
}
}
You can read more about specific events here too: https://our.umbraco.org/documentation/reference/events/contentservice-events.
I have web application that uses the Authorize attribute with roles specified to restrict access to some pages:
[Authorize(Roles = "AD_group1, AD_group2")]
The question is - is there any way I can get some kind of an Active Directory groupId for authorized user (no matter int or string)?
upd:
Basic idea is to store some table in database, containing templates which should be separate for every group. e.g. users in group1 can have some templates for fast answer to typical questions while group2 doesn't have any of them, or have some other templates
If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, "SomeUserName");
// or if you want the currently logged in user - you can also use:
// UserPrincipal user = UserPrincipal.Current;
if(user != null)
{
// get all groups the user is a member of
foreach(GroupPrincipal group in user.GetAuthorizationGroups())
{
string distinguishedName = group.DistinguishedName;
Guid groupGuid = group.Guid;
}
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!
First i briefly want to know how you can use oauth works. What we need to pass in this plugin and what this plugin will return. Do we have to customize the plugin for different php frameworks. I have seen that their is a different extension of oauth for different framework, why is that?
I need to authenticate the users using social networks in yii framework and I have integrated eouath extension of yii to use oauth and have made an action to use access the ads service of google user like this
public function actionGoogleAds() {
Yii::import('ext.eoauth.*');
$ui = new EOAuthUserIdentity(
array(
//Set the "scope" to the service you want to use
'scope'=>'https://sandbox.google.com/apis/ads/publisher/',
'provider'=>array(
'request'=>'https://www.google.com/accounts/OAuthGetRequestToken',
'authorize'=>'https://www.google.com/accounts/OAuthAuthorizeToken',
'access'=>'https://www.google.com/accounts/OAuthGetAccessToken',
)
)
);
if ($ui->authenticate()) {
$user=Yii::app()->user;
$user->login($ui);
$this->redirect($user->returnUrl);
}
else throw new CHttpException(401, $ui->error);
}
If I want to use other services like linkedin, facebook, twitter just to sign up the user should I just change the scope and parameters or also have to make some changes elsewhere. How do I store user information in my own database?
In simple case you may use the table "identities" with fields "*external_id*" and "provider". Every OAuth provider must give unique user identificator (uqiue only for that provider). To make it unique on your site you may use pair with provider predefined name (constant). And any other additional fields (if a provider gives it).
In the same table you should store identity data of internal authorization, with provider name 'custom' (for ex.). To store password and other data use a separate table, and PK from this table would be your "*external_id*". Universal scheme.
And PHP, something like this:
class UserIdentity extends CUserIdentity
{
protected $extUserID;
public function __construct($extUserID)
{
$this->extUserID = $extUserID;
}
...
public function authenticate()
{
...
//After search $this->extUserID as PK in users table (built-in authorization)
...
$identity = Identities::model()->findByAttributes(array(
'ext_id' => $this->extUserID,
'service' => 'forum',
));
if(!count($identity))
{
$identity = new Identities;
$identity->ext_id = $this->extUserID;
$identity->service = 'forum';
$identity->username = $userData['username'];
$identity->save();
}
$this->setState('id', $identity->id);
...
}
}
Now: resolved - no reproducible anymore
For some specific application security, I have the following createQuery function on a table, ie you can only access the table record if you have the "Administrator" credential or if you are the user that is stored in the MembershipDelegate relation.
class OrganisationTable extends Doctrine_Table
function createQuery($alias = ''){
if (!$alias){
$alias = 'o';
}
$query = parent::createQuery($alias);
try {
$user = sfContext::getInstance()->getUser();
}catch(Exception $e){
if ($e->getMessage() == 'The "default" context does not exist.'){
return $query;
}else{
throw $e;
}
}
if ($user->hasCredential('Administrator')){
//all good
}else{
$userId = $user->getAttribute('userId');
print "<!--testaa ".print_r($user->getCredentials(),1).'-->';
$query->
leftJoin("$alias.MembershipDelegate mdelsec")->
addWhere ("mdelsec.joomla_user_id=$userId");
}
return $query;
}
This seems to work fine at all levels, however there is a choice validator for which the $user object seems to come back empty
/**
* Person form base class.
*
*/
...
abstract class BasePersonForm extends BaseFormDoctrine
{
public function setup()
{
$this->setWidgets(array(
...
'organisation_id' => new sfWidgetFormDoctrineChoice(array('model' => $this->getRelatedModelName('Organisation'), 'add_empty' => true)),
class PersonForm extends BasePersonForm{
public function configure(){
$this->widgetSchema['organisation_id']->setOption('renderer_class', 'sfWidgetFormDoctrineJQueryAutocompleter');
$this->widgetSchema['organisation_id']->setOption('renderer_options', array(
'model' => 'Organisation',
'url' => NZGBCTools::makeUriJoomlaCompatible(
sfContext::getInstance()->getController()->genUrl('organisation/jsonList'))
));
$this->validatorSchema['organisation_id']->setOption('required',false);
is there any other way to get the user object in the model?
This approach to row level security may not be MVC by-the-book, but is IMO safer and superior than implementing the same security concepts in the actions:
It can be used with out of the box admin-generator modules
It is much harder to forget to implement it somewhere
It may at times not require any credentials, only Super Admin access
I'm not aware of another way to get the session user in the models (I don't think there is one), but if you're able to, you ought to pass the user object down to the models.
I think you are missing something here, you have mixed the MVC layers quite a lot. My suggestion is to make the Model independent from the Controller (delegate to the Controller the Context-specific situations), and validation should be done by the Controller also ( there should be an action that receives the request , binds the form and validates it. There is where you have to definy anithing thats context speficif). Here is what i would do:
The OrganisationTable class should have 2 methods: createQueryForAdministrator , and createQueryForMembershipDelegate . Each one does the things it should do, and now you should change the Controller (the action that handles it) and do something like:
public function executeAction(sfWebRequest $request)
{
if ($this->getUser()->hasCredential('administrator'))
{
//call the administrator method
} else {
//call the other method
}
}
The action that instances the Form should also check the user credentials and do something like:
If ($user->hasCredential("administrator"))
{
sfContext::getInstance()->getConfiguration()->loadHelper("Url");
$form->getValidator("organization_id")->setOption("url",url_for("#action"));
[..]
} else {
[..]
}
Check the url helper reference, you can do thinks like loading helpers on actions etc and you could also create your own helpers too. Hope this helps!
EDITED: Check this post about sfContext class and alternatives
It now appears that this question was a fluke as I can't reproduce the problem after fixing other issues around the user authentication.