I need to develop a application for a user's management in a IT Project. This is done in order to learn Grails. I have some problem to start with :
In my sample app, a user has many tasks, belongs to a project, have many holiday status, belongs to the Resource planning and thats it. (kind of requirements!)
Now..... When it comes to domain modeling, how I actually model this? I came up the solution of modeling something like this :
class User {
//relationships. . . .
static belongsTo = [ company : Company, role : Role, resource : Resource]
static hasMany = [ holidays : Holiday, tasks : Tasks ]
Profile profile
Project project
String login
String password
static constraints = {
login(unique:true,size:6..15)
profile(nullable:true)
}
String toString() {
this.login
}
}
Now taking advantage of Grails scaffolding. I generated the view, hmmm well thats where I got struck!
With this model in place, clearly when creating a new User., I need to give away project details, resource details. Even though I can change the view as I need, for example I need only two fields say login and password for the new User to register my site. But as per data modeling, how I can handle other fields like Profile, Project and other relationships. This sounds complicated! I know i'm a newbie to both web development and I want your suggestions and how do I proceed with this?
Thanks in advance.
You need to override the save action in your controller and fill those additional fields there.
Try adding the following code in UserController:
def save = {
def userInstance = User.get(params.id)
if (!userInstance) {
userInstance = new User()
// here you can fill in additional fields:
userInstance.profile = myMethodToGetTheProfile()
userInstance.project = myMethodToGetTheProject()
...
}
userInstance.properties = params
if (userInstance.save(flush: true)) {
flash.message = message(code: 'default.created.message', args: [message(code: 'User.propertyName.label', default: 'User'), userInstance.id])}
redirect(action: session.lastActionName ?: "list", controller: session.lastControllerName ?: controllerName)
} else {
render(view: "form", model: [userInstance: userInstance, mode: 'edit'])
}
}
The implementation of methods to get the default project depends on your app's logic.
Related
I have a modified User class in my spring security which use attribute email instead of username. Now I want to a functionality to switch users, so an admin can login seamlessly as a particular user without logging out. I came across this Link , which shows there is a switchUserFilter to achieve this. So I tried to get it working by passing j_username as email,but it gets redirected to a blank page and the user does not switch.
I have tried all these things but still could not figure out a way around it:
1) Added to Config.groovy:
grails.plugins.springsecurity.userLookup.usernamePropertyName='email'
2) Create a method in User class getUserName() to return email.
P.S: I looked into the source code of springSecurity switchUserFilter(link)and came across this code on line 209:
protected Authentication attemptSwitchUser(HttpServletRequest request)
throws AuthenticationException {
UsernamePasswordAuthenticationToken targetUserRequest;
String username = request.getParameter(usernameParameter);
But I am not sure if that is the issue and do not want to make changes in the plugin.
The usernameParameter property of the SwitchUserFilter is set to username by default. That does seem to be part of your problem.
The SwitchUserFilter has a method named setUsernameParameter() that allows you to change this default. It seems the filter is a bean, so you might be able to do something like this in grails-app/conf/spring/Config.groovy
import org.springframework.security.web.authentication.switchuser.SwitchUserFilter
beans = {
switchUserFilter {
usernameParameter = 'email'
}
}
Or maybe something like this in grails-app/config/BootStrap.groovy
def switchUserFilter
def init = { servletContext ->
switchUserFilter.usernameParameter = 'email'
}
Finally found the solution: Add this to the config.groovy file
grails.plugin.springsecurity.userLookup.usernamePropertyName = 'email'
grails.plugin.springsecurity.useSwitchUserFilter = true
grails.plugin.springsecurity.controllerAnnotations.staticRules = [
....
'/j_spring_security_switch_user': ['ROLE_SWITCH_USER', 'isFullyAuthenticated()'],
'/j_spring_security_exit_user': ['isFullyAuthenticated()'],
'/public/**': ['permitAll']
.....
]
2) Then create a Role ROLE_SWITCH_USER in bootstrap.groovy
def switchUserRole = Role.findByAuthority('ROLE_SWITCH_USER') ?: new Role(authority: 'ROLE_SWITCH_USER').save(flush: true, failOnError: true)
And assign it to a super user
3) Then follow the instruction(here) to update the view to add a switch button
Okay, I am trying to find all of my Employee that have a given role... However no matter what I try I end up getting the same exception thrown...
enum Role {
SFC("State Fitness Coordinator"),
TRAINING("Training"),
DFC("District Fitness Coordinator"),
final String longName
private Role(String longName) {
this.longName = longName
}
}
class Employee {
static hasMany = [roles: Role]
static constraints = {
}
}
The first Thing I tried was Employee.findAllByRoles(Role.DFC)
Then I tried:
Employee.findAll("FROM Employee e WHERE e.roles IN (:role)", [role: [Role.DFC]])
as well as
Employee.withCriteria {
'in'('roles', [Role.DFC])
}
all resulting in
Class
java.sql.SQLException
Message
Missing IN or OUT parameter at index:: 1
Any direction would be much appreciated.
with grails 2.3.8 and H2
Employee.findAll("FROM Employee e WHERE :role in elements(e.roles) ", [role: Role.DFC.toString()])
this works… even if I think that Role could be a real Domain simplifying all operations
I think the problem is that Role is an enum. Are you sure you want to make it an enum?
I recommend making it a class since new roles might be added. If so, you can easily add a record using bootstrap or SQL query without making a new build to production.
class Role {
String roleName
String description
}
In Bootstrap:
Role sfc = new Role("SFC","State Fitness Coordinator")
Role sfc = new Role("TRAINING," "Training")
Role sfc = new Role("DFC", "District Fitness Coordinator")
N.B. Please make sure you are not using spring security plugin which has its own 'Role' class. If so, please change the name of your 'Role' class to something like 'EmployeeRole'
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.
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.
I am working with the Silverlight RIA Services and I want to create custom authentication. This appears to be the only thing that has virtually no documentation (I've read through the entire RIAServicesOverview.docx).
Do you know of a way for me to create a customer authentication service? I don't want to use the default ASP.NET membership model. I don't know what interface or abstract class I need to implement - although I did find System.Web.Ria.ApplicationServices.IAuthentication.
Do I need to implement IAuthentication? If so, could you give me some advice on how to go about doing so? These are the following methods:
public User GetUser();
public User Login(string userName, string password, bool isPersistent, string customData);
public User Logout();
public void UpdateUser(User user);
I don't know how I would implement any of these (except for Login) - how could the service possibly know what user is currently logged in in order for Logout() to work?
I've been scouring the web in search of how to do this for hours, and I can't find anything that describes how to create a simple DomainService that can be used for authenticating a user in an "RIA-linked" Silverlight project.
If someone could shed some light on this, I'd be sincerely grateful.
Thanks,
Charles
[EDIT]
I found the RIA Services page on the MSDN Code Gallery. There's a section called Authentication Samples, which links to some great code samples. Check it out if you want to know more about how authentication works within RIA Services.
If you create a "Silverlight Business Application" you'll see how the template implements authentication. (Or just go here and download the template sample project.)
To simplify, here's the process I used:
First, I create a domain service (FooService) that derives from LinqToEntitiesDomainService where FooContext is my entity model. In it I add all the CRUD operations to access my custom DB table and return user profiles.
Next, create a concrete User class on the serverside by deriving from UserBase:
using System.Web.Ria;
using System.Web.Ria.ApplicationServices;
public class User : UserBase
{}
Finally, derive a class from AuthenticationBase and implement the following four methods:
[EnableClientAccess]
public class AuthenticationService : AuthenticationBase<User>
{
private FooService _service = new FooService();
protected override bool ValidateUser(string username, string password)
{
// Code here that tests only if the password is valid for the given
// username using your custom DB calls via the domain service you
// implemented above
}
protected override User GetAuthenticatedUser(IPrincipal pricipal)
{
// principal.Identity.Name will be the username for the user
// you're trying to authenticate. Here's one way to implement
// this:
User user = null;
if (this._service.DoesUserExist(principal.Identity.Name)) // DoesUserExist() is a call
// added in my domain service
{
// UserProfile is an entity in my DB
UserProfile profile = this._service.GetUserProfile(principal.Identity.Name);
user.Name = profile.UserName;
user.AuthenticationType = principal.Identity.AuthenticationType;
}
return user;
}
public override void Initialize(DomainServiceContext context)
{
this._service.Initialize(context);
base.Initialize(context);
}
protected override void Dispose(bool disposing)
{
if (disposing)
this._service.Dispose();
base.Dispose(disposing);
}
}
Here is a complete official example from MS:
http://code.msdn.microsoft.com/Custom-Authentication-96ca3d20
How about implementing the IAuthorization interface?