Code Monkey home page Code Monkey logo

ember-can's Introduction

Ember-can

Ember Observer Travis CI Status


Simple authorisation addon for Ember.

Installation

Install this addon via ember-cli:

ember install ember-can

Compatibility

  • Ember.js v3.20 or above
  • Ember CLI v3.20 or above
  • Node.js v18 or above

Quick Example

You want to conditionally allow creating a new blog post:

{{#if (can "create post")}}
  Type post content here...
{{else}}
  You can't write a new post!
{{/if}}

We define an ability for the Post model in /app/abilities/post.js:

// app/abilities/post.js

import { inject as service } from '@ember/service';
import { Ability } from 'ember-can';

export default class PostAbility extends Ability {
  @service session;

  @computed('session.currentUser')
  get user() {
    return this.session.currentUser;
  }

  @readOnly('user.isAdmin') canCreate;
  get canCreate() {
    return this.user.isAdmin;
  }
}

We can also re-use the same ability to check if a user has access to a route:

// app/routes/posts/new.js

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default class NewPostRoute extends Route {
  @service abilities;

  beforeModel(transition) {
    let result = super.beforeModel(...arguments);

    if (this.abilities.cannot('create post')) {
      transition.abort();
      return this.transitionTo('index');
    }

    return result;
  }
}

And we can also check the permission before firing action:

import Component from '@ember/component';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';

export default class CreatePostComponent extends Component {
  @service abilities;

  @action
  createPost() {
    if (this.abilities.can('create post', this.post)) {
      // create post!
    }
  }
});

Helpers

can

The can helper is meant to be used with {{if}} and {{unless}} to protect a block (but can be used anywhere in the template).

{{can "doSth in myModel" model extraProperties}}
  • "doSth in myModel" - The first parameter is a string which is used to find the ability class call the appropriate property (see Looking up abilities).

  • model - The second parameter is an optional model object which will be given to the ability to check permissions.

  • extraProperties - The third parameter are extra properties which will be assigned to the ability

As activities are standard Ember objects and computed properties if anything changes then the view will automatically update accordingly.

Example

{{#if (can "edit post" post)}}
  ...
{{else}}
  ...
{{/if}}

As it's a sub-expression, you can use it anywhere a helper can be used. For example to give a div a class based on an ability you can use an inline if:

<div class={{if (can "edit post" post) "is-editable"}}>

</div>

cannot

Cannot helper is a negation of can helper with the same API.

{{cannot "doSth in myModel" model extraProperties}}

Abilities

An ability class protects an individual model which is available in the ability as model.

Please note that all abilites names have to be in singular form

// app/abilities/post.js

import { computed } from '@ember/object';
import { Ability } from 'ember-can';

export default class PostAbility extends Ability {
  // only admins can write a post
  @computed('user.isAdmin')
  get canWrite() {
    return this.user.isAdmin;
  }

  // only the person who wrote a post can edit it
  @computed('user.id', 'model.author')
  get canEdit() {
    return this.user.id === this.model.author;
  }
}

// Usage:
// {{if (can "write post" post) "true" "false"}}
// {{if (can "edit post" post user=author) "true" "false"}}

Additional attributes

If you need more than a single resource in an ability, you can pass them additional attributes.

You can do this in the helpers, for example this will set the model to project as usual, but also member as a bound property.

{{#if (can "remove member from project" project member=member)}}
  ...
{{/if}}

Similarly using abilities service you can pass additional attributes after or instead of the resource:

this.abilities.can('edit post', post, { author: bob });
this.abilities.cannot('write post', null, { project: project });

These will set author and project on the ability respectively so you can use them in the checks.

Looking up abilities

In the example above we said {{#if (can "write post")}}, how do we find the ability class & know which property to use for that?

First we chop off the last word as the resource type which is looked up via the container.

The ability file can either be looked up in the top level /app/abilities directory, or via pod structure.

Then for the ability name we remove some basic stopwords (of, for in) at the end, prepend with "can" and camelCase it all.

For example:

String property resource pod
write post canWrite /abilities/post.js app/pods/post/ability.js
manage members in project canManageMembers /abilities/project.js app/pods/project/ability.js
view profile for user canViewProfile /abilities/user.js app/pods/user/ability.js

Current stopwords which are ignored are:

  • for
  • from
  • in
  • of
  • to
  • on

Custom Ability Lookup

The default lookup is a bit "clever"/"cute" for some people's tastes, so you can override this if you choose.

Simply extend the default AbilitiesService in app/services/abilities.js and override parse.

parse takes the ability string eg "manage members in projects" and should return an object with propertyName and abilityName.

For example, to use the format "person.canEdit" instead of the default "edit person" you could do the following:

// app/services/abilities.js
import Service from 'ember-can/services/abilities';

export default class AbilitiesService extends Service {
  parse(str) {
    let [abilityName, propertyName] = str.split('.');
    return { propertyName, abilityName };
  }
};

You can also modify the property prefix by overriding parseProperty in our ability file:

// app/abilities/feature.js
import { Ability } from 'ember-can';
import { camelize } from '@ember/string';

export default class FeatureAbility extends Ability {
  parseProperty(propertyName) {
    return camelize(`is-${propertyName}`);
  },
};

Injecting the user

How does the ability know who's logged in? This depends on how you implement it in your app!

If you're using an Ember.Service as your session, you can just inject it into the ability:

// app/abilities/foo.js
import { Ability } from 'ember-can';
import { inject as service } from '@ember/service';

export default class FooAbility extends Ability {
  @service session;
}

The ability classes will now have access to session which can then be used to check if the user is logged in etc...

Components & computed properties

In a component, you may want to expose abilities as computed properties so that you can bind to them in your templates.

import Component from '@ember/component';
import { inject as service } from '@ember/service';
import { computed } from '@ember/object';

export default class MyComponent extends Component {
  @service abilities; // inject abilities service

  post = null; // received from higher template

  @computed('post')
  get ability() {
    return this.abilities.abilityFor('post', this.post /*, customProperties */);
  }
}

// Template:
// {{if ability.canWrite "true" "false"}}

Accessing abilities within an Ember engine

If you're using engines and you want to access an ability within it, you will need it to be present in your Engine’s namespace. This is accomplished by doing what is called a "re-export":

//my-engine/addon/abilities/foo-bar.js
export { default } from 'my-app/abilities/foo-bar';

Upgrade guide

See UPGRADING.md for more details.

Testing

Make sure that you've either ember install-ed this addon, or run the addon blueprint via ember g ember-can. This is an important step that teaches the test resolver how to resolve abilities from the file structure.

Unit testing abilities

An ability unit test will be created each time you generate a new ability via ember g ability <name>. The package currently supports generating QUnit and Mocha style tests.

Integration testing in your app

For testing you should not need to specify anything explicitly. Anyway, you can stub the service following the official EmberJS guide if needed.

Development

Installation

  • git clone https://github.com/minutebase/ember-can.git
  • cd ember-can
  • npm install

Linting

  • npm run lint:hbs
  • npm run lint:js
  • npm run lint:js -- --fix

Running tests

  • ember test – Runs the test suite on the current Ember version
  • ember test --server – Runs the test suite in "watch mode"
  • ember try:each – Runs the test suite against multiple Ember versions

Running the dummy application

For more information on using ember-cli, visit https://ember-cli.com/.

Contributing

See the Contributing guide for details.

License

This version of the package is available as open source under the terms of the MIT License.

ember-can's People

Contributors

blimmer avatar charlesfries avatar cibernox avatar dependabot[bot] avatar ember-tomster avatar esbanarango avatar exelord avatar gorzas avatar happycollision avatar jbandura avatar jembezmamy avatar joshsmith avatar karlguillotte avatar kategengler avatar knownasilya avatar loganrosen avatar makepanic avatar miguelcobain avatar mileszim avatar minichate avatar mydea avatar ondrejsevcik avatar remi avatar rlivsey avatar rwjblue avatar sukima avatar vluoto avatar xomaczar avatar zachgarwood avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ember-can's Issues

a little help with the user injection...

so I followed along with the part about injecting the user.. I am using simple auth and torii.. in my templates I can use session.currentUser to get the user.. in the ability I did this to experiment..

console.log(session.currentUser.name);

and it resulted in:
ReferenceError: session is not defined

I am sure I am doing something wrong.. any help, suggestions, tips etc..

thanks!!
Gary

How is this different from computed properties?

Hi guys, how does this addon differ from custom computed properties on model? So instead of having a dedicated file abilities.js I can place all my permission checks inside model as canXYZ computed property.

Thank you for explanation.

Update docs to suggest server-side implementation(s)

We've seen a couple of issues asking about how to validate roles on the server side. Though I don't think this add-on should be opinionated on the server-side implementation, it could be useful to suggest how to implement this on the server (can-can-can, can-can for node, home grown, etc.)

if-can helper failing

Hi. This project has appeared at just the right time. Good work.

I'm attempting to get this to work, and I've installed from master.

I've setup a single ability in abilities/game.js with a single method canCreate which returns true.

In my navigation, I've used the {{if-can}} helper like this:

{{#if-can 'create game'}}
  ...
{{/if-can}}

But I'm getting an error:

"Assertion Failed: Cannot delegate set('_ability-1', <game@ability:game::ember643>) 
to the 'content' property of object proxy <game@controller:application::ember644>: 
its 'content' is undefined."

From line 42 of the if-can helper:

        set(context, id, ability);

Any ideas what I'm missing?

0.7.1 resolver breaks 1.13.x app

Getting this issue with 0.7.1 on 1.13.x app:

not ok 60 PhantomJS 2.0 - TestLoader Failures cs/tests/acceptance/error-test: could not be loaded
    ---
        message: >
            Could not find module `ember-resolver` imported from `cs/initializers/setup-ember-can`
        stack: >
                at missingModule (http://localhost:7357/assets/vendor.js:177:93)
                at findModule (http://localhost:7357/assets/vendor.js:192:30)
                at reify (http://localhost:7357/assets/vendor.js:128:32)
                at build (http://localhost:7357/assets/vendor.js:146:28)
                at findModule (http://localhost:7357/assets/vendor.js:194:14)
                at requireModule (http://localhost:7357/assets/vendor.js:181:22)
                at http://localhost:7357/assets/vendor.js:70968:33
                at forEach ([native code])
                at default (http://localhost:7357/assets/vendor.js:70965:19)
                at http://localhost:7357/assets/cs.js:132:41
                at exports (http://localhost:7357/assets/vendor.js:96:39)
                at build (http://localhost:7357/assets/vendor.js:146:17)
                at findModule (http://localhost:7357/assets/vendor.js:194:14)
                at reify (http://localhost:7357/assets/vendor.js:128:32)
                at build (http://localhost:7357/assets/vendor.js:146:28)
                at findModule (http://localhost:7357/assets/vendor.js:194:14)
                at reify (http://localhost:7357/assets/vendor.js:128:32)
                at build (http://localhost:7357/assets/vendor.js:146:28)
                at findModule (http://localhost:7357/assets/vendor.js:194:14)
                at requireModule (http://localhost:7357/assets/vendor.js:181:22)
                at require (http://localhost:7357/assets/test-loader.js:60:16)
                at loadModules (http://localhost:7357/assets/test-loader.js:51:25)
                at load (http://localhost:7357/assets/test-loader.js:82:35)
                at http://localhost:7357/assets/test-support.js:15143:20
        Log: |

Question about the necessity of having an Ember.Object as a resource

Hi,

I’ve come across an unexpected behavior today and I wanted to investigate a little more about the cause. We ditched ember-data in one of our projects and, as a result, my route models aren’t instances of Ember.Object. This seems to be incompatible with the current CanMixin helper can.

Here is a dumbed-down example to explain my issue:

// some-route.js
export default Ember.Route.extend(CanMixin, {
  model() {
    return {foo: 'bar'};
  },

  afterModel(model) {
    if (!this.can('edit stuff', model)) {
      window.console.log('Cannot edit stuff, should redirect you.');
    }
  }
});

// abilities/stuff.js
export default Ability.extend({
  canEdit: computed(function() {
    return this.get('model.foo') === 'bar';
  })
});

In this example, this.get('model') will always be null. Which is weird since this is how I've used this library in the past, and it's working when using the Handlebars helper.

I found out that the problem is related to this processing of the arguments which validates if the resource passed is an Ember.Object. If not, it sets resource to null and populate the properties instead.

As for now, the solution I’m using is to simply always pass some properties like this:

this.cannot('edit stuff', model, {});

I was wondering what was the use-case of switching these arguments. So, I have 2 questions:

  • Why do resource absolutely need to be an Ember.Object?
  • What could be the outcome of my solution since resource is not an Ember.Object?

Thanks for enlightening me 😊

Keep on the good work!

Computed properties + additional attributes

Hi,

Is there a way to do computed.ability with additional attributes.
For a concrete example, how can you write something like this:

{{#if (can "remove member from project" project member=member)}}
  ...
{{/if}}

using computed.ability.

Features exist independently but I cannot find documentation where both are used at the same time.

Thanks

Hacking?

Hi!

Does it support integration with, say, CanCanCan (Rails) on the sever, or anything else that can confirm the authorization rule before performing performing the actual action? How to deal with that to avoid someone hacking the javascript app to gain access to other abilities while keeping the rules DRY?

Update blueprints to ES6 classes

What do you think about updating the blueprints to use ES6 classes? Or is it soon?

So instead generating the old syntax, the blueprint generates something like this:

import { Ability } from 'ember-can';

export default class extends Ability {
}

Maybe we can add an option so the developer can choose between the version they prefer, something like it's been doing with the blueprints of ember source.

blueprint: Ember 3.0 testing structure

The current generator for a new ability creates an accompanying test in the old fashion.

For example, running ember generate ability post would create the following test file:

// tests/unit/abilities/post-test.js

import { moduleFor, test } from 'ember-qunit';

moduleFor('ability:post', 'Unit | Ability | post', {
  // Specify the other units that are required for this test.
  // needs: ['service:foo']
});

// Replace this with your real tests.
test('it exists', function(assert) {
  const ability = this.subject();
  assert.ok(ability);
});

The new testing style in Ember 3.0 supports a cleaner structure, which would translate to this:

import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';

module('Unit | Ability | post', function(hooks) {
  setupTest(hooks);

  // Replace this with your real tests.
  test('it exists', function(assert) {
    const ability = this.owner.lookup('ability:post');
    assert.ok(ability);
  });
});

It would be nice for that to be the test that is generated for Ember 3.0+ apps.

I'll have a look into this myself after posting this issue, but I figured if it was too complex to take on, I'd at least leave this issue here for someone else to see.

no example app

Hi,

is there an example app with tests? I wanted to see how to unit test abilities. Unfortunately ember g ability-test foo doesn't create a test case other than 'it exists'.

Tests fail on Ember 2.10++

not ok 12 PhantomJS 1.9 - Acceptance | IfCanTest: changing the resource should update the helper contents
    ---
        actual: >
            false
        expected: >
            true
        stack: >
                at http://localhost:7357/assets/test-support.js:3881
                at http://localhost:7357/assets/test-support.js:4611
                at http://localhost:7357/assets/vendor.js:47885
                at adapterDispatch (http://localhost:7357/assets/vendor.js:49020)
                at dispatchError (http://localhost:7357/assets/vendor.js:27441)
                at onerrorDefault (http://localhost:7357/assets/vendor.js:40944)
                at http://localhost:7357/assets/vendor.js:68388
                at http://localhost:7357/assets/vendor.js:69272
                at http://localhost:7357/assets/vendor.js:10894
                at http://localhost:7357/assets/vendor.js:10962
                at http://localhost:7357/assets/vendor.js:11086
                at http://localhost:7357/assets/vendor.js:11156
                at http://localhost:7357/assets/vendor.js:11279
                at run (http://localhost:7357/assets/vendor.js:32069)
                at http://localhost:7357/assets/vendor.js:48780
        message: >
            TypeError: 'undefined' is not a function (evaluating 'this.get('helper').compute(params, hash)')
        Log: |
            { type: 'error',
              text: '\'TypeError: \\\'undefined\\\' is not a function (evaluating \\\'this.get(\\\'helper\\\').compute(params, hash)\\\')\\n    at compute 

https://travis-ci.org/minutebase/ember-can/builds/193584199

Related to glimmer2?

provide `ability` computed macro as a named export

Now that we have the new Ember javascript modules API I think it would make sense to export the ability computed macro as its own export.

We could then use it like:

import Controller from '@ember/controller';
import { ability } from 'ember-can';

export default Controller.extend({
  // looks up the "post" ability and sets the model as the controller's "content" property
  postAbility: ability('post', 'content')
});

Another problem the current API has is that it is incompatible with ember's modules API unless you do some aliasing because you now import computed using:

import { computed } from '@ember/object';

which collides with

import { computed } from 'ember-can';

What I am doing at the moment is:

import { computed } from '@ember/object';
import { computed as can } from 'ember-can';

// and then use it like
someAbility: can.ability('post'),
someComputedProperty: computed(function() {
  return value;
})

It wouldn't break existing codebases since we would still be exporting computed as we are now.
We would also update the README to use this new API.

Is anyone opposed to this change?

Update blueprints to ES6

The problem:

Currently whenever you generate the tests using blueprints, it'll use the old es5 syntax:

//...
var ability = this.subject();
//...

This causes errors in eslint and has to be fixed manually every time.

Add generator

We should have a generator which creates an ability file & corresponding test.

Upgrade to ember-cli-babel@^6.0.0

This addon is outdated with the recent Ember versions

[email protected]:
  version "0.8.4"
  resolved "https://registry.yarnpkg.com/ember-can/-/ember-can-0.8.4.tgz#196f2492b47b4cf65e9842aafe4b5a9fb9092011"
  dependencies:
    ember-cli-babel "^5.1.5"
$> ember -v

ember-cli: 2.14.1
node: 8.2.1
os: darwin x64

Do I have to hardcode who has access?

I am thinking of rewriting my php app to full stack js using SANE but we do something there that I dont know how to do with this type of techology, I see here that you define manually who has access to what in the app. what about if I have a configuration on the app for the profiles? I mean I am admin, I create a profile called canmakesomething, and via the ember app I select what modules it can access, so basically the ember app just needs to ask the server for the access rights of the profile so it can then use the abilities on the client side. Did I make my self clear? it would be good to configure the abilities from a json file or something like that.

Example of a component test using `can()` helper?

This is related to #8, but different. I worked through doing unit tests of our abilities and seem to have acceptance tests (with full use of initializers and the container) running smoothly, but I'm hitting some snags when trying to run a contained unit test of a component.

I hit this one first:

Error: Assertion Failed: A helper named 'can' could not be found
    at new Error (native)
    at Error.EmberError (http://localhost:7357/assets/vendor.js:22557:21)
    at Object.Ember.default.assert (http://localhost:7357/assets/vendor.js:15711:13)
    at subexpr (http://localhost:7357/assets/vendor.js:17935:11)
    at Object.render (http://localhost:7357/assets/backend.js:1956:44)
    at renderHTMLBarsTemplate (http://localhost:7357/assets/vendor.js:18388:21)
    at renderView (http://localhost:7357/assets/vendor.js:18355:16)
    at renderView (http://localhost:7357/assets/vendor.js:47271:5)
    at mixin.Mixin.create.render (http://localhost:7357/assets/vendor.js:47292:7)
    at EmberRenderer_createElement [as createElement] (http://localhost:7357/assets/vendor.js:49416:14)

The below seems to resolve that issue (although I wonder if we should make a test helper to make this simpler and ease implementation changes in the future? I might be able to help there ...):

import Ember from 'ember';
import { moduleForComponent, test } from 'ember-qunit';

import helper from 'backend/helpers/can';

moduleForComponent('sidebar-menu', {
  beforeEach: function() {
    Ember.HTMLBars._registerHelper('can', helper);
  }
  // specify the other units that are required for this test
  //needs: ['component:foo', 'helper:bar']
});

But now I'm hitting a second error regarding auto-loading abilities:

Error: Assertion Failed: No ability type found for forms
    at new Error (native)
    at Error.EmberError (http://localhost:7357/assets/vendor.js:22557:21)
    at Object.Ember.default.assert (http://localhost:7357/assets/vendor.js:15711:13)
    at exports.default (http://localhost:7357/assets/vendor.js:84566:22)
    at subexpr (http://localhost:7357/assets/vendor.js:17940:34)
    at Object.render (http://localhost:7357/assets/backend.js:1956:44)
    at renderHTMLBarsTemplate (http://localhost:7357/assets/vendor.js:18388:21)
    at renderView (http://localhost:7357/assets/vendor.js:18355:16)
    at renderView (http://localhost:7357/assets/vendor.js:47271:5)
    at mixin.Mixin.create.render (http://localhost:7357/assets/vendor.js:47292:7)

The app/abilities/forms.js ability is properly setup and works in the UI (and in acceptance tests). But thinking about it, in essence I'm running a mini-acceptance test (just for a component). Which would be why the initializers you use to setup the abilities aren't working.

Any suggestions / pointers on how to handle things here?

Reduce Reliance On Computed Properties

When using Ember Can for some recent projects it got me thinking to what a more modern feeling ember-can v2 could/would look like.

Confusing Use of Computed Properties

Right now all can... lookups rely on computed properties.
This was really popular at the creation of ember-can and was inspired by @attr use in can-can but I think that moving to a more functional approach would make things a lot clearer and possibly enable things like #20.

My idea behind this is to make can... lookups use functions instead of computed properties like this:

// Before
canEdit: computed('model.isActive', 'session.isAuthenticated', function() {
	return this.get('model.isActive') && this.get('session.isAuthenticated');
})

// After
canEdit(model, props) {
	return model.get('isActive') && this.get('session.isAuthenticated');
}

This explicit behavior makes it much clearer how to interact with abilities and reduces the observer stack.
Here in the example I still show the use of having a container aware EmberObject so that things like services can still be injected.


I think this would require a v2 release but I think that it could still be done in a backwards compatible way while deprecating computed property ability definitions.

Redirect in route on reload

I'm using ember-simple-auth for authentication. Wanted to protect admin-routes.

Following the documentation, I have something like this:

// app/routes/super-protected-route.js

import Ember from 'ember';
import { CanMixin } from 'ember-can';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';

export default Ember.Route.extend(AuthenticatedRouteMixin, CanMixin, {
  beforeModel() {
    this._super(...arguments);
    if (!this.can('see super-protected-thing')) {
      return this.transitionTo('index');
    }
  },
});
// app/abilities/super-protected-thing.js

import Ember from 'ember';
import { Ability } from 'ember-can';

export default Ability.extend({
  user: Ember.computed('session.currentUser', function () {
    return this.get('session.currentUser');
  }).readOnly()

  canSee: Ember.computed('user.isAdmin', function () {
    return this.get('user.isAdmin');
  }),
});

User is loaded by the SessionService:

// app/services/session.js

import Ember from 'ember';
import SessionService from 'ember-simple-auth/services/session';

export default SessionService.extend({
  store: Ember.inject.service(),

  currentUser: Ember.computed('data.authenticated.user_id', function () {
    const userID = this.get('data.authenticated.user_id');
    return this.get('store').findRecord('user', userID);
  }).readOnly()
});

If the admin is logged in and refreshes the page he get's redirected to index because the currentUser is not loaded when the SuperProtectedRoute enters beforeModel().

How to fix this?

Implementing for routes

You gave this example for checking access to a route:

`import Ember from 'ember';
import { CanMixin } from 'ember-can';

export default Ember.Route.extend(CanMixin, {
beforeModel: function() {
if (!this.can('write post')) {
this.transitionTo('index');
}
}
});`

This works in general when navigating to the route, however we have seen some undesired effects when deeplinking to that route or doing a browser page reload. At this point the can evaluation does not seem to work yet and it always triggers the transitionTo() first (meaning you have to go through the navigation again to reach the route

Question: abilities in other framework

Hi,

I am a fan of your addon and I have been using it in some Ember projects. I am interested to know if your addon has been ported to React or if you are aware of other similar project in other framework. I did some research and I did not find anything.

Thanks in advance

Use via RouterDSL

Hi there, we found ourselves writing the following code in routes, which is great. However, as the app grew we felt like we could all benefit from a birds eye view of all the routes that require abilities.

beforeModel: function() {
    if (!this.can('write post')) {
      this.transitionTo('index');
    }
  }

We thought the best place for overlooking those would be in the Router.map.

Router.map(function() {
  this.route('posts', { abilities: ['manage team', 'view post'] }, function() {
    this.route('create', { abilities: ['create post'] });
    this.route('show', { path: ':id', resourceAbilities: ['update post'] });
  });
});

We've implemented an extension of the RouterDSL in a similar manner to the way ember-torii does with authenticatedRoute.

I was wondering if you would be interested in including this type of functionality into this addon.

best way to wait for session.user

Hi,
I am using ember-siple-auth and am saving the user object into the session, and the session in turn is injected into ember-can and all works great, except at the very first loading of the app before the session.user has been collected/stored.

Is there a recomended way of forcing ember-can to wait before the session.user has been stored before responding to any ember-can ability checks?

currently my abilities/requesttype.js file looks like this:

import Ember from 'ember';
import { Ability } from 'ember-can';

/*
 * ROLES:
 *    admin
 *    agent
 *    fdc
 */
export default Ability.extend({
  session: Ember.inject.service(),
  canEdit: Ember.computed(function() {
    var role = this.session.get('user.role');
    return (role === 'admin' || role === 'fdc');
  }
});

ember-can v0.8.4 build displays failing

The latest release ember-can v0.8.4 build displays failing, when will that be fixed?

Also on the releases page, only v0.8.2 is tagged "verified" and "latest release". Does this mean currently v0.8.2 is the latest stable release and v0.8.4 is not?

Thanks in advance.

Usage in in-repo-engine

hi,

trying to use ember-can in our app that consists of the main app and a few in-repo-engines.

we have a account-section and a corresponding account permission (read/write)

according to the docs, it should look like this:

// /lib/account/addon/abilities/account.js
export { default } from 'merchant-admin/abilities/account';

(merchant-admin is the main app, account is one of the in-repo-engines and account is also the name of the ability created in the main app)

I tried that, but then I'm getting this error:

Uncaught Error: Could not find module account/abilities/account imported from (require)

is it even possible to import from the main app (into the in-repo-engine)? or is there maybe another step missing?

ember:          3.2.2
ember-engines:  0.5.20
ember-can:      1.1.0

Can you please resolve the deprecation ember-cli-version-checker ?

The message keeps showing when moving the can service into an engine.

DEPRECATION: An addon is trying to access project.nodeModulesPath. This is not a reliable way to discover npm modules. Instead, consider doing: require("resolve").sync(something, { basedir: project.root }). Accessed from:   DependencyVersionChecker.NPMDependencyVersionChecker (/Users/bzhang2/Project/mg-ui/node_modules/ember-can/node_modules/ember-cli-version-checker/src/npm-dependency-version-checker.js:11:32)

With recent ember-cli development, the resolver modifications no longer seem to be necessary

As of ember-cli 2.17.0, the test-helper module no longer uses the custom tests/helpers/resolver.js and instead defaults to the app/resolver.js. Basically, the whole tests/helpers/resolver.js file is removed completely.

With it gone, and the app resolver being used, it looks like the fix for pluralized ability: 'abilities' is no longer necessary, so we should be able to safely remove it, unless I'm mistaken.

Potential issue with ability state

Hey,
I noticed a behavior which man not be expected by users and leads to potential issues. I need your verifications.

The problem

As abilities are kept as a singleton we are using one instance to determine a specific ability which means we are overwriting those instance whenever we ask for them.

  abilityFor(abilityName, model, properties = {}) {
    let ability = getOwner(this).lookup(`ability:${abilityName}`);
    assert(`No ability type found for ${abilityName}`, ability);

    ability.setProperties(Object.assign({}, { model }, properties));
    return ability;
  },

It doesn't seem like a good strategy but that's, not an issue. The issue is in the case when we have a logic basing on properties. In that case, we are not overriding those values which every request (cuz we are unable to track it as this is a hash) and we are getting into a dirty state of the class with every call.

Quick example:

  1. ability = abilityFor('user', this.get('modelUser'), { test: true })
  2. ability.get('test') // returns true
  3. abilityFor('user', this.get('modelUser'))
  4. ability.get('test') // returns true but it should be undefined

Proposal solution

instead of using singleton instance we can use a factory to create a new clean instance for every request.

Concerns

How does it impact the performance as we would have to create a new instance for every helper?
How do we cover destroying of those objects?

Attempting to register an unknown factory

Whenever I try to needs an ability, I get the following error:
Attempting to register an unknown factory:ability:sample-submission``

There's definitely app/abilities/sample-submission.js file. What could be causing this?

Parent component is destroyed: Cannot read property 'removeObserver' of undefined

I use the can helper in a component template. On click an action bubbles up to the controller, that filters and destroys the component.

Then the following error is logged to console:

TypeError: Cannot read property 'removeObserver' of undefined
    at Class.destroy (can.js:33)

Potential fix:

Check in addon/helpers/can.js if this._ability exists:

if (this._ability) {
  this._ability.removeObserver(this._abilityProp, this, 'recompute');
}

This avoids the particular error in my case, but I do not have the Ember knowledge if the can observer is removed in this case somehow automatically. Typically, automatically does not exist...

RFC: API Changes

A while back, @rlivsey asked me if I had any ideas on how to improve the API for this library. I didn't at the time because I didn't have enough concrete cases. I've since found a few things that are harder than they should be.

Prevents Cohesion

In route:posts, we check whether the user has permission to manage posts in general:

if (!this.can("manage posts")) { this.replaceWith('index'); }

In route:post/edit, we check whether the user has permission to manage that specific post:

if (!this.can("manage post", post)) { this.replaceWith('index'); }

But we have to define those two abilities in app/abilities/posts.js and app/abilities/post.js even though the logic is closely related.

Prevents Cohesion, Part 2

We have about 20 permissions that related to about 12 different models. All of the definitions are very short and they all look very similar. Having app/billing/ability.js and app/account/ability.js just to get names seems silly. It would be easier if we could just do

// app/abilities/all.js
export default Ability.extend({
  canManageBilling: ...,
  canManageAccount: ...,
});

Makes Searching Hard

Permissions are defined as FooAbility.prototype.canTwizzleBar, but used as can("twizzle bar in foo"). This makes it hard to find the definition of the thing you're looking at. If I see a template that looks like

{{#if (can "twizzle bar in foo")}}

I want to copy and paste that string and find the definition. That would be easier if it were

{{#if (can "twizzleBarInFoo")}}

or if the definition were

Ability.extend({
  "can twizzle bar in foo": function() { return true; }
});

Suggestion

I don't have a single concrete suggestion. I understand the value of having a FooAbility with just a canEdit, especially for projects that have many or complex permissions schemes.

My inclination would be to have apps define a single app/abilities.js. If larger apps want to break that logic up, they can.

An alternative would be to have apps define any number of app/abilities/*.js, but each is a Mixin. They all get combined into one object.

My two alternatives both require moving the full name of the ability into the property key instead of composing the key and the class name.

Add more examples

Please add more examples. I'm interested how I can implement custom checking with passing into param

{{#if (can "edit post" post)}} ... {{else}}

How handle passed post? Thx

Question about mutable models

Hey guys! I have a question about models mutability. What if I use this template:

import Ember from 'ember';
import { Ability } from 'ember-can';

export default Ability.extend({
  canWrite: Ember.computed('user.isAdmin', function() {
    return this.get('user.isAdmin');
  })
});

And some user takes an Ember extension and changed his/her isAdmin property. Will the policies show some secret (hidden) parts of the application? Of course, the backend shouldn't react on some disallowed actions from that user, but is there any possibility to protect the property isAdmin?

Thanks for your answer in advance.

Cannot helper issue

Hi,
I noticed some unexpected errors according to cannot helper with looking for a wrong __container__. I fix it by defining my own helper:

Before

screen shot 2017-09-19 at 17 00 44

My Solution

// helpers/cannot.js

import CanHelper from 'ember-can/helpers/can';

export default CanHelper.extend({
  compute() {
    return !this._super(...arguments);
  }
});

What do you think about it?

async abilities

Maybe supporting promises by returning a promise since resolving/rejecting is like true/false. This would make these much more powerful.

Introduction to ember-can on Global Ember Meetup

For some reason, I feel like I already asked about this but I can't see it anywhere.

Would someone from this project be interested in giving an "Introduction to ember-can" on Global Ember Meetup?

An "Introduction to " talk has the following format

  • Why was it created?
  • What problem is it trying to solve?
  • How do people use it?
  • What should people be aware of?

Would you be interested in giving this talk on April 30th or June 11th?

How to use in a Controller

I am following (I believe) your examples but cannot get the output to work as expected, so maybe someone can point out where I'm going wrong: :)

two scenarios for outputting info in my template: (template.hbs)

{{#if canSendRequests}}
   Can Send method A <br>
{{else}}
   Cannot Send method A<br>
{{/if}}
{{#if (can "send requests")}}
   Can Send method B<br>
{{else}}
   Cannot Send method B<br>
{{/if}}

Method A is trying to use a calculated variable taken from the controller, where as Method B is using a template helper method. For me Method A is not working (always returns true)

the above produces (for two different users) the following:

  1. An Admin User (who _should_ be able to Send)
Can Send method A     <-- Correct
Can Send method B     <-- Correct
  1. Another User (who _cannot_ send)
Can Send method A     <-- NOT Correct
Cannot Send method B    <-- Correct

This is my controller (trimmed down a lot) : (controller.js)

import Ember from 'ember';
import { computed } from 'ember-can';

export default Ember.Controller.extend({
    ability:              computed.ability('requests', 'model'),
    canSendRequests:      Ember.computed.reads('ability', 'canSend'),
});

my abilities/requests.js file I assume is correct as the helper version is working as expected, however snippets of this file are:

isService: function() {
    var role = this.get('user.role');
    return (role === 'fdc' || role === 'admin' || role === 'super');
 },
canSend: Ember.computed('user.role',function() {
    return this.isService();
 }),

Ember version 1.13.10
Ember Can ver: 0.7.0

Can anyone point me in the right direction of where I'm going wrong? - thanks

Failed: A helper named 'can' could not be found

Hey, seems like my ember-cli is not detecting the can.js helper. When doing:

if (can 'view')
  h1 hello

I get error:

Failed: A helper named 'can' could not be found

Currently using ember-cli 0.2.7 & ember 1.13.2

I thought it was a problem with our app, so I scaffolded a new ember app to double check, and still getting the same error. Seems like the helpers are not found?

When I manipulated the app/helpers/can.js to be renamed to can-can.js and helper as can-can that removed the helper not found but resulted to:

Uncaught TypeError: Cannot read property 'extend' of undefined(anonymous function) @ can.js:7mod.state @ loader.js:141tryFinally @ loader.js:21requireModule @ loader.js:139requireFrom @ loader.js:112reify @ loader.js:97mod.state @ loader.js:140tryFinally @ loader.js:21requireModule @ loader.js:139Ember.DefaultResolver.extend._extractDefaultExport @ ember-resolver.js:367resolveOther @ ember-resolver.js:109superWrapper @ ember.debug.js:17428exports.default.EmberObject.default.extend.resolveHelper @ ember.debug.js:4777exports.default.EmberObject.default.extend.resolve @ ember.debug.js:4594resolve @ ember.debug.js:4437resolve @ ember.debug.js:2109Registry.resolve @ ember.debug.js:1715factoryFor @ ember.debug.js:1317instantiate @ ember.debug.js:1375lookup @ ember.debug.js:1273Container.lookup @ ember.debug.js:1208lookupHelper @ ember.debug.js:8191subexpr @ ember.debug.js:8005render @ application.js:134renderHTMLBarsTemplate @ ember.debug.js:8491renderView @ ember.debug.js:8463renderView @ ember.debug.js:35400mixin.Mixin.create.render @ ember.debug.js:35423EmberRenderer_createElement @ ember.debug.js:37468Renderer_renderTree @ ember.debug.js:9140scheduledRenderTree @ ember.debug.js:9216Queue.invoke @ ember.debug.js:878Queue.flush @ ember.debug.js:943DeferredActionQueues.flush @ ember.debug.js:748Backburner.end @ ember.debug.js:173Backburner.run @ ember.debug.js:228Backburner.join @ ember.debug.js:247run.join @ ember.debug.js:15904run.bind @ ember.debug.js:15966jQuery.Callbacks.fire @ jquery.js:3148jQuery.Callbacks.self.fireWith @ jquery.js:3260jQuery.extend.ready @ jquery.js:3472completed @ jquery.js:3503

Coming from https://github.com/minutebase/ember-can/blob/master/addon/helpers/can.js#L4

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.