Is there a way to limit access to Grails domain objects across the board to the owner of the object?
For example, I can make an assert easily, but I don't want to duplicate that everywhere, or risk missing a spot.
This isn't exactly the same as multi-tenancy because it's not just a tenant ID - it may be specific business logic for different domain objects.
class MyDomain {
String name
String user
}
class MyController {
def show(Long id) {
def obj = MyDomain.get(id)
// *** How do I not do copy-paste this line in each individual controller
// that touches MyDomain?? ***
assert obj.user == CURRENT_USER
return obj
}
}
There are so many ways to handle such scenarios, as other answers are suggesting, however, I think one proper way to approach it is to use Spring Security Plugin and spring-security-acl plugin. ACL plugin will drill down into object level and helps you to control
According to doc
"The ACL plugin adds Domain Object Security support to a Grails
application that uses Spring Security."
The combination of both security core and ACL can help you accomplish what you need.
Filter might be one way, another way is to adjust your queries if possible. You would probably need to be able to search for some other criteria other than the ID. Without understanding your use case a little more, it is hard to give a better answer.
def show(String someValue) {
def currentUser = howeverYouGetYourUser
def obj = MyDomain.findByUserAndSomeValue(currentUser, someValue)
if (obj) {
// yeah!!!
} else {
// boo!!
}
}
Ideally, if you are looking for specific data for a specific user, ID isn't really the way to go.
Iam not sure if you can do it in domain level but one way is using filters. There is a filters plugin available for Grails. Keep the User in session and verify in filter for each request..
Sample filter code:
class SecurityFilters {
def filters = {
loginCheck(controller: '*', action: '*') {
before = {
if (!session.user && !actionName.equals('login')) {
redirect(action: 'login')
return false
}
}
}
}
}
and specify the filter attributes..
Here is the documentation grails.org/doc/2.2.1/ref/Plug-ins/filters.html
Related
I use CouchDB and I would like all my users to have unique emails. I would expect that the database return a status 400 (bad request) when I try to duplicate an email.
But since there is no way to define constraints in CouchDB, I should implement it myself, my question is:
In which layer of my application should this rule stand?
(1) Domain objects layer
I don't really know how to implement it in this layer
(2) Interactor layer
This constraint could be implemented in interactors here since there is the place where business rules stand. But if there is multiple rules for single document it could add unnecessary complexity...
function createUser(userData) {
let email = userData.email;
let exist = await userDB.userExist(email);
if(exist) {
// return status 400
} else {
// create user
}
}
(3) Database gateway layer
The constraint can also be implemented in the database gateway layer. Usually we'll have a gateway for each specific entity. But does that means that external services adapters contains a bit of business logic?
class userDB() {
constructor(opts) {
this.db = opts.db.connect();
}
async userExist(email) {
return await this.db.fetchByView('email', email);
}
async create(email) {
let exist = await this.userExist(data.email);
if(exist) {
// throw error
} else {
// create the user
}
}
}
Unique email address is a very old DDD topic. It relates to the set validation.
The simplest (and from my point of view is also the best) is to place a constraint at the database level.
From what I know, the only whay to create an unique constraint in CouchDB is to use the _id field so you can use this solution. This idea is to put the email in the _id field.
let exist = await this.userExist(data.email);
if(exist) { // throw
error } else { // create the user }
This method is not safe for concurrent updates. Two users can be created at the same time. Imagine that for both the requests the this.userExist(data.email) is returning false.
I am not a CouchDB expert but from pure Clean Architecture perspective ensuring unique email addresses is a business rule which should be implemented in an interactor.
With this approach ur business rule would remain intact even if u would once decide to replace the detail CouchDB with some other detail - another storage system.
Good day!
In my Orchard, I have several content types all with my custom part attached. This part defines to what users this content is available. For each logged user there is external service, which defines what content user can or cannot access. Now I need access restriction to apply everywhere where orchard display content lists, this includes results by specific tag from a tag cloud, or results listed from Taxonomy term. I seems can’t find any good way to do it except modifying TaxonomyServices code as well as TagCloud services, to join also my part and filter by it. Is this indeed the only way to do it or there are other solutions? I would like to avoid doing changes to built-in modules if possible but cannot find other way.
Thanks in advance.
I'm currently bumbling around with the same issue. One way I'm currently looking at is to hook into the content manager.
[OrchardSuppressDependency("Orchard.ContentManagement.DefaultContentManager")]
public class ModContentManager : DefaultContentManager, IContentManager
{
//private readonly Lazy<IShapeFactory> _shapeFactory;
private readonly IModAuthContext _modAuthContext;
public ModContentManager(IComponentContext context,
IRepository<ContentTypeRecord> contentTypeRepository,
IRepository<ContentItemRecord> contentItemRepository,
IRepository<ContentItemVersionRecord> contentItemVersionRepository,
IContentDefinitionManager contentDefinitionManager,
ICacheManager cacheManager,
Func<IContentManagerSession> contentManagerSession,
Lazy<IContentDisplay> contentDisplay,
Lazy<ISessionLocator> sessionLocator,
Lazy<IEnumerable<IContentHandler>> handlers,
Lazy<IEnumerable<IIdentityResolverSelector>> identityResolverSelectors,
Lazy<IEnumerable<ISqlStatementProvider>> sqlStatementProviders,
ShellSettings shellSettings,
ISignals signals,
//Lazy<IShapeFactory> shapeFactory,
IModAuthContext modAuthContext)
: base(context,
contentTypeRepository,
contentItemRepository,
contentItemVersionRepository,
contentDefinitionManager,
cacheManager,
contentManagerSession,
contentDisplay,
sessionLocator,
handlers,
identityResolverSelectors,
sqlStatementProviders,
shellSettings,
signals) {
//_shapeFactory = shapeFactory;
_modAuthContext = modAuthContext;
}
public new dynamic BuildDisplay(IContent content, string displayType = "", string groupId = "") {
// So you could do something like...
// var myPart = content.As<MyAuthoPart>();
// if(!myPart.IsUserAuthorized)...
// then display something else or display nothing (I think returning null works for this but
//don't quote me on that. Can always return a random empty shape)
// else return base.BuildDisplay(content, displayType, groupId);
// ever want to display a shape based on the name...
//dynamic shapes = _shapeFactory.Value;
}
}
}
Could also hook into the IAuthorizationServiceEventHandler, which is activated before in the main ItemController and do a check to see if you are rendering a projection or taxonomy list set a value to tell your content manager to perform checks else just let them through. Might help :)
Using Grails (or hibernate), I was wanting to know if there is a specific design pattern or method we should be using when implementing a SEARCH of our domain.
For example, on my website, I want to be able to filter(or search) by multiple properties in the domain.
EG: For I have a page which displays a list of HOTELS. When I submit a search form, or if a user clicks "filter by name='blah'", when I enter the controller I get the following:
Domain
String name
String location
Controller
if(params.name && params.reference) {
// Find name/reference
} else if(params.name) {
// Find name
} else if(params.reference) {
// Find reference
} else {
// Find all
}
As you can understand, if there are more properties in the domain to search/filter, the longer the controller gets.
Any help. Please note, I do not want to use the 'searchable' plugin, as this is too complex for my needs.
I would embed these in a named query in the Domain class itself. For example:
Class Hotel {
String name
String city
String country
boolean isNice
static namedQueries = {
customSearch { p ->
if (p?.name) eq('name', p.name)
if (p?.city) eq('name', p.city)
if (p?.country) eq('name', p.country)
if (p?.isNice != null) eq('isNice', p.isNice)
}
}
}
Then later in a controller somewhere ...
def results = Hotel.customSearch(params)
Of course this is a very simple example, but you can expand on it using the same named query or even adding others and chaining them together.
I'm using a function to allow query composition from Web UI and I would to implement paging functionality which it will be available for dataBound controls such as ObjectDataSource, gridView, etc:
public class MyClass<TEntity> where TEntity : class
{
FakeEntities xxx = new FakeEntities();
public IEnumerable<TEntity> Get(Func<IQueryable<TEntity>, IQueryable<TEntity>> queryExpression)
{
var query = xxx.Set<TEntity>();
return queryExpression(query).ToList();
}
public int Count()
{
// What Can I return?
}
}
// **** USAGE ****
MyClass<User> u = new MyClass<User>();
var all = u.Get(p => p.Where(z => z.Account == "Smith").OrderBy(order => order.IdOther).Skip(1).Take(2));
The above query use Take and Skip function, so can I get real count of my entities? Obviously I must return Query Count without modifying filter expression.
I found this solution: Get count of an IQueryable<T>
However I get targetInvocationException with inner message {"This method supports the LINQ to Entities infrastructure and is not intended to be used directly from your code."}
I know my request could be freak-abnormal, because best practice should to impose to move "presentation needs" to some wrap class and that's is what I'll do. So I don't need anymore to get Count entities on my business logic class.
That's just UI concern only.
Thank you the same.
What is the best way to pass the model variables to layout in Grails? Specifically, I'm using Spring security plugin which has User class. I also have Contact class that looks like this:
class Contact {
String realname
String company
String mobile
String fix
String email
User user
...
What are the options for getting the currently logged in person's company in my layout (main.gsp)?
To add to the above answer, you could alternatively set a session variable for the user when you login in whatever controller method gets called.
You can also just set a session variable for the company in the controller method:
session.company = Contact.findByUser(session.user)?.company
or from the example above
session.company = Contact.findByUser(SecurityContextHolder.context.authentication?.principal)?.company
And in your main.gsp, something like:
<span id="companyName">${session.company}</span>
Do you mean that you need to pass this model for every page, automatically, instead of manual passing it at render at each of controllers? You can use filters there:
def filters = {
all(controller: '*', action: '*') {
before = {
request.setAttribute('loggedInPerson', SecurityContextHolder.context.authentication?.principal)
//Notice, that there is used original Authentication, from Spring Security
//If you need you can load your Contact object there, or something
}
after = {
}
afterView = {
}
}
}
and use loggedInPerson at your gsp:
Hello ${loggedInPerson.username}!
Btw, there is also Spring Security tags, that can help you without using your own filter, like:
Hello <sec:loggedInUserInfo field="username"/>!
If you want to add a certain object to the model, you can also use the "interceptors" provided by grails. To add a certain variable to a particular controller, you can use something like this.
def afterInterceptor = {model, modelAndView->
model.loggedInUser = getLoggedInUser() // retrieve your user details here
}
And you can retrieve loggedInUser in the main.gsp layout as ${loggedInUser}.
If you need to get these details in multiple controllers, you can create a BaseController and keep the afterInterceptor in this BaseController. All controllers which need the reference to the logged in user in their corresponding views should extend the BaseController.