How to detect Member group change in SilverStripe 4 - silverstripe-4

I have a DataExtension for Member, MyMemberExtension.
I'm trying to detect a user being moved from a pending-users group to a public-users group using onBeforeWrite and onAfterWrite in MyMemberExtension.
However the group change seems to happen before the onBeforeWrite here.
In MyMemberExtension I added an $IsPending class member.
public function onBeforeWrite()
{
if ($this->owner->inGroup('pending-users'))
{
self::$isPending = true;
}
}
and
public function onAfterWrite()
{
if ($this->owner->inGroup('public-users') and self::$isPending)
{
# Moved from the pending to public - send email
}
}
I also tried using $this->owner->getChangedFields(); but Groups isn't part of the array.
How am I able to detect a user changing from pending-users to public-users?

Related

Eclipse Scout callback for entering value in fields

I would like to know if there is a function that fires up when user set value in field but not if program set value in field.
so function :
user click on field 'myField and change value -> method fires up
in program : myField.setValue = SomeValue; -> method doesn't fires up.
problem is with loop detection. If your logic is that you have 4 field and try to detect if any of those fields are changed and then fire method for change some values inside those fields :
#Override
protected void execChangedValue() throws ProcessingException {
super.execChangedValue();
valueFieldsChange(this);
}
protected void valueInPriceBoxFieldsChange(AbstractValueField field) {
... calculate some values in those fields....
}
and I get :
!MESSAGE org.eclipse.scout.rt.client.ui.form.fields.AbstractValueField.setValue(AbstractValueField.java:338) Loop detection in...
So I know the method execChangedValue() are not what I am looking for. Is there similar method with explained behavior ?
Marko
Let start to say that loop detection is useful in 90% of the cases.
The warning is displayed when you are inside an execChangedValue() and you try to update the value of the same field.
If I understand your example correctly you have inside your field:
public class FirstField extends AbstractStringField {
#Override
protected void execChangedValue() throws ProcessingException {
calculateNewValues();
}
}
And the method:
public void calculateNewValues() {
//some logic to compute the new values: value1, value2, value3
getFirstField().setValue(value1);
getSecondField().setValue(value2);
getThirdField().setValue(value3);
}
At this point, you really need to be sure that when the user sets the value in the FirstField to something, you might want to change it pragmatically to something else. This might be really confusing for the user.
If you are sure that you need to update the value, there is a way to set a value without triggering execChangedValue() on the field. I have proposed a new method: setValueWithoutChangedValueTrigger(T value) on the Eclipse Scout Form: Controlling if execChangedValue() is called or not.
On the forum you will find a snippet that you can add to your field (or in a common template: AbstractMyAppStringField).
public class FirstField extends AbstractStringField {
#Override
protected void execChangedValue() throws ProcessingException {
calculateNewValues();
}
public void setValueWithoutValueChangeTrigger(String value) {
try {
setValueChangeTriggerEnabled(false);
setValue(value);
}
finally {
setValueChangeTriggerEnabled(true);
}
}
}
And you will be able to use it:
public void calculateNewValues() {
//some logic to compute the new values: value1, value2, value3
getFirstField().setValueWithoutChangedValueTrigger(value1);
getSecondField().setValueWithoutChangedValueTrigger(value2);
getThirdField().setValueWithoutChangedValueTrigger(value3);
}
I hope this helps.

get users who have a specific role

I need to get the list of all my users having a specific role, is there any way to do it easily? The solution I figured out for now would be to retrive all users then apply a filter on each using is granted function (which is hardcore)
PS: I don't like using the db request that skims over data and if the user role equals the wanted role it returns it, else it doesn't. Which means that we don't take into account users with super roles.
Because of the role hierarchy, I don't see a way to avoid grabbing all the users and then filtering. You could make a user role table and add all possible user roles but that would get out of date if you changed the hierarchy.
However, once you have all the roles for a given user then you can test if a specific one is supported.
There is a role hierarchy object to help.
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Core\Role\RoleHierarchy;
class RoleChecker
{
protected $roleHeirarchy;
public function __construct(RoleHierarchy $roleHierarchy)
{
$this->roleHierarchy = $roleHierarchy; // serviceId = security.role_hierarchy
}
protected function hasRole($roles,$targetRole)
{
$reachableRoles = $this->roleHierarchy->getReachableRoles($roles);
foreach($reachableRoles as $role)
{
if ($role->getRole() == $targetRole) return true;
}
return false;
}
}
# services.yml
# You need to alias the security.role_hierarchy service
cerad_core__role_hierarchy:
alias: security.role_hierarchy
You need to pass an array of role objects to hasRole. This is basically the same code that the security context object uses. I could not find another Symfony service just for this.
The is also a parameter value called '%security.role_hierarchy.roles%' that comes in handy at times as well.
Symfony 5 answer, it's a little bit easier:
namespace App\Controller;
...
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
use Symfony\Component\Security\Core\Role\RoleHierarchy;
class UserController extends AbstractController
{
private $roleHierarchy;
/**
* #Route("/users", name="users")
*/
public function usersIndex(RoleHierarchyInterface $roleHierarchy)
{
$this->roleHierarchy = $roleHierarchy;
// your user service or your Doctrine code here
$users = ...
foreach ($users as $user) {
$roles = $roleHierarchy->getReachableRoleNames($user->getRoles());
\dump($roles);
if ($this->isGranted($user, 'ROLE_SUPER_ADMIN')) {
...
}
}
...
}
private function isGranted(User $user, string $role): bool
{
$reachableRoles = $this->roleHierarchy->getReachableRoleNames($user->getRoles());
foreach ($reachableRoles as $reachableRole) {
if ($reachableRole === $role) {
return true;
}
}
return false;
}
}
Note: I put everything in the controller for the sake of simplicity here, but of course I'd recommend to move the Role Management code into a separate service.

Using object Helper Methods to implement authorization rules

I have the following:-
I am working on an asset management system using Asp.net MVC4 with windows authentication enabled.
The system allow to specify what actions a group of users can do(for example certain group can have the authority to add new physical asset , while they can only read certain logical asset, and so on).
So I found that using the build-in Asp.net role management, will not allow me to have the level of flexibility I want. So I decided to do the following:-
I have created a table named “group” representing the user groups. Where users are stored in active directory.
I have created a table named ”Security Role” which indicate what are the permission levels each group have on each asset type(edit, add, delete or view)per asset type.
Then on each action methods , I will use Helper methods to implement and check if certain users are within the related group that have the required permission ,, something such as
On the Car model object I will create a new helper method
Public bool HaveReadPermison(string userName) {
//check if this user is within a group than have Read permission on CARS, //OR is within a GROUP THAT HAVE HIGHER PERMISON SUCH AS EDIT OR ADD OR //DELETE.
}
Next, On the Action method, I will check if the user has the Read permission or not by calling the action method:-
public ActionResult ViewDetails(int id) { // to view transportation asset type
Car car = repository.GetCar(id);
if (!car.HaveReadPermision(User.Identity.Name)) {
if (car == null)
return View("NotFound");
else
return View(car);
}
else
return view (“Not Authorized”);
So can anyone advice if my approach will be valid or it will cause problem I am unaware about.
Regards
In my opinion, once you have decided to use the ASP membership and role providers you can keep leveraging them also for authorization, simply using the Authorize attribute. This will also allow to restrict access by user names and roles.
What the attribute won't do is Action-based authorization. In that case there are a few options but in my opinion this could be brilliantly resolved by a Custom Action Filter based loosely on the following code:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CheckUserPermissionsAttribute : ActionFilterAttribute
{
public string Model { get; set; }
public string Action { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var user = filterContext.HttpContext.User.Identity.Name; // or get from DB
if (!Can(user, Action, Model)) // implement this method based on your tables and logic
{
filterContext.Result = new HttpUnauthorizedResult("You cannot access this page");
}
base.OnActionExecuting(filterContext);
}
}
Yes, it is vaguely inspired to CanCan, which is a nice Ruby gem for this kind of things.
Returning Unauthorized (401) will also instruct your server to redirect to the login page if one is specified. You may want to work on that logic if you want to redirect somewhere else. In that case you should do:
filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary { { "Controller", "Home" }, { "Action", "Index" } });
and choose the appropriate controller/action pair.
You can use the attribute like this:
[CheckUserPermissions(Action = "edit", Model = "car")]
public ActionResult Edit(int id = 0)
{
//..
}
Let me know if that works nicely for you.
The approach you took looks reasonable, but I would add few changes:
What if you forgot to call HaveReadPermision method? And checking authotization from Actions is not the cleanest solution either, that is not an Action reponsibility.
It is better to keep authorization logic separately. For instance you can create a decorator over you repository which will check the permissions of the current User:
public class AuthorizationDecorator: IRepository
{
public AuthorizationDecorator(IRepository realRepository, IUserProvider userProvider)
{
this.realRepository = realRepository;
this.userProvider = userProvider;
}
public Car GetCar(int id)
{
if(this.UserHaveReadPermission(this.userProvider.GetUserName(), Id))
{
return this.realRepository.GetCar(id);
}
else
{
throw new UserIsNotAuthorizedException();
}
}
private bool UserHaveReadPermission(string username, int id)
{
//do your authorization logic here
}
}
IUserProvider will return curent user name from httpRequest.
After doing the change you don't need to warry about authorization when writing Actions

Grails, injecting/populating domain object with value from session

In my application many classes have common field 'company'. When application saves that objects, they must be filled with company (there is validation for that). Company is also kept in a session. Now, when I want to use domain class as a command object, company must be already filled or I get validation error. Is there any way to always fill company field, before any validation happens, so that I didn't have to do it manually every time.
(I tried custom data binder, but it does not work when there is no parameter in a request)
You could set the property just before the object is saved, updated or validated using the GORM events beforeInsert, beforeUpdate or beforeValidate.
In your domain you need something like that:
import org.springframework.web.context.request.RequestContextHolder
class Foo {
String company
...
def beforeInsert = {
try {
// add some more error checking (i.e. if there is is no request)
def session = RequestContextHolder.currentRequestAttributes().getSession()
if(session) {
this.company = session.company
}
} catch(Exception e) {
log.error e
}
}
}
If you want to bind a property before the process of binding, You can create a custom BindEventListener and register in the grails-app/conf/spring/resources.groovy
First of all, create your custom BindEventListener
/src/groovy/SessionBinderEventListener.groovy
import org.springframework.beans.MutablePropertyValues
import org.springframework.beans.TypeConverter
class SessionBinderEventListener implements BindEVentListener {
void doBind(Object wrapper, MutablePropertyValues mpv, TypeConverter converter) {
def session = RequestContextHolder.currentRequestAttributes().getSession()
mpv.addPropertyValue("company", session.company)
}
}
Second of all, register your BindEventListener
grails-app/conf/spring/resources.groovy
beans = {
sessionBinderEventListener(SessionBinderEventListener)
}
However, if your domain class does not hold a property called company, you will get InvalidPropertyException. To overcome this issue, create a list of classes which contain a property called company - See details bellow
/src/groovy/SessionBinderEventListener.groovy
import org.springframework.beans.MutablePropertyValues
import org.springframework.beans.TypeConverter
class SessionBinderEventListener implements BindEVentListener {
private List<Class> allowedClasses = [Foo]
void doBind(Object wrapper, MutablePropertyValues mpv, TypeConverter converter) {
if(!(allowedClasses.contains(wrapper.class))) {
return
}
def session = RequestContextHolder.currentRequestAttributes().getSession()
mpv.addPropertyValue("company", session.company)
}
}

foreach statement cannot operate on variables of type

here is i am trying to work.
List<MasterEmployee > masterEmployee = new List<MasterEmployee >();
masterEmployee = MasterEmployee.GetAll("123"); //connecting db and returning a list...
foreach (MasterEmployee item in masterEmployee)
{
foreach (Registration reg in item.Registration) //<<<error here...
{
//
}
}
error here:
Error 2 foreach statement cannot operate on variables of type Registration because Registration does not contain a public definition for 'GetEnumerator'
i have a class called MasterEmployee and in it i have a with few props and few methods on it
[Serializable]
public class MasterEmployee
{
//few props omitted ....
protected Registration _registration;
[CopyConstructorIgnore]
public Registration Registration
{
get
{
return _registration;
}
set
{
this._registration = value;
}
}
protected User _user;
[CopyConstructorIgnore]
public User MyUser
{
get
{
return _user;
}
set
{
this._user= value;
}
}
protected Student _student;
[CopyConstructorIgnore]
public Student Student
{
get
{
return _student;
}
set
{
this._student = value;
}
}
}
The explanation provided in the error message is clear enough. You are trying to iterate item.Registration, which is an instance of Registration. However, Registration is not derived from an iterable type, and does not implement the GetEnumerator function required for custom iterable types. So it cannot be iterated using a foreach loop.
But I believe either your naming conventions are incorrect, or you have misunderstood your data model. Why would a Registration instance ever contain a collection of Registration instances? If an item can have multiple Registration instances associated with it, then the property should be called something like item.Registrations, and it should not be of type Registration, it should be a list/collection type that contains Registration instances.
The class should be derived from IEnumerable.
reference and examples: http://msdn.microsoft.com/de-de/library/system.collections.ienumerable%28VS.80%29.aspx

Resources