Skip to content

Commit 73823ce

Browse files
committed
URL Inspection tool integration draft
1 parent 0f38f60 commit 73823ce

File tree

16 files changed

+497
-1
lines changed

16 files changed

+497
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
function urlInspectionToolController(notificationsService, umbracoCmsIntegrationsGoogleSearchConsoleUrlInspectionToolResource) {
2+
var vm = this;
3+
4+
vm.loading = false;
5+
vm.isConnected = false;
6+
7+
umbracoCmsIntegrationsGoogleSearchConsoleUrlInspectionToolResource.getAuthorizationUrl().then(function(response) {
8+
vm.authorizationUrl = response;
9+
});
10+
11+
vm.onConnectClick = function() {
12+
vm.authorizationWindow = window.open(vm.authorizationUrl,
13+
"GoogleSearchConsole_Authorize",
14+
"width=900,height=700,modal=yes,alwaysRaised=yes");
15+
}
16+
17+
vm.onRevokeToken = function() {
18+
revokeToken();
19+
}
20+
21+
// authorization listener
22+
window.addEventListener("message", function (event) {
23+
if (event.data.type === "google:oauth:success") {
24+
25+
var codeParam = "?code=";
26+
27+
vm.authorizationWindow.close();
28+
29+
var code = event.data.url.slice(event.data.url.indexOf(codeParam) + codeParam.length);
30+
31+
umbracoCmsIntegrationsGoogleSearchConsoleUrlInspectionToolResource.getAccessToken(code).then(function (response) {
32+
if (response !== "error") {
33+
vm.isConnected = true;
34+
notificationsService.success("Google Search Console Authentication", "Access Approved");
35+
} else {
36+
notificationsService.error("Google Search Console Authentication", "Access Denied");
37+
}
38+
});
39+
} else if (event.data.type === "google:oauth:denied") {
40+
vm.showError("Google Search Console Authentication", "Access Denied");
41+
42+
vm.authWindow.close();
43+
}
44+
45+
}, false);
46+
47+
function revokeToken() {
48+
umbracoCmsIntegrationsGoogleSearchConsoleUrlInspectionToolResource.revokeToken().then(function () {
49+
vm.isConnected = false;
50+
});
51+
}
52+
}
53+
54+
angular.module("umbraco")
55+
.controller("UmbracoCms.Integrations.GoogleSearchConsole.UrlInspectionToolController", urlInspectionToolController)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
function urlInspectionToolResource($http, umbRequestHelper) {
2+
3+
const apiEndpoint = "backoffice/UmbracoCmsIntegrationsGoogleSearchConsole/UrlInspectionApi";
4+
5+
return {
6+
getAuthorizationUrl: function () {
7+
return umbRequestHelper.resourcePromise(
8+
$http.get(`${apiEndpoint}/GetAuthorizationUrl`),
9+
"Failed to retrieve resource");
10+
},
11+
getAccessToken: function (authorizationCode) {
12+
return umbRequestHelper.resourcePromise(
13+
$http.post(`${apiEndpoint}/GetAccessToken`, { code: authorizationCode }), "Failed to retrieve resource");
14+
},
15+
revokeToken: function () {
16+
return umbRequestHelper.resourcePromise(
17+
$http.post(`${apiEndpoint}/RevokeToken`), "Failed to retrieve resource");
18+
},
19+
};
20+
}
21+
22+
angular.module("umbraco.resources")
23+
.factory("umbracoCmsIntegrationsGoogleSearchConsoleUrlInspectionToolResource", urlInspectionToolResource);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<language>
3+
<area alias="urlInspectionTool">
4+
<key alias="title">URL Inspection Tool</key>
5+
</area>
6+
</language>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"javascript": [
3+
"~/App_Plugins/UmbracoCms.Integrations/SEO/GoogleSearchConsole/URLInspectionTool/js/url-inspection-tool.resource.js",
4+
"~/App_Plugins/UmbracoCms.Integrations/SEO/GoogleSearchConsole/URLInspectionTool/js/url-inspection-tool.controller.js"
5+
],
6+
"css": []
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<div ng-controller="UmbracoCms.Integrations.GoogleSearchConsole.UrlInspectionToolController as vm">
2+
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
3+
<umb-box>
4+
<umb-box-header title-key="urlInspectionTool_title">
5+
<umb-button action="vm.onConnectClick()"
6+
type="button"
7+
button-style="primary"
8+
state="init"
9+
label="Connect"
10+
disabled="vm.isConnected">
11+
</umb-button>
12+
<umb-button action="vm.onRevokeToken()"
13+
type="button"
14+
button-style="danger"
15+
state="init"
16+
label="Revoke"
17+
disabled="!vm.isConnected">
18+
</umb-button>
19+
</umb-box-header>
20+
<umb-box-content>
21+
22+
</umb-box-content>
23+
</umb-box>
24+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#if NETCOREAPP
2+
using Microsoft.AspNetCore.Mvc;
3+
4+
using Umbraco.Cms.Web.BackOffice.Controllers;
5+
using Umbraco.Cms.Web.Common.Attributes;
6+
#else
7+
using System.Web.Http;
8+
9+
using Umbraco.Web.Mvc;
10+
using Umbraco.Web.WebApi;
11+
#endif
12+
13+
using System;
14+
using System.Collections.Generic;
15+
using System.Net.Http;
16+
using System.Threading.Tasks;
17+
using Umbraco.Cms.Integrations.SEO.GoogleSearchConsole.URLInspectionTool.Models.Dtos;
18+
using Umbraco.Cms.Integrations.SEO.GoogleSearchConsole.URLInspectionTool.Services;
19+
20+
21+
namespace Umbraco.Cms.Integrations.SEO.GoogleSearchConsole.URLInspectionTool.Controllers
22+
{
23+
[PluginController("UmbracoCmsIntegrationsGoogleSearchConsole")]
24+
public class UrlInspectionApiController : UmbracoAuthorizedApiController
25+
{
26+
// Using a static HttpClient (see: https://www.aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/).
27+
private readonly static HttpClient s_client = new HttpClient();
28+
29+
// Access to the client within the class is via ClientFactory(), allowing us to mock the responses in tests.
30+
internal static Func<HttpClient> ClientFactory = () => s_client;
31+
32+
private readonly GoogleService _googleService;
33+
34+
private readonly ITokenService _tokenService;
35+
36+
public UrlInspectionApiController(GoogleService googleService, ITokenService tokenService)
37+
{
38+
_googleService = googleService;
39+
40+
_tokenService = tokenService;
41+
}
42+
43+
[HttpGet]
44+
public string GetAuthorizationUrl() => _googleService.GetAuthorizationUrl();
45+
46+
[HttpPost]
47+
public async Task<string> GetAccessToken([FromBody] AuthorizationRequestDto authorizationRequestDto)
48+
{
49+
var requestData = new Dictionary<string, string>
50+
{
51+
{"code", authorizationRequestDto.Code},
52+
{"client_id", _googleService.GetClientId()},
53+
{"redirect_uri", "oauth/google"},
54+
{"grant_type", "authorization_code"}
55+
};
56+
57+
var requestMessage = new HttpRequestMessage
58+
{
59+
Method = HttpMethod.Post,
60+
RequestUri = new Uri(_googleService.GetAuthProxyTokenEndpoint()),
61+
Content = new FormUrlEncodedContent(requestData),
62+
};
63+
requestMessage.Headers.Add(_googleService.ServiceHeaderKey.Key, _googleService.ServiceHeaderKey.Value);
64+
65+
var response = await ClientFactory().SendAsync(requestMessage);
66+
if (response.IsSuccessStatusCode)
67+
{
68+
var result = await response.Content.ReadAsStringAsync();
69+
70+
_tokenService.SaveParameters(_googleService.TokenDbKey, result);
71+
72+
return result;
73+
}
74+
75+
return "error";
76+
}
77+
78+
[HttpPost]
79+
public void RevokeToken()
80+
{
81+
_tokenService.RemoveParameters(_googleService.TokenDbKey);
82+
}
83+
}
84+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+

2+
using Microsoft.Extensions.DependencyInjection;
3+
#if NETCOREAPP
4+
using Umbraco.Cms.Core.Composing;
5+
using Umbraco.Cms.Core.DependencyInjection;
6+
using Umbraco.Cms.Integrations.SEO.GoogleSearchConsole.URLInspectionTool.Services;
7+
8+
#else
9+
using Umbraco.Cms.Integrations.SEO.GoogleSearchConsole.URLInspectionTool.Services;
10+
using Umbraco.Core;
11+
using Umbraco.Core.Composing;
12+
using Umbraco.Web;
13+
#endif
14+
15+
namespace Umbraco.Cms.Integrations.SEO.GoogleSearchConsole.URLInspectionTool
16+
{
17+
public class GoogleComposer : IComposer
18+
{
19+
#if NETCOREAPP
20+
public void Compose(IUmbracoBuilder builder)
21+
{
22+
builder.ContentApps().Append<URLInspectionToolContentApp>();
23+
24+
builder.Services.AddSingleton<GoogleService>();
25+
26+
builder.Services.AddSingleton<ITokenService, TokenService>();
27+
}
28+
#else
29+
public void Compose(Composition composition)
30+
{
31+
composition.ContentApps().Append<URLInspectionToolContentApp>();
32+
33+
composition.Register<GoogleService>(Lifetime.Singleton);
34+
35+
composition.Register<ITokenService, TokenService>(Lifetime.Singleton);
36+
}
37+
#endif
38+
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Newtonsoft.Json;
2+
3+
namespace Umbraco.Cms.Integrations.SEO.GoogleSearchConsole.URLInspectionTool.Models.Dtos
4+
{
5+
public class AuthorizationRequestDto
6+
{
7+
[JsonProperty("code")]
8+
public string Code { get; set; }
9+
}
10+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using Newtonsoft.Json;
2+
3+
namespace Umbraco.Cms.Integrations.SEO.GoogleSearchConsole.URLInspectionTool.Models.Dtos
4+
{
5+
public class TokenDto
6+
{
7+
[JsonProperty("access_token")]
8+
public string AccessToken { get; set; }
9+
10+
[JsonProperty("token_type")]
11+
public string TokenType { get; set; }
12+
13+
[JsonProperty("expires_in")]
14+
public int ExpiresIn { get; set; }
15+
16+
[JsonProperty("refresh_token")]
17+
public string RefreshToken { get; set; }
18+
19+
[JsonProperty("isAccessTokenAvailable")]
20+
public bool IsAccessTokenAvailable => !string.IsNullOrEmpty(AccessToken);
21+
}
22+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+

2+
namespace Umbraco.Cms.Integrations.SEO.GoogleSearchConsole.URLInspectionTool.Services
3+
{
4+
public abstract class BaseAuthService
5+
{
6+
public abstract string GetClientId();
7+
8+
public abstract string GetAuthorizationUrl();
9+
10+
public abstract string GetAuthProxyTokenEndpoint();
11+
}
12+
}

0 commit comments

Comments
 (0)