Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Setting cookie attributes in $cookieStore #2459

Closed
wants to merge 6 commits into from

Conversation

alonbardavid
Copy link
Contributor

As a response to issues: #1786,#950,#1918,#1320
This PR enables setting a cookie's path and expires attributes using the $cookieStore service.
$cookies was not changed, since it's API is too strict, I could find no way to implement it without causing breaking changes, or using some terrible hacks.
I would suggest adding a new service to the module to support more advanced features (such as Json $watch expression, which should be more useful then binding the $cookies object directly), but this would probably require a bit of research regarding use cases.

Some things to note regarding the implementation:

  • urlroot (karma property) was added to all unit tests - to facilitate testing of different cookie path
  • Backward incompatible change (bad fixing a bad behaviour):
    delete cookie now deletes cookies set with a path other then / (or baseHref)
    that is before the fix, if you had a cookie set on path /karma (if it was added by the server for instance),
    you would be able to see it, but deleting it will fail silently (and will not delete)
    delete cookie now deletes duplicate cookies with the same name (if they have different paths)
  • Due to the fact that no browser allows inspecting the Path and Expiration date of a cookie, I chose to add a private _setCookie function inside the $browser module
    which I override in unit tests. Not the most elegent solution, but the only other way to check Expiration is to mock the document object and change how document.cookie works, and that would have taken me too long for very little benefit.

Design decisions:

  • to change the path and/or expiration date of a cookie, you can pass an options object as the third argument to cookies(name,value)
    the options object can override Path and Expires when adding a value, all other options variables are ignored (but are allowed, I.E. no validation is made).
    You can pass either path, expires,none or both.
  • the path option is checked to be a string and be partial to window.location, otherwise, the default path is used. (warning is logged)
  • the expires option is checked to be a Date and in the future, otherwise no expiration is set. (warning is logged)
  • at the moment no support for non-Date expires is supported (for instance such as maxAge, or a number to indicate number of millesconds for cookie to last)
  • you can now delete a cookie on a specific path by calling cookie(name,undefined,options), with options containing path
  • only the $cookieStore service will support putting cookies with different path/expiration (as well as deleting certain paths.
    this is to support backward compatability with $cookies.

Notes:
if the value passed to browser.cookies is not a String, it fails silently.

This is my first time contributing here (and on a Major open source project), I hope this can be incorporated.
I'm more than happy to make changes if something is wrong, so please let me know.

$cookieStore service had no way to set cookie attributes (such as expires).
The api for $cookieStore.put and $cookieStore.remove now has a third argument-
options,which allows setting cookie attributes, or deleting cookies under a
specific path.
- to change the path and/or expiration date of a cookie, you can pass an
  options object as the third argument to cookies(name,value).
  the options object can override Path and Expires when adding a value.
  You can pass either path, expires,none or both.
- the path option is checked to be a string and be partial to window.location
  otherwise, the default path is used. (warning is logged)
- the expires option is checked to be a Date and in the future,
  otherwise no expiration is set. (warning is logged)
- you can now delete a cookie on a specific path by calling
  cookie(name,undefined,{path:x})

The $cookie service remains unchanged since supporting cookie attributes
would mean serious backward comptability issues.
Foundations were put in place to make supporting $cookie easier.
GENERIC TEST CHANGE:
To support checking cookie path, all unit-tests now run on path "/karma/tests"
Documentation was updated for both $cookie and $cookieStore with examples.

closes	angular#1786, angular#1320
BREAKING CHANGE: As part of the change, deleting a cookie  now deletes
   cookies set in multiple paths (and duplicate cookies if exists).
   Previously only cookies set in the / path were deleted.
   Since it is not intutive, if this change breaks someones code, it is
   probably as an accidental side-effect.
   It is reasonable to assume that most people actually wanted to delete the
   cookie even if it wasn't set in the same path (since they can see it).
   So while this is a breaking change, it fixes bad behaviour.
   If needed, you can delete the cookie in a specific path using $cookieStore.
@alonbardavid
Copy link
Contributor Author

I also signed the "Contributor License Agreement" electronically, I think (it redirect to nothing :/) .
Using the email used here.

@petebacondarwin
Copy link
Contributor

PR Checklist (Minor Feature)

  • Contributor signed CLA now or in the past (if you just signed, leave a comment here with your real name for cross reference)
  • Feature improves existing core functionality
  • API is compatible with existing Angular apis and relevant standards (if applicable)
  • PR doesn't contain a breaking change
  • PR contains unit tests
  • PR contains e2e tests (if suitable)
  • PR contains documentation update
  • PR passes all tests on Travis (sanity)
  • PR passes all tests on ci.angularjs.org (cross-browser compatibility)
  • PR is rebased against recent master
  • PR is squashed into one commit per logical change
  • PR's commit messages are descriptive and allows us to autogenerate release notes (required commit message format)
  • All changes requested in review have been implemented

@petebacondarwin
Copy link
Contributor

Internet Explorer 8 & 9 don't like this: http://ci.angularjs.org/job/angular.js-pete/90/

@petebacondarwin
Copy link
Contributor

By the way, you appear to have modified all functions to have a space between function and (), which makes it look like you have far more changes than there really are. I suspect that you didn't purposefully do this for some idealistic aim, but that perhaps your editor did it for you?
For consistency with the rest of the code base I would suggest you revert that formatting in the PR.

@petebacondarwin
Copy link
Contributor

Can you explain why you have to expose and mock _setCookie in the service rather than simply injecting document and accessing document[0].cookie in the specs?

@alonbardavid
Copy link
Contributor Author

Function spaces- I suspect the editor did that, I've hopefully reverted that in the following commit.

the problem with accessing document.cookie is that you can't read the Path or Expiration date of a cookie using javascript. So I couldn't have verified that path and expiration was done properly. That's why I need to capture the calls before they reach document.cookie (or mock document.cookie itself, which is a lot of work for little gain).

For example, if I set document.cookie = "cookie=numnum;path=/;expiration=111" when I read document.cookie, I'll get "cookie=numnum".

In regards to why injecting a mock document isn't easy, it's mostly because the tests are already using document, and I would need to mock all the APIs that are used (or alternatively use something like harmony-proxies, which don't work on all browsers).

I've fixed the IE issues that appeared on the tests, however for some bizzare reason, my IE suite get stuck at test 749, and I'm unable at the moment to complete a full suite on IE. since I haven't touched those tests, I'm mostly sure it works (and the fact they didn't appear on the cross-browser hudson also raises my hopes here).

@alonbardavid
Copy link
Contributor Author

Can I ask why "PR is squashed into one commit per logical change" and "PR's commit messages are descriptive" are not checked, did I miss something?

@petebacondarwin
Copy link
Contributor

@Illniyar -
Thanks for working on the PR.

To answer your question about the tasks:
Two of your four commits are not in the correct format and in any case they should probably be squashed into the other two commits.

I would like to look at the mocking issue a bit further because I don't like the idea of creating backdoors just for testing here. I'll give it a go on with IE on my machine.

@alonbardavid
Copy link
Contributor Author

What messages do you put for commits that fix regressions in previous commits? how do you "squash" two commits?

In regards to mocking cookies, what browser support is needed? I can think of a way of doing it, but I need getter/setter support, and I'm not sure what browsers does angular.js need to support (including version numbers for firefox/chrome etc...).
The main problem now is that cookie doesn't work like a normal field, there's no way to mock it without getter/setter support, and if there's one browser that it is tested on and doesn't have such support the tests will fail.

@pkozlowski-opensource
Copy link
Member

@alonbardavid
Copy link
Contributor Author

Thank you pkozlowski-opensource. I'm not really sure how to squash things now though, should a new branch be created with a new PR? I can't see a way to squash commits on the same branch/repository you are currently working on.

@alonbardavid
Copy link
Contributor Author

I've commited a fix that will remove the _setCookie backdoor. Hopefully this should work in: IE8+,FF4+,Chrome and Opera12+.

@petebacondarwin
Copy link
Contributor

@Illniyar - don't worry about squashing. I can rebase if and when we merge.

@petebacondarwin
Copy link
Contributor

@Illniyar - I am having some issues with this:

http://plnkr.co/edit/6RuykjI6U1AJec0GMwPE?p=preview

It doesn't seem to actually store the cookie in the browser.

Moreover, my understanding is that you remove a cookie from the browser by setting its expiration value to now. You are not doing this.

Any thoughts?

@alonbardavid
Copy link
Contributor Author

I'm not sure what is going on there. If I take the exact same code and put it on directly on a server, it works. Something in Plunker is not working properly, probably there is some kind of permission issue that block the cookie from being set,maybe something to do with the iFrame?

In regards to removing cookie, we set the date to "expires=Thu, 01 Jan 1970 00:00:00 GMT" in all places where we try to delete the cookie, I'm not sure what your seeing that is different.

@alonbardavid
Copy link
Contributor Author

any news here?

@karlfreeman
Copy link

Would love to see this be pulled in! Thanks for the request @Illniyar 👍

@karlfreeman
Copy link

@Illniyar Have you considered also adding secure to the cookie store?

@alonbardavid
Copy link
Contributor Author

It should be relatively easy to add (along with domain specific cookies ) as the additional options argument provides a lot of lieniency. However there were several reasons why I didn't:

  1. There were no issues open for this (or for adding domain).
  2. I did not want to overload a single commit with too much functionality.
  3. The testing for this might require quite a bit of effort.

@karlfreeman
Copy link

I guess we need to hear from @petebacondarwin about how much $cookieStore should do. Would be great to have feature parity with other js cookie libs.

@karlfreeman
Copy link

#950 does mentions a couple things like secure

@IgorMinar
Copy link
Contributor

I looked over the commit a4ed23c and I like the direction, but it still needs some major refactoring:

  • the use of $cookieOptionHash is really hackish, I think that we should make it possible to set the option via $cookies service. How about $cookies.$set(name, value, options) or $cookies(name, value, options) both are backwards compatible.
  • I'd like to move all of the cookie related code out of $browser into the angular-cookies module. long time go other stuff depended on $browser, but currently cookies is the last major piece that holds us from killing $browser. This change could happen as part of this refactoring (maybe as a separate commit though).

What do you think about these two big changes? Would you be up for addressing them?

If you sign up for the $browser refactoring and are capable of making the changes in a timely manner, I'll make sure that this change lands in 1.2 (we'd like to feature freeze 1.1.x within a week).

@alonbardavid
Copy link
Contributor Author

The $cookies service (as opposed to the $cookieStore service) was appearently desgined to work as a hashmap, I.E. people might use the cookies hashmap directly (see the example I added to the docs to see how it might be used).
Which means that adding any variable to it will cause backward incompatability with anyone who iterates through the entire hashmap.
Since it is returned directly by the factory, we also can't add multiple functions to the $cookie service (as opposed to the $cookieStore service).

I didn't consider returning $cookies as a function, I can't think of a reason why it'll break backward compatability, can you? if not, then it should be done.

it shouldn't be a problem moving the cookies part out of $browser.

I'd be happy to refactor both (assuming $cookies as a function doesn't break compatability).

Considering someone might be using the $browser (and $browser.cookies) directly, shouldn't there be a deprecation period?

@nunosilva800
Copy link

+1 for this!

@tangentfairy-zz
Copy link

I'm having the same issue, would like to see this added

@gurdotan
Copy link

gurdotan commented Dec 8, 2014

Same here, this is really needed.

@adrianduke
Copy link

+1

@jdscolam
Copy link

+1 as well.

@ddahan
Copy link

ddahan commented Dec 15, 2014

+1 too..

@petebacondarwin petebacondarwin modified the milestones: 1.4.x, 1.3.x Dec 16, 2014
@petebacondarwin petebacondarwin assigned shahata and unassigned IgorMinar Dec 16, 2014
@altryne
Copy link

altryne commented Jan 8, 2015

👍
I really think that using routing in your SPA, without the possibility to define that all cookies will have the same path is problematic and even error prone!

@petebacondarwin
Copy link
Contributor

@altryne - does #10530 solve the problem for you?

@altryne
Copy link

altryne commented Jan 8, 2015

@petebacondarwin hey, I'm not sure how to try this out, but what I don't see there is a global config, so I can set up my default params like path , expiry and domain once

@petebacondarwin
Copy link
Contributor

@altryne - can you comment on that PR so that @shahata can ensure that he covers your use case?

@altryne
Copy link

altryne commented Jan 10, 2015

@petebacondarwin now looking at it again, this was indeed supported!
An options parameter was added to $cookieStore.put() which supports passing path, domain, expires and secure parameters.
thanx!
hope this will land sometime soon

@lgalfaso
Copy link
Contributor

The new cookies implementation landed, this can be closed

@lgalfaso lgalfaso closed this Mar 12, 2015
@mike-spainhower
Copy link

👏

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.