Skip to content

Commit

Permalink
Adding support for EU cookie compliance (#4420)
Browse files Browse the repository at this point in the history
  • Loading branch information
chenriksson committed Jul 21, 2017
1 parent 988968f commit ed45532
Show file tree
Hide file tree
Showing 27 changed files with 462 additions and 70 deletions.
51 changes: 51 additions & 0 deletions src/NuGetGallery.Core/Cookies/CookieComplianceServiceBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using NuGetGallery.Diagnostics;

namespace NuGetGallery.Cookies
{
/// <summary>
/// Base cookie compliance service with access to some Gallery resources.
/// </summary>
public abstract class CookieComplianceServiceBase : ICookieComplianceService
{
private string _domain;
private IDiagnosticsSource _diagnostics;

protected string Domain => _domain ?? throw new InvalidOperationException(CoreStrings.CookieComplianceServiceNotInitialized);

protected IDiagnosticsSource Diagnostics => _diagnostics ?? throw new InvalidOperationException(CoreStrings.CookieComplianceServiceNotInitialized);

public virtual Task InitializeAsync(string domain, IDiagnosticsService diagnostics, CancellationToken cancellationToken)
{
// Service should only be initialized once.
if (_domain != null)
{
throw new InvalidOperationException(CoreStrings.CookieComplianceServiceAlreadyInitialized);
}

_domain = domain;
_diagnostics = diagnostics.GetSource(GetType().Name);

return Task.Delay(0);
}

public abstract bool CanWriteNonEssentialCookies(HttpRequestBase request);

public abstract bool NeedsConsentForNonEssentialCookies(HttpRequestBase request);

public abstract CookieConsentMessage GetConsentMessage(HttpRequestBase request, string locale = null);

public abstract string GetConsentMarkup(HttpRequestBase request, string locale = null);

public abstract IEnumerable<string> GetConsentScripts(HttpRequestBase request, string locale = null);

public abstract IEnumerable<string> GetConsentStylesheets(HttpRequestBase request, string locale = null);
}
}
18 changes: 18 additions & 0 deletions src/NuGetGallery.Core/Cookies/CookieConsentMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace NuGetGallery.Cookies
{
public class CookieConsentMessage
{
public string Message { get; set; }

public string MoreInfoUrl { get; set; }

public string MoreInfoText { get; set; }

public string MoreInfoAriaLabel { get; set; }

public string[] Javascripts { get; set; }
}
}
51 changes: 51 additions & 0 deletions src/NuGetGallery.Core/Cookies/ICookieComplianceService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Web;

namespace NuGetGallery.Cookies
{
/// <summary>
/// Cookie compliance service, used to comply with EU privacy laws.
/// </summary>
public interface ICookieComplianceService
{
/// <summary>
/// Determine if consent is still needed for writing non-essential cookies.
/// </summary>
/// <returns>True if consent is needed, false if consent is already provided or not required.</returns>
bool NeedsConsentForNonEssentialCookies(HttpRequestBase request);

/// <summary>
/// Determine if non-essential cookies can be written.
/// </summary>
/// <returns>True if non-essential cookies can be written, false otherwise.</returns>
bool CanWriteNonEssentialCookies(HttpRequestBase request);

/// <summary>
/// Get the cookie consent banner message and resources. This API is an alternative to the default
/// rendering APIs below and can be used to customize the UI. Note that the messaging must remain intact.
/// </summary>
CookieConsentMessage GetConsentMessage(HttpRequestBase request, string locale = null);

#region Default CookieConsent rendering

/// <summary>
/// Get the default HTML markup for the cookie consent banner.
/// </summary>
string GetConsentMarkup(HttpRequestBase request, string locale = null);

/// <summary>
/// Get the default CSS links for the cookie consent banner.
/// </summary>
IEnumerable<string> GetConsentStylesheets(HttpRequestBase request, string locale = null);

/// <summary>
/// Get the default Javascript links for the cookie consent banner.
/// </summary>
IEnumerable<string> GetConsentScripts(HttpRequestBase request, string locale = null);

#endregion
}
}
32 changes: 32 additions & 0 deletions src/NuGetGallery.Core/Cookies/NullCookieComplianceService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Web;

namespace NuGetGallery.Cookies
{
/// <summary>
/// Default, no-op instance of the cookie compliance service, used when no shim is registered.
/// </summary>
public class NullCookieComplianceService: CookieComplianceServiceBase
{
private static readonly string[] EmptyStringArray = new string[0];

// Consent is not necessary and cookies can be written.

public override bool NeedsConsentForNonEssentialCookies(HttpRequestBase request) => false;

public override bool CanWriteNonEssentialCookies(HttpRequestBase request) => true;

// No markdown or scripts will be included.

public override CookieConsentMessage GetConsentMessage(HttpRequestBase request, string locale = null) => null;

public override string GetConsentMarkup(HttpRequestBase request, string locale = null) => string.Empty;

public override IEnumerable<string> GetConsentScripts(HttpRequestBase request, string locale = null) => EmptyStringArray;

public override IEnumerable<string> GetConsentStylesheets(HttpRequestBase request, string locale = null) => EmptyStringArray;
}
}
20 changes: 19 additions & 1 deletion src/NuGetGallery.Core/CoreStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/NuGetGallery.Core/CoreStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,10 @@
<data name="Manifest_InvalidDependencyVersion" xml:space="preserve">
<value>The package manifest contains an invalid Dependency Version: '{0}'</value>
</data>
<data name="CookieComplianceServiceNotInitialized" xml:space="preserve">
<value>The cookie compliance service is not initialized.</value>
</data>
<data name="CookieComplianceServiceAlreadyInitialized" xml:space="preserve">
<value>The cookie compliance service is already initialized.</value>
</data>
</root>
14 changes: 14 additions & 0 deletions src/NuGetGallery.Core/Diagnostics/IDiagnosticsService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace NuGetGallery.Diagnostics
{
public interface IDiagnosticsService
{
/// <summary>
/// Gets an <see cref="IDiagnosticsSource"/> by the specified name.
/// </summary>
/// <param name="name">The name of the source. It's recommended that you use the unqualified type name (i.e. 'UserService').</param>
IDiagnosticsSource GetSource(string name);
}
}
20 changes: 20 additions & 0 deletions src/NuGetGallery.Core/Diagnostics/IDiagnosticsSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace NuGetGallery.Diagnostics
{
public interface IDiagnosticsSource
{
void ExceptionEvent(Exception exception);

void TraceEvent(TraceEventType type, int id, string message,
[CallerMemberName] string member = null, [CallerFilePath] string file = null, [CallerLineNumber] int line = 0);

void PerfEvent(string name, TimeSpan time, IEnumerable<KeyValuePair<string, object>> payload);
}
}
6 changes: 6 additions & 0 deletions src/NuGetGallery.Core/NuGetGallery.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,15 @@
<Compile Include="Auditing\PackageAuditRecord.cs" />
<Compile Include="Auditing\ScopeAuditRecord.cs" />
<Compile Include="Auditing\UserAuditRecord.cs" />
<Compile Include="Cookies\CookieComplianceServiceBase.cs" />
<Compile Include="Cookies\CookieConsentMessage.cs" />
<Compile Include="Cookies\ICookieComplianceService.cs" />
<Compile Include="Cookies\NullCookieComplianceService.cs" />
<Compile Include="CoreConstants.cs" />
<Compile Include="CredentialTypes.cs" />
<Compile Include="Completion.cs" />
<Compile Include="Diagnostics\IDiagnosticsService.cs" />
<Compile Include="Diagnostics\IDiagnosticsSource.cs" />
<Compile Include="DisposableAction.cs" />
<Compile Include="Entities\Credential.cs" />
<Compile Include="Entities\CuratedFeed.cs" />
Expand Down
1 change: 0 additions & 1 deletion src/NuGetGallery.Core/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,3 @@
// AssemblyVersion, AssemblyFileVersion, AssemblyInformationalVersion, AssemblyMetadata (for Branch, CommitId, and BuildDateUtc)

[assembly: AssemblyMetadata("RepositoryUrl", "https://www.github.com/NuGet/NuGetGallery")]

67 changes: 38 additions & 29 deletions src/NuGetGallery/App_Code/ViewHelpers.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@using NuGetGallery
@using NuGetGallery.Helpers
@using NuGetGallery.Configuration
@using NuGetGallery.Cookies

@helper PreviousNextPager(IPreviousNextPager pager)
{
Expand Down Expand Up @@ -128,30 +129,34 @@

if (!string.IsNullOrEmpty(iKey))
{
<!-- Telemetry -->
<script type="text/javascript">
var appInsights = window.appInsights || function (config) {
function s(config) {
t[config] = function () {
var i = arguments;
t.queue.push(function () { t[config].apply(t, i) })
var cookieService = DependencyResolver.Current.GetService<ICookieComplianceService>();
if (cookieService.CanWriteNonEssentialCookies(Request))
{
<!-- Telemetry -->
<script type="text/javascript">
var appInsights = window.appInsights || function (config) {
function s(config) {
t[config] = function () {
var i = arguments;
t.queue.push(function () { t[config].apply(t, i) })
}
}
}

var t = { config: config }, r = document, f = window, e = "script", o = r.createElement(e), i, u;
for (o.src = config.url || "//az416426.vo.msecnd.net/scripts/a/ai.0.js", r.getElementsByTagName(e)[0].parentNode.appendChild(o), t.cookie = r.cookie, t.queue = [], i = ["Event", "Exception", "Metric", "PageView", "Trace"]; i.length;) s("track" + i.pop());
return config.disableExceptionTracking || (i = "onerror", s("_" + i), u = f[i], f[i] = function (config, r, f, e, o) {
var s = u && u(config, r, f, e, o);
return s !== !0 && t["_" + i](config, r, f, e, o), s
}), t
}({
instrumentationKey: '@(iKey)',
samplingPercentage: @(samplingPct)
});
var t = { config: config }, r = document, f = window, e = "script", o = r.createElement(e), i, u;
for (o.src = config.url || "//az416426.vo.msecnd.net/scripts/a/ai.0.js", r.getElementsByTagName(e)[0].parentNode.appendChild(o), t.cookie = r.cookie, t.queue = [], i = ["Event", "Exception", "Metric", "PageView", "Trace"]; i.length;) s("track" + i.pop());
return config.disableExceptionTracking || (i = "onerror", s("_" + i), u = f[i], f[i] = function (config, r, f, e, o) {
var s = u && u(config, r, f, e, o);
return s !== !0 && t["_" + i](config, r, f, e, o), s
}), t
}({
instrumentationKey: '@(iKey)',
samplingPercentage: @(samplingPct)
});

window.appInsights = appInsights;
appInsights.trackPageView();
</script>
window.appInsights = appInsights;
appInsights.trackPageView();
</script>
}
}
}

Expand Down Expand Up @@ -209,15 +214,19 @@
var propertyId = config.Current.GoogleAnalyticsPropertyId;
if (!string.IsNullOrEmpty(propertyId))
{
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
var cookieService = DependencyResolver.Current.GetService<ICookieComplianceService>();
if (cookieService.CanWriteNonEssentialCookies(Request))
{
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

ga('create', '@propertyId', 'auto');
ga('send', 'pageview');
</script>
ga('create', '@propertyId', 'auto');
ga('send', 'pageview');
</script>
}
}
}
}
Expand Down
Loading

0 comments on commit ed45532

Please sign in to comment.