[WIP/BETA] EU cookie law

Started by emanuele, April 21, 2012, 04:30:56 PM

Previous topic - Next topic

emanuele

Reference: http://www.simplemachines.org/community/index.php?topic=425349.0
Repo: https://github.com/emanuele45/EU-cookie-law
Package: attached

This package is a work in progress, I don't know if all the cookies are blocked.

Please also remember that this mod is intended to work with SMF only, I will not support any mod.

If you find cookies once installed try to provide as much informations as possible to find out why it is created (i.e. action you were doing, mods installed, themes installed, used SSI, particular configurations (permissions, options enabled, etc.).

The attached package should:
  • prevent any kind of cookie to be set up (even ban-related cookies are not put in place, bans will relay rely on a complete ban check every time unless the user accept the cookie);
  • since these actions would setup a a cookie, I disabled at "action-time" any post, vote, moderate, etc. action that could create a cookie (I added more than necessary just because I was too lazy to check if the actions actually create a cookie);
  • the "accept cookie" is obtained through a cookie itself (i.e. once you click on "accept" a cookie is created) that will last for the session (i.e. every time you or your users will close the browser you will be asked again to accept the cookies, this could be changed to a more persistent cookie...let's say a week?);
  • there is an hidden setting (ecl_strict_interpretation) that enables a possible stricter interpretation of the law: in other terms you or your users will not be allowed to login or register unless the accept the cookies. As far as I can tell this is *not* required by the law (UK instructions on implementation), because as soon as the user registers or logs in he is accepting the communication (or something like that, I read it yesterday and I don't remember the exact terms), but still can be enabled if you want.

    Important: the privacy notice is completely unwritten, it's just a placeholder, I'm not good at writing this kind of legal-related things...


Take a peek at what I'm doing! ;D




Hai bisogno di supporto in Italiano?

Aiutateci ad aiutarvi: spiegate bene il vostro problema: no, "non funziona" non è una spiegazione!!
1) Cosa fai,
2) cosa ti aspetti,
3) cosa ottieni.

CircleDock

This is an excellent starting point, Emanuele! But I'm still seeing visitor cookies being set :(

Anyone who has the Google Analytics mod installed should perform the following edit on subs.php

Search for:
function ob_google_analytics($buffer)
{
    global $modSettings, $boardurl;


Replace with:
function ob_google_analytics($buffer)
{
    global $modSettings, $boardurl;

    if (!ecl_authorized_cookies()) return;

This will stop the four cookies set by Google Analytics.

emanuele

Quote from: CircleDock on April 21, 2012, 11:58:38 PM
This is an excellent starting point, Emanuele! But I'm still seeing visitor cookies being set :(
I visited your forum and this applies:
Quote from: emanuele on April 21, 2012, 04:30:56 PM
Please also remember that this mod is intended to work with SMF only, I will not support any mod.
I cannot know what other mods you have installed and how all these uses cookies, remove that is responsibility of mods' authors, I can't do (almost) anything about it.

Of course other mods can rely on the functions provided by this mod for their functionalities.


Take a peek at what I'm doing! ;D




Hai bisogno di supporto in Italiano?

Aiutateci ad aiutarvi: spiegate bene il vostro problema: no, "non funziona" non è una spiegazione!!
1) Cosa fai,
2) cosa ti aspetti,
3) cosa ottieni.

CircleDock

Quote from: emanuele on April 22, 2012, 04:02:00 AM
Quote from: CircleDock on April 21, 2012, 11:58:38 PM
This is an excellent starting point, Emanuele! But I'm still seeing visitor cookies being set :(
I visited your forum and this applies:
Quote from: emanuele on April 21, 2012, 04:30:56 PM
Please also remember that this mod is intended to work with SMF only, I will not support any mod.
I cannot know what other mods you have installed and how all these uses cookies, remove that is responsibility of mods' authors, I can't do (almost) anything about it.

Of course other mods can rely on the functions provided by this mod for their functionalities.
I also have another site in the process of construction where I tested your mod. It doesn't have the Portal which I suspect is your concern here. That site also sets the PHPSESSID cookie even when I take no action to permit any cookies.

I have also had a look at the Portal's code and it appears to do nothing which would directly cause a cookie to be set. Using your convenient function, I have blocked Google Analytics as noted above.

Is it possible that SSI has an unplugged path which can cause a cookie to be set? That's about the only thing I can think of.

emanuele

My point is not that the portal is setting the session, my point is that I cannot see the cookie on your site...unless I enable javascript, so it means it's a script.

Here it is the script that is setting the session:
http://liveinthephilippinesforum.com/forum/sachat/index.php?action=head&theme=default


Take a peek at what I'm doing! ;D




Hai bisogno di supporto in Italiano?

Aiutateci ad aiutarvi: spiegate bene il vostro problema: no, "non funziona" non è una spiegazione!!
1) Cosa fai,
2) cosa ti aspetti,
3) cosa ottieni.

CircleDock

#5
Quote from: emanuele on April 22, 2012, 04:53:31 AM
My point is not that the portal is setting the session, my point is that I cannot see the cookie on your site...unless I enable javascript, so it means it's a script.

Here it is the script that is setting the session:
http://liveinthephilippinesforum.com/forum/sachat/index.php?action=head&theme=default

Ah! Thank you!!

I'm attaching that particular file and assume that the cookie is being set in line 20:

define('SMF', 1);

// Experimental Optimizer
define('loadOpt', 1);

        session_start(); // <--- line 20
session_cache_limiter('nocache');

// Lets go head and load the settings here.
require_once(str_replace('//','/',dirname(__FILE__).'/').'../Settings.php');

// Load SMF's compatibility file for unsupported functions.
if (@version_compare(PHP_VERSION, '5') == -1) {
require_once($sourcedir . '/Subs-Compat.php');
}


As the chat facility isn't available for guests anyway, in your opinion would the following work?

define('SMF', 1);

// Experimental Optimizer
define('loadOpt', 1);

// Lets go head and load the settings here.
require_once(str_replace('//','/',dirname(__FILE__).'/').'../Settings.php');

// Load SMF's compatibility file for unsupported functions.
if (@version_compare(PHP_VERSION, '5') == -1) {
require_once($sourcedir . '/Subs-Compat.php');

require_once($sourcedir . '/Subs-EclWarning.php');
        if (!ecl_authorized_cookies())
                 return;
        session_start();
session_cache_limiter('nocache');



Or, perhaps it would be better to put:

define('SMF', 1);

// Experimental Optimizer
define('loadOpt', 1);

// Lets go head and load the settings here.
require_once(str_replace('//','/',dirname(__FILE__).'/').'../Settings.php');

// Load SMF's compatibility file for unsupported functions.
if (@version_compare(PHP_VERSION, '5') == -1) {
require_once($sourcedir . '/Subs-Compat.php');

require_once($sourcedir . '/Subs-EclWarning.php');
        if (ecl_authorized_cookies())
             session_start();


allow most of the rest of the code to execute and just before it tests to see if the Mod is disabled, put:

        $modSettings['2sichat_disable'] = !ecl_authorized_cookies();

if ($modSettings['2sichat_disable']) {
die();
}
if ($modSettings['2sichat_load_chk']) {
doLoadCHK();
}


I'm a "dabbler" rather than a coder, so would welcome any help you can provide :)

Mark

emanuele

I would go a bit further (but that depends on how the chat is working) and not even echo the <script> tag if the user is not allowed to use the chat...
It should be in index.template.php...the exact implementation depends.


Take a peek at what I'm doing! ;D




Hai bisogno di supporto in Italiano?

Aiutateci ad aiutarvi: spiegate bene il vostro problema: no, "non funziona" non è una spiegazione!!
1) Cosa fai,
2) cosa ti aspetti,
3) cosa ottieni.

CircleDock

#7
This is what's necessary in the case of SA-Chat. SA-Chat's index.php file will require the following edit:

Search for:
define('SMF', 1);

// Experimental Optimizer
define('loadOpt', 1);

     session_start();
session_cache_limiter('nocache');

// Lets go head and load the settings here.
require_once(str_replace('//','/',dirname(__FILE__).'/').'../Settings.php');

// Load SMF's compatibility file for unsupported functions.
if (@version_compare(PHP_VERSION, '5') == -1) {
require_once($sourcedir . '/Subs-Compat.php');
}


Replace with:
//
// SA-Chat - index.php - Modified to prevent cookies being set when they haven't been
// expressly permitted by the user. Based on Emanuele's EU Cookie Law modification.
//
define('SMF', 1);

// Experimental Optimizer
define('loadOpt', 1);


// Lets go head and load the settings here.
require_once(str_replace('//','/',dirname(__FILE__).'/').'../Settings.php');

// Load SMF's compatibility file for unsupported functions.
if (@version_compare(PHP_VERSION, '5') == -1) {
require_once($sourcedir . '/Subs-Compat.php');
}
    //
    // Load Emanuele's 'EU Cookie-checker Modification.
    require_once($sourcedir . '/Subs-EclWarning.php');
   
    // If the user hasn't accepted cookies, get out! We can not go ahead and load SA-Chat
    // because set_session() sets cookies and so potentially does SA-Chat's javascript.
    if (!ecl_authorized_cookies())
        die();
   
    // Okay, cookies can be set so continue.   
    session_start();
session_cache_limiter('nocache');


It should be noted that the above will prevent SA-Chat from loading and invoking session_start() which causes a cookie to be set. It is necessary to stop SA-Chat from loading because its Javascript also has the potential of setting cookies.

For the sake of completeness, here's the modification necessary to prevent Google Analytics from setting all its cookies:

In subs.php

Search for:

function ob_google_analytics($buffer)
{
    global $modSettings, $boardurl;



Replace with:

function ob_google_analytics($buffer)
{
    global $modSettings, $boardurl;

    if (!ecl_authorized_cookies())
         return;


As far as I am concerned, Emanuele's Mod coupled with the above change means that no cookies whatsoever are set until the visitor clicks on the "Accept" link. This means my sites now comply fully with the EU Directive and with UK law.

Thank you very much indeed Emanuele for providing this modification!


Mark

CircleDock

#8
There is one change that needs to be made to the ecl_authorized_cookies() function in Subs-EclWarning.php:

Search for:
elseif (isset($_COOKIE['ecl_auth']) || isset($_COOKIE[$cookiename]))
        $storeCookies = true;
    elseif (isset($_GET['cookieaccept']))
    {
        setcookie('ecl_auth', 1, 0, '/');
        $storeCookies = true;
    } else
        $storeCookies = false;


Replace with:
// Temporary code until May 26. On that date, remove this code and re-enable the commented-out section
// below.
//------------------------------------------>
elseif (isset($_COOKIE['ecl_auth']))
        $storeCookies = true;
    elseif (isset($_GET['cookieaccept']))
    {
        setcookie('ecl_auth', 1, time()+60*60*24*30, '/'); // Set cookie to expire in 30 days
        $storeCookies = true;
    } else
        $storeCookies = false;
//<------------------------------------------
// Code to be re-enabled on May 26
//
//elseif (isset($_COOKIE['ecl_auth']) || isset($_COOKIE[$cookiename]))
//        $storeCookies = true;
//    elseif (isset($_GET['cookieaccept']))
//    {
//        setcookie('ecl_auth', 1, 0, '/');
//        $storeCookies = true;
//    } else
//        $storeCookies = false;
//<-------------------------------------------


The reason for that is that someone who is already a member of your site won't necessarily know that you're setting cookies - or indeed their purpose. The law requires everyone to "opt-in" as of May 26 regardless of whether or not they have visited the site previously.

Fortunately Emanuele sets a cookie when assent is given but as written the cookie lasts for the current session only and will (or should) be removed when the browser is closed.

With this change, a visitor need only accept once and regardless or not if they become a member, they shouldn't be asked to accept cookies again.

However this change is only necessary until May 26 after which it should be changed back to the original code.

oOo--STAR--oOo

Hey emanuele,

Could you modify the code so that the cookie agreement only has to be accepted once. As there is no requirement for them to have to keep accepting on every visit.

I know you set the cookie to say its accepted, but would it not be best to store this value in the database?
You can't fool a sufficiently talented fool.

http://www.uniquez-home.com
In Design Phase!

Mods I am designing,  No refresh Collapse Categories , Poll Redesign , Pure CSS Breadcrumb , Profile Statuses, Profile Views.

CircleDock

#10
Quote from: aljo1985 on April 22, 2012, 08:15:38 PM
Hey emanuele,

Could you modify the code so that the cookie agreement only has to be accepted once. As there is no requirement for them to have to keep accepting on every visit.

I know you set the cookie to say its accepted, but would it not be best to store this value in the database?

That is precisely the way it is working at the moment! Emanuele's code tests for the presence of EITHER the "CookieAcceptance Cookie" (ecl_auth) OR a "regular members" cookie and if either is found, then there is no requirement to get another acceptance from the user.

There is a problem with his code, however, which has just occurred to me. If, having accepted cookies, the visitor then logs-on but does NOT set his session time to be 'forever', then his "regular members" cookie will be removed as indeed so will his "CookieAcceptanceCookie" when he closes his browser. The fix for that is very simple:

Search for:        setcookie('ecl_auth', 1, 0, '/');

Replace with:         setcookie('ecl_auth', 1, time() + 189345600, '/');    // 60*60*24*365.25*6

That will set a 6 year cookie (the same length of time as SMF's 'forever' cookie.

You can, if you wish, replace the numerical value 1 with a descriptive string so that the visitor will know the purpose of that cookie - eg: setcookie('ecl_auth', 'EU_Cookie_Acceptance', time() + 189345600, '/');

You can not store the cookie acceptance value in the database because guests must also be prompted to accept cookies and, unless they register, they have no details to store in the database.

emanuele

The first issue I opened is exactly about that. Make it 6 years is in my opinion a bit too much. I'd set it to no more that a month or a year.

P.S.
I'm playing with a couple of ideas, I hope to have something ready in the next few days. I think it will be...nice. :P


Take a peek at what I'm doing! ;D




Hai bisogno di supporto in Italiano?

Aiutateci ad aiutarvi: spiegate bene il vostro problema: no, "non funziona" non è una spiegazione!!
1) Cosa fai,
2) cosa ti aspetti,
3) cosa ottieni.

CircleDock

Take a look at the attached package which is a combination of Javascript and PHP. It has the advantage of scalability as some EU nations may require separate "opt-ins" for first and third-party cookies (the UK currently does not require this).

You can see it in action here:

http://www.allaboutcookies.org/

but the only missing item is a link to a Privacy Policy page. That may be a configuration setting within the package.

I rather like this implementation as the acceptance window is modal and it also overcomes the problem of "shared PCs" - where the owner may not be aware of what cookies have been set by others using the computer.

feline

Quote from: emanuele on April 26, 2012, 01:01:37 PM
The first issue I opened is exactly about that. Make it 6 years is in my opinion a bit too much. I'd set it to no more that a month or a year.
I mean, that the max lifetime for cookies is limited to one year..  ::)

CircleDock

Quote from: feline on April 27, 2012, 07:32:18 PM
Quote from: emanuele on April 26, 2012, 01:01:37 PM
The first issue I opened is exactly about that. Make it 6 years is in my opinion a bit too much. I'd set it to no more that a month or a year.
I mean, that the max lifetime for cookies is limited to one year..  ::)
Yeah but as the cookie would expire and would/should be,removed by the browser, the member would have to re-accept cookies. The longer that is delayed, the better. in my view.

CircleDock

How to make Emanuele's Modification only affect visitors from within the EU

As it stands, Emanuele's modification will prompt all visitors to accept cookies, regardless of where they are located in the world. This may not be desirable and could be irritable for non-EU members.  There is a way around this but you will need to have Spuds' geoIP (IP to Location) modification installed as the method presented here makes use of that modification's database tables.

We will be editing just one file - Subs-EclWarning.php - which should be in your Sources directory.

Search for:

    global $cookiename, $modSettings;

    static $storeCookies;

    if (isset($storeCookies))
        return $storeCookies;


Replace with:

    global $cookiename, $modSettings;

    static $storeCookies;
    static $inEU;           // True if visitor's Point of Presence is within a EU member state

    // Have we checked this visitor's Point of Presence?
    if (!isset($inEU))
        $inEU = ecl_IsInEU();
    // If he's not within the EU, he can have cookies without giving permission
    if (!$inEU)
        $storeCookies = true;

    if (isset($storeCookies))
        return $storeCookies;


Immediately above the function ecl_authorized_cookies(), add the following code:

//
// Convert an IPv4 Address to a long integer. Adapted from a similarly-named
// geoIP function and included to save loading the whole of geoIP.
//
// Parameter:   $ip_Addr       An IPv4 Address in dotted notation
//
// Returns:     >0             IT Address as long integer
//              0              No IP Address or badly-formed IP Address
//
function ecl_geo_dot2long($ip_addr)
{
    if (empty($ip_addr))
return 0;
    elseif (preg_match('~\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}~', $ip_addr, $dummy))
{
        $ips = explode('.', $ip_addr);
        return ($ips[3] + $ips[2] * 256 + $ips[1] * 256 * 256 + $ips[0] * 256 * 256 * 256);
    }
else
return 0;
}

//
// Master Function to determine whether an IP Address points to a location within the European Union.
// If the geoIP modification is available and the visitor's IP Address is IPv4, then we use the geoIP
// tables. IPv6 Addresses currently not catered for.
//
// Returns     true     If the ECL modification should prompt for permission to store cookies.
//
function ecl_IsInEU()
{
    global $sourcedir;
   
  if (!isset($_SERVER['REMOTE_ADDR']))
    {
      return true;
    }
 
  if ((file_exists($sourcedir . 'geoIP.php')) && (strpos($_SERVER['REMOTE_ADDR'],':') === false))
  {
    return ecl_geoIP($_SERVER['REMOTE_ADDR']);
  }

// Add code here if you want to check IPv6 Addresses (not currently catered for).
  return true;
}

//
// This function checks the geoIP database tables to determine whether a given
// IPv4 Address is located within the EU. Uses code from geoIP modification.
//
// Parameter:   $ipAddress      A properly-formed IPv4 Address
//
// Returns:     true            IP Address is within the EU
//              false           IP Address not within the EU
//
function ecl_geoIP($ipAddress)
{
    global $smcFunc;
   
    $euNations = array('AD', 'AT', 'BE', 'BG', 'CY', 'CZ',      // Andorra (Sain), Austria, Belgium, Bulgaria, Cyprus, Czech Rep,
                       'DE', 'DK', 'EE', 'ES', 'EU', 'FI',      // Germany, Denmark, Estonia, Spain, (Europe), Finland,
                       'FR', 'FX', 'GB', 'GG', 'GR', 'HU',      // France, France (Metropolitain), United Kingdom, Guernsey, Greece, Hungary,
                       'IE', 'IM', 'IT', 'JE', 'LI', 'LT',      // Ireland, Isle of Man, Italy, Jersey, Lithuania, Lichtenstein,
                       'LU', 'LV', 'MC', 'MT', 'PL', 'PT',      // Luxemburg, Latvia, Monaco, Malta, Poland, Portugal,
                       'RO', 'SE', 'SI', 'SK', 'SM', 'VA',      // Romania, Sweden, Slovenia, Slovakia, San Marino, Vatican City             
    );
   
    $ip = $ipAddress;
    // We have an IPv4 address, convert to a long integer
    if (strpos($ip,'.'))
        $ipl = ecl_geo_dot2long($ip);

    // No declared IP Address, must be a dodgy user. Assume he's in Europe!
    if ($ipl === 0)
        return true;
       
    // Localhost is assumed not to be within the EU, though.
    if ($ipl == 2130706433)
        return false;
   
    // Query the geoIP database       
    $request = $smcFunc['db_query']('', '
SELECT ip.start, ip.end,
gc.cc, gc.cn as country
FROM {db_prefix}geoip_ip as ip
LEFT JOIN {db_prefix}geoip_countries AS gc ON (ip.locid = gc.ci)
WHERE ip.end >= {int:ip}
ORDER BY ip.end ASC
LIMIT 1',
array(
'ip' => (int) $ipl
)
);

    // Sanitise the result. Assume the visitor is within the EU.
$row = $smcFunc['db_fetch_assoc']($request);
    $cc = 'EU';
    // If the IP Address is not contained within a range, the next higher value will be returned.
    if ($row['start'] <= $ipl && $row['end'] >= $ipl)
        $cc = $row['cc'];
    $smcFunc['db_free_result']($request);   
   
    //  We have the two-letter country code, does it match?
    $res = array_search($cc, $euNations);
    if ($res === false)
            return false;
    return true;
}


That's it folks!

A caveat and other considerations:

  • This addition does not currently cater for checking the location of IPv6 Addresses and assumes any IPv6 address is within the EU.
  • As other countries (eg the US, Australia, New Zealand etc) enact similar cookie laws, their two-letter country codes can be added to the array $euNations
  • You can replace the database query with a cURL request to an external IP-to-location server.
  • Note that some EU member states have more than one identifiable country code (eg the UK includes 'GB', 'GG', 'IM' and "JE').
  • Note that I'm only testing for the presence of the geoIP modification (and hence its database), its code is not loaded and executed by this modification. If you have installed geoIP but have not updated its database, then you will need to do this.

Enjoy!

emanuele

Honestly geo location is not at all accurate, so I would strictly avoid it.


Take a peek at what I'm doing! ;D




Hai bisogno di supporto in Italiano?

Aiutateci ad aiutarvi: spiegate bene il vostro problema: no, "non funziona" non è una spiegazione!!
1) Cosa fai,
2) cosa ti aspetti,
3) cosa ottieni.

CircleDock

Quote from: emanuele on May 03, 2012, 01:07:08 PM
Honestly geo location is not at all accurate, so I would strictly avoid it.
Your point is well taken but MaxMind, whose database this uses, claims it to be well over 98% accurate at the country level, which is all we're interested in. The changes I've suggested do err on the side of caution and assume the visitor is within the EU unless their IP Address shows otherwise.

There's enough hostility towards this law as it is and some EU-based Admins might well be hesitant to implement any solution that might antagonize their non-EU members and visitors.

Given that many UK Government sites employ Geo-Location, as does all the UK's television broadcasters, Amazon etc., I can not see any reason why the ICO should object or come down heavily against a Forum where its use of Geo-Location has produced an erroneous result.

Arantor

The ICO does not itself use country based geo-location for the purposes of cookie detection and I can't see them taking it well if there is a complaint because it seems to be 'how can we avoid having to do this' which is a sign of bad faith compliance.

Geo-location where it can be used to benefit the user's experience is encouraged, or where it is required for legal compliance (e.g. content that is only licensed in certain places) but to avoid compliance with a law, is not something I'd want to argue.

emanuele

Quote from: CircleDock on May 04, 2012, 12:48:44 PM
Your point is well taken but MaxMind, whose database this uses, claims it to be well over 98% accurate at the country level, which is all we're interested in.
That would leave you a 1% (well, probably less) of probability that an EU-based user will not be recognized as such.
If you are lucky enough within this 1% you will not get anyone that will sue you (or report to the competent authority).

Feel free to post it, no problem, I (personally) would not include it in a package. ;)


Take a peek at what I'm doing! ;D




Hai bisogno di supporto in Italiano?

Aiutateci ad aiutarvi: spiegate bene il vostro problema: no, "non funziona" non è una spiegazione!!
1) Cosa fai,
2) cosa ti aspetti,
3) cosa ottieni.

Advertisement: