How to add a filter in in the "middle of the URL" using Restlet? - url-routing

I have the following routes:
/projects/{projectName}
and
/projects/{projectName}/Wall/{wallName}
Now I'd like to have that all GETs be allowed but PUT, POST, DELETE should only be allowed by project members i.e. users members of that project. I have a special class that given a user id and project name I can get the status of the user's membership - something like MyEnroler.getRole(userId, projectName) - where the userId is part of the request header and the projectName is taken from the URI.
I've tried a number of things but doesn't work. Here's the idea:
public class RoleMethodAuthorizer extends Authorizer {
#Override
protected boolean authorize(Request req, Response resp) {
//If it's a get request then no need for further authorization.
if(req.getMethod().equals(Method.GET))
return true;
else
{
String authorEmail = req.getClientInfo().getUser().getIdentifier();
String projectName = req.getAttributes().get("project").toString();
Role userRole = MyEnroler.getRole(authorEmail, projectName);
//forbid updates to resources if done by non-members of project
if(userRole.equals(MyEnroler.NON_MEMBER))
return false;
//for everybody else, return true
return true;
}
}
}
Now simply doing the following completely fails when creating inbound root in the Application:
Router projectRouter = new Router(getContext());
RoleMethodAuthorizer rma = new RoleMethodAuthorizer();
//Guard declaration here. Then setNext Restlet
guard.setNext(projectRouter);
projectRouter.attach("/projects/{project}",rma);
Router wallRouter = new Router(getContext());
wallRouter.attach("/Wall/{wallName}", WallResource.class);
rma.setNext(wallRouter);
//return guard;
So a request to /projects/stackoverflow/Wall/restlet fails. The URL is never found. I'm guessing since it's trying to match it with the projectRouter. Well I tried the various modes (MODE_BEST_MATCH or MODE_FIRST/NEXT_MATCH) to no avail.
Nothing seems to work. Conceptually this should work. I'm only intercepting a call and just being transparent to the request, but don't know how things are working on the inside.
I could move the authorizer just after the guard, but I'd lose access to the request attribute of projectName - I don't wish to parse the URL myself to search for the projectName since the URL pattern could change and would break the functionality - i.e. require 2 changes instead of one.
Any ideas how to achieve this?

I would use the standard RoleAuthorizer class to supply the list of allowed roles, along with your custom enroller probably split into two I would then add a custom Filter class that does something like this to call your Enrolers.
protected int beforeHandle(final Request request, final Response response) throws ResourceException {
final String projectName = (String) request.getAttributes().get("projectName");
// Check that a projectName is supplied, should not have got this far otherwise but lets check.
if (projectName == null || projectName.isEmpty()) {
throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND);
}
if (Method.GET.equals(request.getMethod())){
new ReadEnroler(projectName).enrole(request.getClientInfo());
}else{
new MutateEnroler(projectName).enrole(request.getClientInfo());
}
return super.beforeHandle(request, response);
}
the enrolers would then set the appropriate values in the clientInfo.getRoles() Collection when enrole was called.

Related

Spring React and Sessions.. how to keep session

I have set up my spring to maintain a HTTP session on an object like so:
#Component
#SessionScope
public class Basket { .. }
controller:
#PostMapping(path="/basket/addItem/{user}", consumes = "application/json", produces = "application/json")
public Basket createBasket(#PathVariable String user, #RequestBody Item item) {
System.out.println("POSTING..................................");
return basketService.addItem(user, item);
}
now when i use a REST client, in firefox i can see that the session bean is created and maintained for the duration - multiple calls. I can append to the object. If i try another client, it gets its own session with its own bean. great..
spring logs the following:
Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [269] milliseconds.
However im trying to create a basic front end in react, when react makes a request using axios it gets a new bean every time, which means that the session must be ending after each call. IS that correct? or im not tying it to the react application...
Maybe the approach im taking is not correct, maybe i should use a a different approach, Im trying to learn about spring boot, so its a basic project... and right now i want to maintain user session for a cart. so subsequent calls i can append to the object...
by adding the following to my controller it all began to work.
#CrossOrigin(origins = { "http://localhost:3000" }, allowedHeaders = "*", allowCredentials = "true")

How to use a CookieCollection in multiple functions

I'm setting up a web page using cookies to determine if the user already logged in, using a cookie containing his id. Problem is : The cookie is either not written or the cookie collection is not updated.
I've tried reading the documentation, but it does not define the usage of CookieCollection.
Here's the function where i write my cookie :
function displayData(){
$id = $this->getRequest()->getSession()->read('id');
$cookies = CookieCollection::createFromServerRequest($this->getRequest());
if(!$cookies->has('id')){
$cookie = (new Cookie('id'))
->withValue($id)
->withExpiry(new DateTime('+999 year'))
->withPath('/')
->withDomain('break-first.eu')
->withSecure(true)
->withHttpOnly(true);
$cookies = $cookies->add($cookie);
}
// Other stuff
}
And where I try reading it :
function index(){
$cookies = $this->getRequest()->getCookieCollection();
dd($cookies);
}
I expect having a cookie named "id", but I don't have it. Only CAKEPHP and pll_language are showing up.
First things first, CakePHP provides authentication functionality with cookie authentication, you may want to have a look at that instead of driving a custom solution.
Cookbook > Plugins > Authentication
That being said, what you're doing there will create a cookie collection object, which however is just that, a lone object somewhere in space, it won't affect the state of your application, in order for that to happen you have to actually modify the response object.
However what you're trying to do there doesn't require cookie collections in the first place, you can simply read and write cookies directly via the methods provided by the request and response objects, like:
// will be `null` in case the cookie doesn't exist
$cookie = $this->getRequest()->getCookie('id');
// responses are immutable, they need to be reassinged
this->setResponse(
$this->getResponse()->withCookie(
(new Cookie('id'))
->withValue($id)
->withExpiry(new DateTime('+999 year'))
->withPath('/')
->withDomain('break-first.eu')
->withSecure(true)
->withHttpOnly(true)
)
);
And if you where to use a cookie collection for whatever reason, then you'd use withCookieCollection() to pass it into the response:
$this->setResponse($this->getResponse()->withCookieCollection($cookies));
If you run into strict typing errors, you could for example create a custom reponse class with an overridden Response::convertCookieToArray() method and cast the string to an integer there (make sure that PHP_INT_MAX covers your target date timestamp, 32-Bit incompatibility is why the fix that landed in CakePHP 4.x, probably won't come to 3.x), something like:
src/Http/Response.php
namespace App\Http;
use Cake\Http\Cookie\CookieInterface;
use Cake\Http\Response as CakeResponse;
class Response extends CakeResponse
{
protected function convertCookieToArray(CookieInterface $cookie)
{
$data = parent::convertCookieToArray($cookie);
$data['expire'] = (int)$data['expire'];
return $data;
}
}
You can pass that into the app in your webroot/index.php file, as the second argument of the $server->run() call:
// ...
$server->emit($server->run(null, new \App\Http\Response()));
See also
Cookbook > Request & Response Objects > Request > Cookies
Cookbook > Request & Response Objects > Response > Setting Cookies

Cakephp 3 - CRUD plugin - Use id from auth component

Currently, I'm using the CRUD v4 plugin for Cakephp 3. For the edit function in my user controller it is important that only a user itself can alter his or her credentials. I want to make this possible by inserting the user id from the authentication component. The following controller method:
public function edit($id = null){
$this->Crud->on('beforeSave', function(\Cake\Event\Event $event) {
$event->subject()->entity->id = $this->Auth->user('id');
});
return $this->Crud->execute();
}
How can I make sure I don't need to give the id through the url? The standard implementation requires the url give like this: http://domain.com/api/users/edit/1.json through PUT request. What I want to do is that a user can just fill in http://domain.com/api/users/edit.json and send a JSON body with it.
I already tried several things under which:
$id = null when the parameter is given, like in the example above. Without giving any id in the url this will throw a 404 error which is caused by the _notFound method in the FindMethodTrait.php
Use beforeFind instead of beforeSave. This doesn't work either since this isn't the appropriate method for the edit function.
Give just a random id which doesn't exist in the database. This will through a 404 error. I think this is the most significant sign (combined with point 1) that there is something wrong. Since I try to overwrite this value, the CRUD plugin doesn't allow me to do that in a way that my inserting value is just totally ignored (overwriting the $event->subject()->entity->id).
Try to access the method with PUT through http://domain.com/api/users.json. This will try to route the action to the index method.
Just a few checks: the controllerTrait is used in my AppController and the crud edit function is not disabled.
Does anyone know what I'm doing wrong here? Is this a bug?
I personally would use the controller authorize in the Auth component to prevent anyone from updating someone else's information. That way you do not have to change up the crud code. Something like this...
Add this line to config of the Auth component (which is probably in your AppController):
'authorize' => ['Controller']
Then, inside the app controller create a function called isAuthorized:
public function isAuthorized($user) {
return true;
}
Then, inside your UsersController you can override the isAuthorized function:
public function isAuthorized($user) {
// The owner of an article can edit and delete it
if (in_array($this->request->action, ['edit'])) {
$userId = (int)$this->request->params['pass'][0];
if ($user['id'] !== $userId) {
return false;
}
}
return parent::isAuthorized($user);
}

RPC call to external server

I am a new bie on GWT, I wrote an application on abc.com, I have another application i.e. xyz.com, xyz.com?id=1 provides me a data in json format, I was thinking to find a way that how to get that json file in abc.com via RPC call, because I have seen tutorials in which RPC calls are used to get data from its server. any help will be appreciated.
EDIT
I am trying to implement this in this StockWatcher tutorial
I changed my code slightly change to this
private static final String JSON_URL = "http://localhost/stockPrices.php?q=";
AND
private void refreshWatchList() {
if (stocks.size() == 0) {
return;
}
String url = JSON_URL;
// Append watch list stock symbols to query URL.
Iterator iter = stocks.iterator();
while (iter.hasNext()) {
url += iter.next();
if (iter.hasNext()) {
url += "+";
}
}
url = URL.encode(url);
MyJSONUtility.makeJSONRequest(url, new JSONHandler() {
#Override
public void handleJSON(JavaScriptObject obj) {
if (obj == null) {
displayError("Couldn't retrieve JSON");
return;
}
updateTable(asArrayOfStockData(obj));
}
});
}
before when I was requesting my url via RequestBuilder it was giving me an exception Couldn't retrieve JSON but now JSON is fetched and status code is 200 as I saw that in firebug but it is not updating on table. Kindly help me regarding this.
First, you need to understand the Same Origin Policy which explains how browsers implement a security model where JavaScript code running on a web page may not interact with any resource not originating from the same web site.
While GWT's HTTP client and RPC call can only fetch data from the same site where your application was loaded, you can get data from another server if it returns json in the right format. You must be interacting with a JSON service that can invoke user defined callback functions with the JSON data as argument.
Second, see How to Fetch JSON DATA

Retrieve salesforce instance URL instead of visualforce instance

From a visualforce page, I need to retrieve our organization's salesforce instance's URL, and not the visual force URL.
For example I need https://cs1.salesforce.com instead of https://c.cs1.visual.force.com
Here's what I've tried so far and the outcome I got:
Accessed the Site global variable from the VF Page:
<apex:outputText value="{!$Site.Domain}" /> returns null
Sidenote: Everything in $Site.xxx seems to return null.
From the Apex controller:
public String getSfInstance()
{
return ApexPages.currentPage().getHeaders().get('Host');
}
and
public String getSfInstance()
{
return URL.getSalesforceBaseUrl().toExternalForm();
}
returns c.cs1.visual.force.com and https://c.cs1.visual.force.com, respectively.
Question: How do I retrieve what I want: https://cs1.salesforce.com?
Here's something that I used within my Apex Trigger
System.URL.getSalesforceBaseUrl().getHost().remove('-api' );
This gives me proper URL
This is a known issue, the URL.getSalesforceBaseUrl() should provide this information but it does not. However in reality this has very limited functional impact.
Their instance and apex domains are interchangeable in the sense that requesting a URL that does not belong to one gets redirected to the other.
for example if you seek /apex/myPage from cs1.salesforce.com you'll get redirected to c.cs1... and vise versa requesting /ID from apex domain will get you redirected to instance domain (unless detail action has been overridden)
If this does not help you there is one workaround, albeit very ugly :) create a custom object to store the base url and create before insert/update trigger which will set the baseURL field to URL.getSalesforceBaseUrl().toExternalForm(). Apparently trigger is the only place on the platform where this will work (aside from execute anonymous which is not of much use). When setting up the app insert something into that table and later use SOQL to retrieve base url.
Here is an Apex property that you can throw into a Utility class that will reliably return the instance for your org. Using this, you can easily construct your organization's Salesforce URL by appending ".salesforce.com" to the Instance:
public class Utils {
// Returns the Salesforce Instance that is currently being run on,
// e.g. na12, cs5, etc.
public static String Instance {
public get {
if (Instance == null) {
//
// Possible Scenarios:
//
// (1) ion--test1--nexus.cs0.visual.force.com --- 5 parts, Instance is 2nd part
// (2) na12.salesforce.com --- 3 parts, Instance is 1st part
// (3) ion.my.salesforce.com --- 4 parts, Instance is not determinable
// Split up the hostname using the period as a delimiter
List<String> parts = System.URL.getSalesforceBaseUrl().getHost().replace('-api','').split('\\.');
if (parts.size() == 3) Instance = parts[0];
else if (parts.size() == 5) Instance = parts[1];
else Instance = null;
} return Instance;
} private set;
}
// And you can then get the Salesforce base URL like this:
public static String GetBaseUrlForInstance() {
return 'https://' + Instance + '.salesforce.com';
}
FYI: For Scenario (1), the 1st of the 4-part hostname can get really complicated, but you'll always be able to find the Instance name as the 2nd part. For those who are interested, the syntax of Scenario 1 follows this pattern:
<MyDomain>--<SandboxName>--<Namespace>.<Instance>.visual.force.com
Here you have a quite nice and small snippet, that does, what it should for VisualforcePages :-)
String sUrlRewrite = System.URL.getSalesforceBaseUrl().getHost();
// Example: c.cs7.visual.force.com
sUrlRewrite = 'https://'
+ sUrlRewrite.substring(2,6)
+ 'salesforce.com'
+ '/'
+ recordId;
// Returns: https://cs7.salesforce.com/00kM00000050jFMIAY
Use:    Url.getOrgDomainUrl().toExternalForm()
Thanks, Tim Lewis
Note behaviour changes between releases and is sensitive to My Domain settings:
#Future context returns https://na1.salesforce.com
Visualforce context returns https://na1.salesforce.com
Force.com Site context returns https://na1.salesforce.com
#Future context returns https://mydomain.my.salesforce.com
Visualforce context returns https://mydomain.my.salesforce.com
Force.com Site context returns https://mydomain.my.salesforce.com
My Domain is mandatory in new orgs effective Winter '21.
Enhanced Domains is mandatory in all orgs effective Summer '22.
// Not to be confused with Url.getSalesforceBaseUrl()
// http://na1.salesforce.com (can happen in async apex)
// https://c.na1.visual.force.com (local Visualforce Page)
// https://ns.na1.visual.force.com (packaged Visualforce Page)
// https://custom.my.salesforce.com (org has My Domain enabled)
// https://sandbox-mydomain.na1.force.com (sandbox site with My Domain...)
See also the Salesforce Identity API which attests the pod/instance endpoint.
Fix to Alex_E snippet:
String sUrlRewrite = System.URL.getSalesforceBaseUrl().getHost();
String sfBaseProtocol = System.URL.getSalesforceBaseUrl().getProtocol();
//remove namespace
integer firstDotPos = sUrlRewrite.indexOf('.');
sURlRewrite = sURlRewrite.substring(firstDotPos+1);
//replace visual.force with salesforce
sURlRewrite = sURlRewrite.replace('visual.force', 'salesforce');
sUrlRewrite = sfBaseProtocol+'://'+sURlRewrite;
serverURL = sUrlRewrite;
This works for me:
String sUrlRewrite = System.URL.getSalesforceBaseUrl().getProtocol()
+ '://' + System.URL.getSalesforceBaseUrl().getHost()
+ '/' + record.Id;
Here is something to do with regex
public String getURL() {
return String.format(
'https://{0}.salesforce.com',
new String[]{
URL.getSalesforceBaseUrl().getHost().substringAfter('.').substringBefore('.')
});
}

Resources