I am having trouble in validating and reseting some fields based on the role of a user.
I am trying to develop a rest api with grails and my problem appears when i try to reset some fields based on the role of an user. I send a json with the desired "not allowed" changes via PUT to the controller. I modify the not allowed fields to ones that are correct for me and then call .save() and the "not alowed" fields are updated with their sent value, not with the modified by me values. Here is the code.
THE MODEL
package phonebook
class User {
String firstName
String lastName
String phoneNo
String address
String email
String password
boolean active = false
String hash
String authToken = ""
String role = "user"
static hasMany = [contacts:Contact]
static constraints = {
firstName(blank: false)
lastName(blank: false)
address(blank: true)
phoneNo(unique: true)
email(blank: false, unique: true)
password(blank: false)
role(blank: false, inList: ["user", "admin"])
hash(blank: true)
authToken(blank: true)
active(inList:[true,false])
}
}
THE METHOD FROM CONTROLLER:
#Transactional
def update(User userInstance) {
if (!isAuthenticated()){
notAllowed()
return
}
if (userInstance == null) {
notFound()
return
}
//if(isAdmin()){
def userBackup = User.findById(userInstance.id)
userInstance.role = userBackup.role
userInstance.active = userBackup.active
userInstance.hash = userBackup.hash
userInstance.authToken = userBackup.authToken
//}
if (userInstance.hasErrors()) {
respond userInstance.errors, view:'edit'
return
}
userInstance.save flush:false
request.withFormat {
'*'{ respond userInstance, [status: OK] }
}
}
THE JSON SENT VIA PUT
{
"id":"1",
"firstName": "Modified Name 23",
"role":"admin",
"active":"true",
"hash":"asdasd"
}
The above code should not modify my values for hash or active or role even if they are sent.
Any ideas?
Thanks.
The reason your changes are being saved is because by default any changes made to a domain instance will be flushed at the end of the session. This is known as open session in view with automatic session flushing. I recommend you do some reading on some of the main issues people face with GORM.
Proper use of discard may solve your issue. Discard your instance changes before you exit your controller.
For example:
if (!isAuthenticated()){
notAllowed()
userInstance.discard()
return
}
Edit
Based on conversation in the comments this perhaps may be the way to address your issue. A combination of discard and attach.
userInstance.discard()
def userBackup = User.findById(userInstance.id)
userInstance.role = userBackup.role
userInstance.active = userBackup.active
userInstance.hash = userBackup.hash
userInstance.authToken = userBackup.authToken
userInstance.attach()
I was helped by this method.
getPersistentValue
Example
def update(ShopItem shopItemInstance) {
if (shopItemInstance == null) {
notFound()
return
}
if (!shopItemInstance.itemPhoto){
shopItemInstance.itemPhoto =
shopItemInstance.getPersistentValue("itemPhoto");
}
if (shopItemInstance.hasErrors()) {
respond shopItemInstance.errors, view:'edit'
return
}
shopItemInstance.save flush:true
redirect(action: "show", id: shopItemInstance.id)
}
In your case:
userInstance.role = userInstance.getPersistentValue("role")
userInstance.active = userInstance.getPersistentValue("active")
userInstance.hash = userInstance.getPersistentValue("hash")
userInstance.authToken = userInstance.getPersistentValue("authToken")
It's better if you'll use the command objects feature. You can bind a command object with the request payload, validate it and than find and update the domain object.
You can find more details here:
http://grails.org/doc/2.3.x/guide/theWebLayer.html#commandObjects
And off the record you shoudn't use #Transactional in your controller. You can move that code into a service.
Eq:
def update(Long id, UserCommand cmd){
// Grails will map the json object into the command object and will call the validate() method if the class is annotated with #Validatable
}
Related
I'm following this codelab to build an paging3 app with github API and a local DB. While the first 2 pages load fine, the mediator hits a loop when trying to load the 3rd page when scrolling to bottom - the same PagingState is passed to load() function over and over.
Just wondering if anyone knows what could be the possible root cause here?
Some implementation details:
RemoteMediator: (the prevPage and currentPage is from github API's pagination response header and saved to a local DB.)
// RepositoryMediator
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Repository>
): MediatorResult {
return when (loadType) {
LoadType.REFRESH -> {
fireRequestForPage(1, true /*clear DB*/)
return Success(endOfPaginationReached = false)
}
LoadType.APPEND -> {
// !!!!!!! kept getting the same state when APPEND is triggered, resulting in same currentPage and nextPage
// get currentPage, nextPage from state.lastItemOrNull
if(currentPage < nextPage) {
fireRequestForPage(nextPage)
Success(endOfPaginationReached = false)
} else {
return Success(endOfPaginationReached = true)
}
LoadType.PREPEND -> {
// get currentPage, prevPage from state.firstItemOrNull
if(currentPage > prevPage) {
fireRequestForPage(prevPage)
Success(endOfPaginationReached = false)
} else {
return Success(endOfPaginationReached = true)
}
}
}
}
Observable: I'm using liveData instead of flow to from the Pager:
fun searchRepositoryWithUserId(userLoginName: String): LiveData<PagingData<Repository>> {
// need to create a new Pager each time because the search query is different
return Pager(
config = PagingConfig(pageSize = PAGE_SIZE, enablePlaceholders = false),
remoteMediator = RepositoryMediator()
) {
repoDao().getRepositoriesOfUser(userLoginName)
}.liveData
}
Dao: just a plain query
#Query("SELECT * FROM repository_table WHERE login = :ownerLoginName")
fun getRepositoriesOfUser(ownerLoginName: String): PagingSource<Int, Repository>
For anyone interested, the fix is from Dao, need to update the query to sort on reponame, otherwise the query will return the same last Page for PagingSource even if there're new items inserted into DB, confusing the Mediator.
#Query("SELECT * FROM repository_table WHERE login = :ownerLoginName ORDER BY repository_name ASC")
fun getRepositoriesOfUser(ownerLoginName: String): PagingSource<Int, Repository>
Had a similar issue just now. Trying to sort by different fields had led to RemoteMediator getting stuck in a loop on different page numbers.
Turns out I couldn't rely on item ID's assigned by backend to be primary keys for my Room DB Entity. Assigning primary key ID's locally (starting from zero) seems to have fixed the issue.
I upgrade from Play 2.5 to 2.7, and am having a problem with saving my forms.
When fields are changed and I call the Model.update() the changes are not persisted in the database (even though they show changed when debugging before the update is done)
When however I set them specifically, then they do persists. So it must have to do something with the fact that it does not detect the change and does not see the object as changed. I use getter and setters in the model, and all the properties are private.
This is the controller function (with the two lines to persist those two fields)
#Check(UserTask.MANAGER)
public Result updateSceneSet(Http.Request request) {
Messages messages = messagesApi.preferred(request);
Form<StreamingSceneSet> form = formFactory.form(StreamingSceneSet.class).bindFromRequest(request);
if (form.hasErrors()) {
if (form.rawData().get("id") != null && form.rawData().get("id").length() > 0) {
long itemId = Long.parseLong(form.rawData().get("id"));
StreamingSceneSet item = StreamingSceneSet.findById(itemId);
return badRequest(views.html.streaming.editSceneSetView.render(form, item, messages, request));
} else {
return badRequest(views.html.streaming.createSceneSetView.render(form,messages, request));
}
}
// Form is OK, has no errors we can proceed
StreamingSceneSet item = form.get();
item.setName(item.getName());
item.setDescription(item.getDescription());
// Insert or update?
if (item.getId() == null) {
item.insert();
flash("success", messages().at("addedSceneSet", item.getName()));
} else {
item.update();
flash("success", messages().at("updatedSceneSet", item.getName()));
}
return redirect(routes.Streaming.sceneSets());
}
It seems because when I started the upgrade I had some legacy classes I didn't have getters and setters, and as I had some issue I put in:
play.forms.binding.directFieldAccess = true
Removing this made everything work again.
I am using Grails with RESTful to develop my web application. Everything works fine, till I upgrade my application to Grails 2.3. Here is my UrlMappings:
I still send request, submit or do some other things normally, but in POST, PUT requests, the parameters are missing. Server just recognize only the parameters I put on the URL directly, but the remain I enclose in form or model when submit cannot be found in the "params" variable. He is my UrlMappings:
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?"{ constraints {} }
name apiSingle: "/api/$controller/$id"(parseRequest:true){
action = [GET: "show", PUT: "update", DELETE: "delete"]
constraints { id(matches:/\d+/) }
}
name apiCollection: "/api/$controller"(parseRequest:true){
action = [GET: "list", POST: "save"]
}
name api2: "/api/$controller/$action"(parseRequest:true)
name api3: "/api/$controller/$action/$id"(parseRequest:true)
"/"(view:"/welcome")
"500"(view:'/error')
}
}
I have read the latest document of Grails 2.3, at http://grails.org/doc/latest/guide/theWebLayer.html#restfulMappings
but I think it is not clear. I have tried it follow the documentation but have no result. And there are no any sample about using Grails 2.3 with RESTful for me to refer.
How can I make it work normally as before, and can access all parameter values in REST request? Thank you so much!
According to this http://grails.1312388.n4.nabble.com/Grails-2-3-and-parsing-json-td4649119.html parseRequest has no effect since Grails 2.3
If you use JSON as request body you can accees request params as request.JSON.paramName
As a workaround you can add a filter that will populate data from JSON to params:
class ParseRequestFilters {
def filters = {
remoteCalls(uri: "/remote/**") {
before = {
if (request.JSON) {
log.debug("Populating parsed json to params")
params << request.JSON
}
}
}
}
}
Adding on to Kipriz's answer and cdeszaq's comment, you can write a recursive method to inject nested params. Something along these lines:
public void processNestedKeys(Map requestMap, String key) {
if (getParameterValue(requestMap, key) instanceof JSONObject) {
String nestedPrefix = key + ".";
Map nestedMap = getParameterValue(requestMap, key)
for (Map.Entry<String, Object> entry : nestedMap.entrySet()) {
String newKey = nestedPrefix + entry.key;
requestMap.put(newKey, getParameterValue(nestedMap, entry.key))
processNestedKeys(requestMap, "${nestedPrefix + entry.key}");
}
}
}
public static Map populateParamsFromRequestJSON(def json) {
Map requestParameters = json as ConcurrentHashMap
for (Map.Entry<String, Object> entry : requestParameters.entrySet()) {
processNestedKeys(requestParameters, entry.key)
}
return requestParameters
}
Here is my URLmappings.groovy
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?(.${format})?" {
constraints {
// apply constraints here
}
}
"/ewhet/$id"(controller : "ewhet", action : "show")
"/"(view: "/index")
"500"(view: '/error')
}
}
Here is my ewhetController's show action:
class EwhetController {
def index(){
}
def show(){
def ctx = startAsync()
ctx.start {
render params
//render "this invoked!!"
ctx.complete()
}
}
}
Now when I enter the url as: http://localhost:8080/g24/ewhet/abc
The abc does not get mapped to the params.id and when I render params, I get an empty map [:] . In case if url is entered as http://localhost:8080/g24/ewhet/show?id=abc the id field gets mapped to the params.id and I get:
['id':'abc']
So I just want to get the last part of the url mapped to the id parameter in params map without using any map in the url (like id=abc) as per Section 7.4.3 in Grails documentation So how is that possible and why is my approach not working?
Kindly note that I do not have any domain classes as I am using schemaless mongodb at my backend.
Try to reload the app after changing the UrlMappings.groovy to assure the new config is correctly loaded.
I am developing a plugin in crm 5.0 to retrieve date "ct_valuedate" from an entity called "ct_marketvalue" and formatting and saving in a field called "ct_dateserial"
I get an error while I debug "The given key was not present in the dictionary"
public class MarketValueDateFormatting : PluginBase
{
protected override void ExecutePlugin()
{
try
{
switch (_crmMessage)
{
case CrmPluginMessageEnum.Create:
if (_context.InputParameters.Contains("ct_marketvalue"))
{
//Obtain the logical name of the entity
string moniker1 = ((EntityReference)_context.InputParameters["EntityMoniker"]).LogicalName;
//Verify that the target entity represents an Account.
//If not, this plug-in was not registered correctly.
if (moniker1.Equals("ct_marketvalue"))
{
Entity marketvalueimage = (Entity)_context.PostEntityImages["ct_marketvalue"];
Guid marketvalueid = marketvalueimage.Id;
if (marketvalueimage.Contains("ct_valuedate"))
{
DateTime dateserial = (DateTime)marketvalueimage.Attributes["ct_valuedate"];
String dateserialstring = dateserial.ToString("YYYYMMdd");
Ct_marketvalue marketvalue = new Ct_marketvalue();
marketvalue.Ct_dateserial = dateserialstring;
marketvalue.Id = marketvalueid;
_serviceContext.UpdateObject(marketvalue);
}
}
}
break;
}
catch (Exception ex)
{
throw ex;
}
}
}
}
Few notes about your code.
You should check in your code that _context.PostEntityImages contains "ct_marketvalue". It's possible either to forgot register or to do a mistake in image name.
Might be better use .ToEntity rather than access attributes using .Attributes["ct_valuedate"].
I'm not sure what is purpose of the plugin you wrote, but it looks it is post stage plugin and it updates the same entity instance, that was in InputParameters. Might be better to make this plugin pre stage and update value directly in InputParameters. Because, if not "The given key was not present in the dictionary" exception, it will cause infinite loop. You will need check context.Depth.