Refresh all data other than session data when context changes - ember.js

I'm writing a small sample application which will display some information to a user in dashboards and tables.
The application is being written using ember-cli version 0.0.37. and ember.js version 1.5.1.
I am using ember-simple-auth and ember-simple-auth-oauth2 for authentication, with a custom authenticator and authroizer to inject the client_id, client_secret and access_token into the requests where appropriate (I am fully aware that the client secret shouldn't be in a front end application, but this is a sample application for internal consumption only and not the subject of my question).
The custom authorizer also injects an o parameter into the requests, the value of which is an organisation id. The API returning data uses both the access_token and the o parameter to return data pertaining to a particular organisation. The organisation id is stored in the session.
So once I've browsed around for one organisation, I've got a dropdown component which allows me to choose another organisation. At present, this calls an action on the ApplicationRoute which updates the value in the session, and clears the store of all models that are not account or organisation, as these are used at the application level.
Code:
setSessionOrganisation: function(organisation_id) {
var self = this;
/**
* Check if its the same organisation
*/
if (this.get('session.organisation_id') == organisation_id) {
return;
}
/**
* Get the organisation for the id provided
*/
this.store.find('organisation', organisation_id).then(
function(org) {
/**
* Update the session details
*/
self.set('session.organisation_id', org.get('id'));
self.set('session.organisation_name', org.get('name'));
/**
* Get all types
*/
var store = self.container.lookup('store:main');
var types = [];
var typeMaps = store.typeMaps;
$.each(typeMaps, function(key) {
if (typeMaps.hasOwnProperty(key)) {
var type = typeMaps[key].type.typeKey;
if (type != 'account' && type != 'organisation'){
types.push(typeMaps[key].type.typeKey);
}
}
});
/**
* Clear data for types
*/
for (var i = 0; i < types.length; i++) {
store.unloadAll(types[i]);
};
});
}
I feel the code above is a bit hackish, but I've not found another way to return the model types currently in the store.
When this action is called, and the data has been flushed, I would like to then refresh/reload the current route. Some of the routes will be dynamic, and using the object ids as the dynamic segment. These routes will need to redirect to their list routes. Other routes will carry on lazily loading data when they are navigated to.
So my questions are:
Is there a better way to clear the store data than I have done above?
How can I trigger a route reload?
How can I redirect to the parent route if in a route with a dynamic segment?
As a bonus question, are there any dangers in unloading all the data for views/routes that are presently not being displayed?
Thanks!

I've come to a resolution that satisfies my criteria, with great help from #marcoow
As per the comments on the question, we discussed reloading the entire application to refresh the data store cache. While this approach does work, there was a flicker while the browser completely reloaded the page, and I would prefer to only reload the data and have the application sort itself out. This flicker was also present if App.reset() was called.
I explored other options and found the Ember.Route.refresh() method. This appears to cause the nested routes to reload their models when called from the application route.
In my application I have extended the store in an initializer, so that I can call a function which unloads all records from the store, for every type of model in the store, but also provide a list of model names to exclude:
app/initializers/custom-store.js:
import DS from 'ember-data';
var CustomStore = DS.Store.extend({
/**
* Extend the functionality in the store to unload all instances of each type
* of data except those passed
*
* #param {Array} exclusions List of model type names to exclude from data
* unload
*/
unloadAllExcept: function(exclusions) {
var typeMaps = this.get('typeMaps');
for (var type in typeMaps) {
if (typeMaps.hasOwnProperty(type)) {
var typeKey = (typeMaps[type].type && typeMaps[type].type.typeKey) ? typeMaps[type].type.typeKey : false;
if (typeKey && exclusions.indexOf(typeKey) === -1) {
this.unloadAll(typeKey);
}
}
}
}
});
export
default {
after: 'store',
name: 'custom-store',
initialize: function(container /*, app*/ ) {
container.unregister('store:main');
container.register('store:main', CustomStore);
}
};
This is called from app/routes/application.js in the setSessionOrganisation Action:
setSessionOrganisation: function(organisation_id) {
var _this = this;
/**
* Check if its the same organisation
*/
if (this.get('session.organisation_id') === organisation_id) {
return;
}
/**
* Get the organisation for the id provided
*/
this.store.find('organisation', organisation_id)
.then(
function(org) {
/**
* Update the session details
*/
_this.get('session')
.set('organisation_id', org.get('id'));
_this.get('session')
.set('organisation_name', org.get('name'));
/**
* Clean the local data cache of all data pertaining to
* the old organisation
*/
_this.store.unloadAllExcept(['account', 'organisation']);
/**
* refresh the application route (and subsequently all
* child routes)
*/
_this.refresh();
});
}
As a footnote, this solution does not satisfy the third question I asked, but I have realised that question is a separate issue to do with handling the response from the API, and must be dealt with during the model hook.

Related

How to check "_custom_access" for whole website and not module/path?

example:
path: '/example'
defaults:
_controller: '\Drupal\example\Controller\ExampleController::content'
requirements:
_custom_access: '\Drupal\example\Controller\ExampleController::access'
This custom_access checker will be executed only when someone call mywebsite.domain/example.
But I want that this controller check all urls, run independent of path.
How can I create an independent custom access controller?
The idea for preventing routing access to a very low level (Kernel one to be precise), is to register a EventSubscriber service, subscribing to the REQUEST KernelEvent.
First of all, you will need to create a new custom module.
Once done, you will be able to create a new my_module.services.yml file which will declare a new EventSubscriber
services:
my_module.subscriber:
class: Drupal\my_module\EventSubscriber\MyCustomSubscriber
tags:
- { name: event_subscriber}
Then, create the class referenced above in my_module/src/EventSubscriber/MyCustomSubscriber.php.
Here is a tiny example which checks if the current user is logged-in before accessing any page, otherwise redirect on the login page. This following code is not complete (see the last reference for a better explanation) but it shows you the basics (subscription to the event, dependency injection, event redirection, ...)
<?php
namespace Drupal\my_module\EventSubscriber;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class MyCustomSubscriber implements EventSubscriberInterface {
/**
* The current route match.
*
* #var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* Class constructor.
*
* #param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The current route match.
*/
public function __construct(RouteMatchInterface $route_match) {
$this->routeMatch = $route_match;
}
/**
* {#inheritdoc}
*/
static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = ['isLoggedIn'];
return $events;
}
/**
* It verify the page is requested by a logged in user, otherwise prevent access.
*
* #param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* A response for a request.
*/
public function isLoggedIn(GetResponseEvent $event) {
$route_name = $this->routeMatch->getRouteName();
// Don't run any assertion on the login page, to prevent any loop redirect.
// If intend to be used on a production project, please #see
// https://www.lucius.digital/en/blog/drupal-8-development-always-redirect-all-logged-out-visitors-to-the-login-page for a better implementation.
if ($route_name === 'user.login') {
return;
}
if (\Drupal::currentUser()->isAnonymous()) {
$dest = Url::fromRoute('user.login')->toString();
$event->setResponse(RedirectResponse::create($dest));
}
}
}
To go further, you may read those explanations of registering event subscribers & some use case:
Responding to Events in Drupal 8
How to Register an Event Subscriber in Drupal 8
Always redirect all logged out visitors to the login page
I hope it will help you.

How can you add dynamic ColdBox routes after configuration loads?

I'd like to create some routes based on data in a database table. I assume this will need to be done after the framework initializes so what I've done so far is created a new interceptor that runs on: afterConfigurationLoad.
My interceptor receives a service from my model which will query the data I need to create the routes. However, I'm not sure how to add routes at this point. When I try and call addRoute() directly I get an error Variable ADDROUTE is undefined. so I guess addRoute() doesn't exist in the framework Supertype.
Here's some sample code from my interceptor:
component {
property name="myService" inject="myService"; // injects my model which will get some data
/**
* afterConfigurationLoad
* Runs after the framework configucation loads
*/
void function afterConfigurationLoad( event, interceptData, buffer, rc, prc ){
// get some data which will be converted into routes
var myDataList = myService.list();
// loop through the data
for ( var data in myDataList ) {
// add our route. NOTE: I'll also want to populate the prc scope with a value
addRoute( pattern="/#data.slug#", handler="data", action="index" );
}
}
}
Edit 1: Using ColdBox v5.1.1
Update 1:
Brad's answer put me on the right track. I was unable to inject the router in my interceptor because it generated an error. However, I was able to get an instance of the router from within the function using getInstance( "router#coldbox" ) and then call route() methods as needed to create my routes.
Important: Routes by default are appended to the routing table. You will likely need to prepend the routes if you want them to work.
Here's an updated version of the code which will work:
Solution 1:
component {
property name="myService" inject="myService"; // injects my model which will get some data
/**
* afterConfigurationLoad
* Runs after the framework configucation loads
*/
void function afterConfigurationLoad( event, interceptData, buffer, rc, prc ){
// instance the router
var router = getInstance( "router#coldbox" );
// get some data which will be converted into routes
var myDataList = myService.list();
// loop through the data
for ( var data in myDataList ) {
// prepend our route
router.route( "/#data.slug#" ).prcAppend( { id : #data.id# } ).to( "data.index" ).prepend();
}
}
}
Additionally, there may be a simpler way to solve this problem by simply injecting the model service into the `config/router.cfc' configuration file and adding any dynamic routes that way.
Solution 2:
component {
property name="myService" inject="myService"; // inject the model
function configure() {
setFullRewrites( true );
// get the data I need from the model for dynamic routes
var myDataList = myService.list();
// loop through the data
for ( var data in myDataList ) {
// add the dynamic route
router.route( "/#data.slug#" ).prcAppend( { id : #data.id# } ).to( "data.index" );
}
route( ":handler/:action?" ).end();
}
}
Solution 3:
As it turns out, when the framework initializes, the interceptors get loaded first so not all dependencies will be available yet. Brad suggested using the provider namespace in the property which should also be an acceptable solution.
component {
property name="myService" inject="myService"; // injects my model which will get some data
property name="router" inject="provider:router#coldbox";
/**
* afterConfigurationLoad
* Runs after the framework configucation loads
*/
void function afterConfigurationLoad( event, interceptData, buffer, rc, prc ){
// get some data which will be converted into routes
var myDataList = myService.list();
// loop through the data
for ( var data in myDataList ) {
// prepend our route
router.route( "/#data.slug#" ).prcAppend( { id : #data.id# } ).to( "data.index" ).prepend();
}
}
}
Inject the ColdBox Router
property name='router' inject='router#coldbox';
and call its methods detailed here in the API docs:
http://apidocs.ortussolutions.com/coldbox/5.2.0/index.html?coldbox/system/web/routing/Router.html
The addRoute() method is part of that CFC.

Is Qooxdoo protected against XSS

I'm looking for informations about security on Qooxdoo.
I want to check my app vs OWASP top 10
A point to review is the XSS OWASP A3 XSS
How can I be sure that Qooxdoo is secure against XSS attacks ?
Does Qooxdoo use some sanitizer tools ?
SOLVED
A short answer from all the discussions. Yes Qooxdoo is XSS safe. By default, no javascript value in any field will be executed.
But, if you use rich=true, you have to check input/output
A common XSS attack vector are situations where an attacker somehow inputs JS code into a web application, such that this code then shows up in the DOM of a webpage and gets thus activated.
To protect against this kind of XSS, you must make sure that the backend server does not send user generated (un-cleaned) html towards the browser ... (this has nothing to do with qooxdoo).
That said, the regular qooxdoo widgets do not in general display data as html so you are reasonably safe even without a clever server. The exception is the qx.ui.basic.Label widget and its descendants. The Label widget has the ability to display HTML directly if you set the rich property. The rich property is set to false by default, but if you enable it, you have to make sure you don't display 'dangerous' html content.
Only very few (non essential) qooxdoo widgets allow you to insert HTML code into the DOM. In these instance you have to take care to sanitize the data. The widgets in question are:
qx.ui.embed.Html
qx.ui.table.cellrenderer.Html
qx.ui.progressive.renderer.table.cell.Html
qx.ui.virtual.cell.Html
qx.ui.virtual.layer.HtmlCell
qx.ui.virtual.layer.HtmlCellSpan
If you do use qx.html.* and qx.bom.*and qx.dom.* objects to work with the DOM directly, you are beyond the reach of qooxoo and have to take care to act accordingly.
Another important attack vector are authentication cookies. Most of the attacks work by getting the browser to send a request together with the cookie to its server without the user being aware it.
Qooxdoo itself does not require you to use cookies at all. Since qooxdoo applications by design run in a single browser window, you can work without ever using cookies. An easy way of implementing something like this is to have a 'server access singleton' which takes care of all the communication with the backend and supplies the access token in a special header added to every request.
The code below could serve as a guide ... for the cookie problem.
qx.Class.define('myapp.Server', {
extend : qx.io.remote.Rpc,
type : "singleton",
construct : function() {
this.base(arguments);
this.set({
timeout : 60000,
url : 'QX-JSON-RPC/',
serviceName : 'default'
});
},
properties: {
sessionCookie: {
init: null,
nullable: true
}
},
members : {
/**
* override the request creation, to add our 'cookie' header
*/
createRequest: function() {
var req = this.base(arguments);
var cookie = this.getSessionCookie();
if (cookie){
req.setRequestHeader('X-Session-Cookie',this.getSessionCookie());
}
return req;
}
}
});
and if you provide a login popup window in myapp.uiLogin you could replace
the standard callAsync by adding the following to popup a login window if the backend is unhappy with your request.
/**
* A asyncCall handler which tries to
* login in the case of a permission exception.
*
* #param handler {Function} the callback function.
* #param methodName {String} the name of the method to call.
* #return {var} the method call reference.
*/
callAsync : function(handler, methodName) {
var origArguments = arguments;
var origThis = this;
var origHandler = handler;
var that = this;
var superHandler = function(ret, exc, id) {
if (exc && exc.code == 6) {
var login = myapp.uiLogin.getInstance();
login.addListenerOnce('login', function(e) {
var ret = e.getData();
that.setSessionCookie(ret.sessionCookie);
origArguments.callee.base.apply(origThis, origArguments);
});
login.open();
return;
}
origHandler(ret, exc, id);
};
if (methodName != 'login') {
arguments[0] = superHandler;
}
arguments.callee.base.apply(this, arguments);
},
take a look at the CallBackery application to see how this works in a real application.

Loopback include unrelated lists

In Loopback it is easy to include relational objects when querying for data. For example, one can include all the comments that belong to a blog post in a single call using the include filter.
But in my case I want to get data that doesn't have a relation.
I have a User Detail page. On that page a user can choose a username and there's also a dropdown list where a user can choose from what country he is.
So from the client side I do something like:
Country.find().$promise.then(function(countryData) {
$scope.countries = countryData;
});
Player.find().$promise.then(function(playerData) {
$scope.player = playerData;
}
But what if I get more lists that I want to fill? Like, city, state, colors etc.
Then I'd have to make a lot of separate calls.
Is there a way to include all this data in one call, eventhough they have no relation? Something like this:
Player.find({ filter: { include: ["countries", "colors"] } }).$promise.then(function(data) {
// some stuff
}
You may want to try using the Where filter as documented here
An example of this for querying two specific things would be:
Post.find({where: {and: [{title: 'My Post'}, {content: 'Hello'}]}},
function (err, posts) {
...
});
You could create a remote method on one of your models that makes the calls internally and packages them back up for you.
Use some promise library if not using ES6 to wait for all and then return
Model.getAll = function(next) {
var promises = [];
promises.push(Model.app.models.Country.find());
promises.push(Model.app.models.Player.find());
promises.push(Model.app.models.Color.find());
Promise.all(promises)
.then(function(results) {
next(results);
});
}
/**
Register your remote method here
*/
You could create a remote method on one of your models that makes the calls internally and packages them back up for you.
Use some promise library if not using ES6 to wait for all and then return
Model.getAll = function(next) {
var promises = [];
promises.push(Model.app.models.Country.find());
promises.push(Model.app.models.Player.find());
promises.push(Model.app.models.Color.find());
Promise.all(promises)
.then(function(results) {
next(results);
});
}
/**
Register your remote method here
*/
I have problem and try with this solution but i get error "Failed with multiple errors, see details for more information.". It seems like there is bug on Loopback while using promise.all

Adding item to filtered result from ember-data

I have a DS.Store which uses the DS.RESTAdapter and a ChatMessage object defined as such:
App.ChatMessage = DS.Model.extend({
contents: DS.attr('string'),
roomId: DS.attr('string')
});
Note that a chat message exists in a room (not shown for simplicity), so in my chat messages controller (which extends Ember.ArrayController) I only want to load messages for the room the user is currently in:
loadMessages: function(){
var room_id = App.getPath("current_room.id");
this.set("content", App.store.find(App.ChatMessage, {room_id: room_id});
}
This sets the content to a DS.AdapterPopulatedModelArray and my view happily displays all the returned chat messages in an {{#each}} block.
Now it comes to adding a new message, I have the following in the same controller:
postMessage: function(contents) {
var room_id = App.getPath("current_room.id");
App.store.createRecord(App.ChatMessage, {
contents: contents,
room_id: room_id
});
App.store.commit();
}
This initiates an ajax request to save the message on the server, all good so far, but it doesn't update the view. This pretty much makes sense as it's a filtered result and if I remove the room_id filter on App.store.find then it updates as expected.
Trying this.pushObject(message) with the message record returned from App.store.createRecord raises an error.
How do I manually add the item to the results? There doesn't seem to be a way as far as I can tell as both DS.AdapterPopulatedModelArray and DS.FilteredModelArray are immutable.
so couple of thoughts:
(reference: https://github.com/emberjs/data/issues/190)
how to listen for new records in the datastore
a normal Model.find()/findQuery() will return you an AdapterPopulatedModelArray, but that array will stand on its own... it wont know that anything new has been loaded into the database
a Model.find() with no params (or store.findAll()) will return you ALL records a FilteredModelArray, and ember-data will "register" it into a list, and any new records loaded into the database will be added to this array.
calling Model.filter(func) will give you back a FilteredModelArray, which is also registered with the store... and any new records in the store will cause ember-data to "updateModelArrays", meaning it will call your filter function with the new record, and if you return true, then it will stick it into your existing array.
SO WHAT I ENDED UP DOING: was immediately after creating the store, I call store.findAll(), which gives me back an array of all models for a type... and I attach that to the store... then anywhere else in the code, I can addArrayObservers to those lists.. something like:
App.MyModel = DS.Model.extend()
App.store = DS.Store.create()
App.store.allMyModels = App.store.findAll(App.MyModel)
//some other place in the app... a list controller perhaps
App.store.allMyModels.addArrayObserver({
arrayWillChange: function(arr, start, removeCount, addCount) {}
arrayDidChange: function(arr, start, removeCount, addCount) {}
})
how to push a model into one of those "immutable" arrays:
First to note: all Ember-Data Model instances (records) have a clientId property... which is a unique integer that identifies the model in the datastore cache whether or not it has a real server-id yet (example: right after doing a Model.createRecord).
so the AdapterPopulatedModelArray itself has a "content" property... which is an array of these clientId's... and when you iterate over the AdapterPopulatedModelArray, the iterator loops over these clientId's and hands you back the full model instances (records) that map to each clientId.
SO WHAT I HAVE DONE
(this doesn't mean it's "right"!) is to watch those findAll arrays, and push new clientId's into the content property of the AdapterPopulatedModelArray... SOMETHING LIKE:
arrayDidChange:function(arr, start, removeCount, addCount){
if (addCount == 0) {return;} //only care about adds right now... not removes...
arr.slice(start, start+addCount).forEach(function(item) {
//push clientId of this item into AdapterPopulatedModelArray content list
self.getPath('list.content').pushObject(item.get('clientId'));
});
}
what I can say is: "its working for me" :) will it break on the next ember-data update? totally possible
For those still struggling with this, you can get yourself a dynamic DS.FilteredArray instead of a static DS.AdapterPopulatedRecordArray by using the store.filter method. It takes 3 parameters: type, query and finally a filter callback.
loadMessages: function() {
var self = this,
room_id = App.getPath('current_room.id');
this.store.filter(App.ChatMessage, {room_id: room_id}, function (msg) {
return msg.get('roomId') === room_id;
})
// set content only after promise has resolved
.then(function (messages) {
self.set('content', messages);
});
}
You could also do this in the model hook without the extra clutter, because the model hook will accept a promise directly:
model: function() {
var self = this,
room_id = App.getPath("current_room.id");
return this.store.filter(App.ChatMessage, {room_id: room_id}, function (msg) {
return msg.get('roomId') === room_id;
});
}
My reading of the source (DS.Store.find) shows that what you'd actually be receiving in this instance is an AdapterPopulatedModelArray. A FilteredModelArray would auto-update as you create records. There are passing tests for this behaviour.
As of ember.data 1.13 store.filter was marked for removal, see the following ember blog post.
The feature was made available as a mixin. The GitHub page contains the following note
We recommend that you refactor away from using this addon. Below is a short guide for the three filter use scenarios and how to best refactor each.
Why? Simply put, it's far more performant (and not a memory leak) for you to manage filtering yourself via a specialized computed property tailored specifically for your needs