Assume I have the following Domain class:
import grails.rest.Resource
#Resource(formats=['json'], uri='/api/book')
class Book {
String name
static belongsTo = [user: User]
}
I have defined spring security interceptUrlMap in Config.groovy to restrict url access:
'/api/book': ['ROLE_USER']
Lets assume that there are two books and two users in the system and book with id 1 belongs to user 1 and the second book belongs to user 2. User 1 is logged in and the following request should return book 1 (which it does):
localhost:8080/myapp/api/book/1
but if the same user makes a request to the following resource:
localhost:8080/myapp/api/book/2
I want the rest API to return an empty array or "access denied". How do I achieve this? I need an approach there all request types i.e. GET/POST/PUT/DELETE are taken care of.
Solved this issue by generating the corresponding web-service controller:
grails generate-controller Book
And added restriction for every actions, i.e. Index, Show, Save, Update, Delete.
For example the Show action which is used to GET a resource:
localhost:8080/myapp/api/book/1
is changed to look as the following code:
import static org.springframework.http.HttpStatus.*
import grails.transaction.Transactional
#Transactional(readOnly = true)
class BookController {
def springSecurityService
static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]
Code omitted...
def show(Book bookInstance) {
if(bookInstance == null){
notFound()
return
}
def user = springSecurityService.currentUser
if(bookInstance.user.id == user.id) {
respond bookInstance
return
}
notFound()
}
Code omitted...
}
Grails version: 2.4.4
Related
I am using pytest with pytest-django and pytest-bdd to test a simple django application.
The settings file defines a test sqlite3 database, which is created when the tests are run.
My first function creates a model object:
#given("A message exists")
def given_a_message_exists(transactional_db, message_text):
message = message_models.Message(text=message_text)
message.save()
But although I can open and inspect the database using sql-browser, the model is never persisted to the database. I can, in my next function, obtain the message from the django ORM, but the message isn't persisted in the database, and a call to a url route to delete the message returns a 404 with 'Message not found matching the query'.
#when("User visits the delete message page", target_fixture="page")
def user_visits_delete_message_page(transactional_db, browser, message_text):
message = message_models.Message.objects.get(text=message_text)
url = f"{browser.domain}/delete_message/{message.id}/{message.slug}/"
browser.visit(url)
return browser
When I run the site normally, everything works as expected.
Here are the fixtures from my conftest.py...
MESSAGE_TEXT = "Ipsum Lorum Dolum Est"
CREATE_MESSAGE_URL = reverse("django_messages:message_create")
LIST_MESSAGE_URL = reverse("django_messages:message_list")
LINKS_DICT = {
"create_message": f"a[href='{CREATE_MESSAGE_URL}']",
"list_message": f"a[href='{LIST_MESSAGE_URL}']",
}
PAGES_DICT = {
"create_message": CREATE_MESSAGE_URL,
"list_message": LIST_MESSAGE_URL,
}
#pytest.fixture()
def message_text():
return MESSAGE_TEXT
#pytest.fixture()
def browser(sb, live_server, settings):
staging_server = os.environ.get("STAGING_SERVER")
if staging_server:
sb.visit(staging_server)
else:
sb.visit(live_server)
sb.domain = sb.get_domain_url(sb.get_current_url())
settings.EMAIL_PAGE_DOMAIN = sb.domain
sb.pages = PAGES_DICT
sb.links = LINKS_DICT
return sb
Why is the model not persisted to the database when I call message.save()?
btw, I have tried, transactional_db, and db, along with a whole host of other permutations...
It turns out that using the browser fixture causes a problem that I don't fully understand yet. If I pass the browser into a step, and then return it as a target_fixture for later functions to consume then everything works as expected.
[EDIT]
I discovered that if I define the browser in a fixture, that I must have referenced that browser fixture before or at the same time as the django object using it, or the browser refers to a different domain.
So, in the below, even though the message is created in a fixture (test_message), I must refer to it having referenced the browser. If the function message_exists is not passed 'browser', then the message is not listed on the message_list page.
def message_exists(browser, test_message):
test_message.save()
return test_message
#when("User visits the message list page", target_fixture="page")
def user_visits_messages_page(db, browser):
browser.visit(browser.domain + browser.pages["list_message"])
return browser
#then("The message is listed")
def message_is_listed(message, page):
page.assert_element(f"a[href='/{message.id}/{message.slug}/']")
am using python and google app engine majorly on jinja2 templates
i could like when a user registers a new account, they get a popup indicating that their registration is successful of even any alert on the very interface before moving to the next registration step.
def post(self):
user = (str(users.get_current_user().email()))
userquery = Users.query(Users.email == user)
count = userquery.count()
if count == 0:
#test if user is admin or employee
qry = Users.query()
count = qry.count()
if count == 0:
privilage = 'admin'
db_put = Users(
f_name=self.request.get("f_name"),
l_name = self.request.get("l_name"),
org = self.request.get("org"),
email=users.get_current_user().email(),
privilage = privilage
)
db_put.put()
How are you calling this POST method? Are you sending a form there directly (use method 1) or is this being done with an AJAX call (use method 2)?
Method 1
You can redirect to a GET page where you render a template with a success or error message for Jinja to use. This would however involve a page change.
import webapp2
class MyHandler(webapp2.RequestHandler):
def get(self): # Let's assume /someurl is mapped to this handler.
template_values = {}
notification = self.request.get('notification')
if notification:
template_values['notification'] = notification
self.response.set_status(200)
self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
# Need to get the template from jinja and set it as template variable.
self.response.out.write(template.render(template_values))
def post(self):
# Do all your stuff here.
self.redirect('/someurl?notification=Success')
Alternatively you can set the parameters directly on the request instead of passing them as URI parameters:
def post(self):
# Do all your stuff here.
self.redirect('/someurl, params={'notification': 'Success'})
Method 2
In this method you can send back a JSON response with a success or error message. The caller (whatever function in your javascript that submitted the request to the backend) can use that to render a butterbar message or other popup notification of your choosing:
import json
import webapp2
class MyHandler(webapp2.RequestHandler):
def post(self):
# Do all your stuff here.
self.response.set_status(200)
self.response.headers['Content-Type'] = 'application/json; charset=utf-8'
self.response.headers['Content-Disposition'] = 'attachment'
self.response.out.write(json.JsonEncoder(sort_keys=True).encode('Success'))
For the latter, make sure you think about cross-site scripting (XSS) vulnerabilities and perhaps add a JSON prefix.
I have a small web app with AngularJS front-end and Django ReST in the back. There's a strange hitch going on when I make POST request to the web service: the browser console clearly shows 3 parameters being sent, but the backend logging reports only 2 params received. The result is that the server throws a code 500 error due to a bad database lookup.
Here's the code:
Client
var b = newQuesForm.username.value;
$http.post('/myapp/questions/new', {username:b,title:q.title,description:q.description}).
success(function(data, status, headers, config) {
$http.get('/myapp/questions').success(function(data){
$scope.questions = data;
q = null;
$scope.newQuesForm.$setPristine();
}).error(function(data, status, headers, config) {
console.log(headers+data);
});
}).
error(function(data, status, headers, config) {
console.log(headers+data);
});
Both my manual logging and the dev console show a string like:
{"username":"admin","description":"What's your name?","title":"question 1"}
Server
class CreateQuestionSerializer(serializers.Serializer):
author = UserSerializer(required=False)
title = serializers.CharField(max_length=150)
description = serializers.CharField(max_length=350)
def create(self, data):
q= Question()
d = data
q.title = d.get('title')
q.description = d.get("description")
q.author = User.objects.get(username=d.get('username'))
q.save()
return q
Server-side logging shows the username parameter never succeeds in making the trip, and thus I end up with code 500 and error message:
User matching query does not exist. (No user with id=none)
What's causing some of the data to get lost?
So it turns out the problem was really with the serialization of fields, as #nikhiln began to point out. I followed his lead to refactor the code, moving the create() method to api.py, rather than serializers.py, and stopped relying altogether on the client-side data for the user's identity, something that was a bit silly in the first place (passing User to a hidden input in the view, and then harvesting the username from there and passing it back to the server in the AJAX params). Here's the new code, that works perfectly:
class QuestionCreate(generics.CreateAPIView):
model = Question
serializer_class = CreateQuestionSerializer
def create(self, request,*args,**kwargs):
q= Question()
d = request.data
q.title = d.get('title')
q.description = d.get("description")
q.author = request.user
q.save()
if q.pk:
return Response({'id':q.pk,'author':q.author.username}, status=status.HTTP_201_CREATED)
return Response({'error':'record not created'}, status=status.HTTP_400_BAD_REQUEST)
So here, I do it the right way: pull the User from the request param directly in the backend.
I'm using AngularJS for the front-end and Django for the backend of a web app I'm working on. Right now I'm working on logging in users and I'm having a strange problem. Heres the relevant Angular code:
app.factory('AuthService', ["$http", "$q", "Session", "URL", function($http, $q, Session, URL) {
return {
login: function(credentials) {
var deferred = $q.defer();
$http.post(URL.login, credentials)
.then(function(data, status, headers, config) {
data=data.data; //WHY DOES THIS WORK?
if (data.success == true) {
alert("logged in");
Session.create(credentials.username, data.api_key);
deferred.resolve();
}
else {
deferred.reject("Login failed!");
}
}, function(data, status, headers, config) {
deferred.reject("Login failed!");
});
return deferred.promise
},
And here is the corresponding Django view:
def login_user(request):
'''
Given a username and password, returns the users API key.
'''
if request.method == 'POST':
username = request.POST.get('username',None)
password = request.POST.get('password',None)
user = authenticate(username=username,password=password)
if user is not None:
api_key = ApiKey.objects.get(user=user)
response_data = {}
response_data["api_key"] = str(api_key).split(" ")[0]
response_data["success"] = True
return HttpResponse(json.dumps(response_data), content_type="application/json")
else:
return HttpResponse(json.dumps({"username":username,"success":False}),content_type="application/json")
return HttpResponseBadRequest()
When the user logs in a POST request is sent and handled by the above Django code. The response is then picked up by the AngularJS code above. As you can see the then() method in the Angular code takes the usual four parameters: data, status, config and headers. I expect to see data contain the dictionary output from the Django code, appropriately serialized into a JSON object.
However what happens is that the only parameter of the then() method which is not undefined is data, and this contains EVERYTHING; headers, data, status code,etc.
The line commented 'WHY DOES THIS WORK' fixes the problem, by accessing the data inside. However, I want to know why this is happening and if there is any way to avoid this. My best guess is that it has something to do with the way Django serializes a response but I'm not sure.
I'm using Django 1.6.5.
That is actually how Angular promises work according to the docs. Here is the relevant quote.
Since the returned value of calling the $http function is a promise,
you can also use the then method to register callbacks, and these
callbacks will receive a single argument – an object representing the
response. See the API signature and type info below for more details.
The emphasis was mine.
I am trying to create a webservice class in src/groovy but it gives error and complains no webservice found.
Please help why i am not getting in src/groovy.
import org.apache.cxf.transport.http.HTTPConduit
import org.grails.plugins.wsclient.service.WebService
class ConnectWebservice {
WebService webService ///it complains that not found.
def static wsHandleMap = [:]
def static handle
static def getProxy = {url->
def wsdlURL = url+"?wsdl"
def proxy = webService.getClient(wsdlURL) ////not get webService
proxy?.initialize()
proxy.metaClass.getCxfClient = { ->
delegate.client
}
// get client instance
def cxfClient = proxy.cxfClient
// create new endpoint url
URL newUrl = new URL(url)
// assign new created url to the client
cxfClient.getConduit().getTarget().getAddress().setValue(newUrl.toExternalForm());
//Extra: to set timeout, use:
proxy.client.conduit.clientSidePolicy.setReceiveTimeout(999)
proxy.client.conduit.clientSidePolicy.setConnectionTimeout(999)
//println proxy
return proxy
}
So this is inside src/groovy?
Services don't get injected into files found in src/groovy by default.
To get this working, you should consider making this class a Service (easiest route if possible), or else you will need to register the class as a Spring bean to that required services and classes get injected
PS: You have installed the WSClient plugin right?
i got it working.changed the line -
def proxy = webService.getClient(wsdlURL)
TO
def proxy = new WSClient(wsdlURL,this.class.classLoader)