Skip to content

Commit 11f8a51

Browse files
committed
Implemented OAuth flow using common redirect site.
1 parent 43dc654 commit 11f8a51

File tree

11 files changed

+338
-193
lines changed

11 files changed

+338
-193
lines changed

src/Umbraco.Forms.Integrations.Crm.Hubspot.OAuthCallback/Pages/Index.cshtml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,8 @@
1212
}
1313
else
1414
{
15-
<p>To complete the integration, please add the following code to your <i>UmbracoForms.config</i> file as a setting using the key <i>HubSpotOAuthAuthorizationCode</i>.</p>
15+
<p>To complete the integration, please copy the following code and enter it in Umbraco using the form provided.</p>
1616

1717
<p><input type="text" value="@Model.AuthorizationCode" readonly style="width: 700px; font-size: 36px; text-align: center" /></p>
18-
19-
<p>When complete, the configuration file should look as follows:</p>
20-
<pre style="text-align: left; padding: 10px; border: solid 1px gray">
21-
&lt;setting key="HubSpotAuthenticationMode" value="OAuth" /&gt;
22-
&lt;setting key="HubSpotOAuthAuthorizationCode" value="@Model.AuthorizationCode" /&gt;</pre>
2318
}
2419
</div>

src/Umbraco.Forms.Integrations.Crm.Hubspot.Tests/Services/HubspotContactServiceTests.cs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Threading.Tasks;
1111
using Umbraco.Core.Cache;
1212
using Umbraco.Core.Logging;
13+
using Umbraco.Core.Services;
1314
using Umbraco.Forms.Core;
1415
using Umbraco.Forms.Core.Persistence.Dtos;
1516
using Umbraco.Forms.Core.Providers.Models;
@@ -104,12 +105,13 @@ public async Task GetContactProperties_WithoutApiKeyConfigured_ReturnsEmptyColle
104105
Mock<IFacadeConfiguration> mockedConfig = CreateMockedConfiguration(withApiKey: false);
105106
var mockedLogger = new Mock<ILogger>();
106107
var mockedAppCaches = new Mock<AppCaches>();
107-
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, mockedAppCaches.Object);
108+
var mockedKeyValueService = new Mock<IKeyValueService>();
109+
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, mockedAppCaches.Object, mockedKeyValueService.Object);
108110

109111
var result = await sut.GetContactProperties();
110112

111113
mockedLogger
112-
.Verify(x => x.Warn(It.Is<Type>(y => y == typeof(HubspotContactService)), It.IsAny<string>()), Times.Once);
114+
.Verify(x => x.Info(It.Is<Type>(y => y == typeof(HubspotContactService)), It.IsAny<string>()), Times.Once);
113115
Assert.IsEmpty(result);
114116
}
115117

@@ -119,7 +121,8 @@ public async Task GetContactProperties_WithFailedRequest_ReturnsEmptyCollectionW
119121
Mock<IFacadeConfiguration> mockedConfig = CreateMockedConfiguration();
120122
var mockedLogger = new Mock<ILogger>();
121123
var mockedAppCaches = new Mock<AppCaches>();
122-
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, mockedAppCaches.Object);
124+
var mockedKeyValueService = new Mock<IKeyValueService>();
125+
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, mockedAppCaches.Object, mockedKeyValueService.Object);
123126

124127
var httpClient = CreateMockedHttpClient(HttpStatusCode.InternalServerError);
125128
HubspotContactService.ClientFactory = () => httpClient;
@@ -137,7 +140,8 @@ public async Task GetContactProperties_WithSuccessfulRequest_ReturnsMappedAndOrd
137140
Mock<IFacadeConfiguration> mockedConfig = CreateMockedConfiguration();
138141
var mockedLogger = new Mock<ILogger>();
139142
var mockedAppCaches = new Mock<AppCaches>();
140-
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, mockedAppCaches.Object);
143+
var mockedKeyValueService = new Mock<IKeyValueService>();
144+
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, mockedAppCaches.Object, mockedKeyValueService.Object);
141145

142146
var httpClient = CreateMockedHttpClient(HttpStatusCode.OK, s_contactPropertiesResponse);
143147
HubspotContactService.ClientFactory = () => httpClient;
@@ -155,7 +159,8 @@ public async Task PostContact_WithoutApiKeyConfigured_ReturnsNotConfiguredWithLo
155159
Mock<IFacadeConfiguration> mockedConfig = CreateMockedConfiguration(withApiKey: false);
156160
var mockedLogger = new Mock<ILogger>();
157161
var mockedAppCaches = new Mock<AppCaches>();
158-
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, mockedAppCaches.Object);
162+
var mockedKeyValueService = new Mock<IKeyValueService>();
163+
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, mockedAppCaches.Object, mockedKeyValueService.Object);
159164

160165
var record = new Record();
161166
var fieldMappings = new List<MappedProperty>();
@@ -172,7 +177,8 @@ public async Task PostContact_WithFailedRequest_ReturnsFailedWithLoggedError()
172177
Mock<IFacadeConfiguration> mockedConfig = CreateMockedConfiguration();
173178
var mockedLogger = new Mock<ILogger>();
174179
var mockedAppCaches = new Mock<AppCaches>();
175-
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, mockedAppCaches.Object);
180+
var mockedKeyValueService = new Mock<IKeyValueService>();
181+
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, mockedAppCaches.Object, mockedKeyValueService.Object);
176182

177183
var httpClient = CreateMockedHttpClient(HttpStatusCode.InternalServerError);
178184
HubspotContactService.ClientFactory = () => httpClient;
@@ -193,7 +199,8 @@ public async Task PostContact_WithSuccessfulRequest_ReturnSuccess()
193199
Mock<IFacadeConfiguration> mockedConfig = CreateMockedConfiguration();
194200
var mockedLogger = new Mock<ILogger>();
195201
var mockedAppCaches = new Mock<AppCaches>();
196-
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, mockedAppCaches.Object);
202+
var mockedKeyValueService = new Mock<IKeyValueService>();
203+
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, mockedAppCaches.Object, mockedKeyValueService.Object);
197204

198205
var httpClient = CreateMockedHttpClient(HttpStatusCode.OK);
199206
HubspotContactService.ClientFactory = () => httpClient;
@@ -227,7 +234,8 @@ public async Task PostContact_WithSuccessfulRequestAndUnmappedField_ReturnSucces
227234
Mock<IFacadeConfiguration> mockedConfig = CreateMockedConfiguration();
228235
var mockedLogger = new Mock<ILogger>();
229236
var mockedAppCaches = new Mock<AppCaches>();
230-
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, mockedAppCaches.Object);
237+
var mockedKeyValueService = new Mock<IKeyValueService>();
238+
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, mockedAppCaches.Object, mockedKeyValueService.Object);
231239

232240
var httpClient = CreateMockedHttpClient(HttpStatusCode.OK);
233241
HubspotContactService.ClientFactory = () => httpClient;
@@ -253,9 +261,6 @@ public async Task PostContact_WithSuccessfulRequestAndUnmappedField_ReturnSucces
253261
private static Mock<IFacadeConfiguration> CreateMockedConfiguration(bool withApiKey = true)
254262
{
255263
var mockedConfiguration = new Mock<IFacadeConfiguration>();
256-
mockedConfiguration
257-
.Setup(x => x.GetSetting(It.Is<string>(y => y == "HubSpotAuthenticationMode")))
258-
.Returns("ApiKey");
259264
if (withApiKey)
260265
{
261266
mockedConfiguration

src/Umbraco.Forms.Integrations.Crm.Hubspot/App_Plugins/UmbracoForms.Integrations/Crm/Hubspot/hubspot-field-mapper-template.html

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,63 @@
44
<pre>{{ vm.mappings | json }}</pre>
55
-->
66

7-
<div class="umb-forms-mappings" ng-if="vm.mappings.length > 0 && vm.hubspotFields.length > 0">
7+
<div ng-show="!vm.loading && !vm.isAuthorizationConfigured">
8+
<p>Umbraco Forms is not configured with a HubSpot CRM account.</p>
9+
<p>To do this you can either create and save an API key into the <i>UmbracoForms.config</i> file.</p>
10+
<p>Or you can follow the steps below to complete an OAuth connection.</p>
11+
<dl>
12+
<dt>Step 1.</dt>
13+
<dd>Click <a href="{{vm.authenticationUrl}}" target="_blank" style="text-decoration: underline">here</a> to authenticate with HubSpot.</dd>
14+
<dt>Step 2.</dt>
15+
<dd>Paste the authorization code below and click to complete the authentication.</dd>
16+
</dl>
17+
<input type="text" placeholder="Enter authorization code" ng-model="vm.authorizationCode" />
18+
<button ng-click="vm.authorize()" ng-disabled="vm.authorizationCode === ''">Authorize</button>
19+
</div>
820

9-
<div class="umb-forms-mapping-header">
10-
<div class="umb-forms-mapping-field -no-margin-left">Form Field</div>
11-
<div class="umb-forms-mapping-field">Hubspot Field</div>
12-
<div class="umb-forms-mapping-remove -no-margin-right"></div>
13-
</div>
21+
<div ng-show="!vm.loading && vm.isAuthorizationConfigured">
1422

15-
<div ng-repeat="mapping in vm.mappings">
23+
<div class="umb-forms-mappings" ng-show="vm.mappings.length > 0 && vm.hubspotFields.length > 0">
1624

17-
<div class="umb-forms-mapping">
18-
<div class="umb-forms-mapping-field">
19-
<select class="-full-width"
20-
ng-options="field.id as field.value for field in vm.fields"
21-
ng-model="mapping.formField"
22-
ng-change="vm.stringifyValue()">
23-
</select>
24-
</div>
25+
<div class="umb-forms-mapping-header">
26+
<div class="umb-forms-mapping-field -no-margin-left">Form Field</div>
27+
<div class="umb-forms-mapping-field">Hubspot Field</div>
28+
<div class="umb-forms-mapping-remove -no-margin-right"></div>
29+
</div>
2530

26-
<div class="umb-forms-mapping-field">
27-
<select class="-full-width"
28-
ng-options="field.value as field.name for field in vm.hubspotFields"
29-
ng-model="mapping.hubspotField"
30-
ng-change="vm.stringifyValue()">
31-
</select>
32-
</div>
31+
<div ng-repeat="mapping in vm.mappings">
32+
33+
<div class="umb-forms-mapping">
34+
<div class="umb-forms-mapping-field">
35+
<select class="-full-width"
36+
ng-options="field.id as field.value for field in vm.fields"
37+
ng-model="mapping.formField"
38+
ng-change="vm.stringifyValue()">
39+
</select>
40+
</div>
3341

34-
<div class="umb-forms-mapping-remove -no-margin-right">
35-
<a href="" ng-click="vm.deleteMapping($index)"><i class="icon-trash"></i></a>
42+
<div class="umb-forms-mapping-field">
43+
<select class="-full-width"
44+
ng-options="field.value as field.name for field in vm.hubspotFields"
45+
ng-model="mapping.hubspotField"
46+
ng-change="vm.stringifyValue()">
47+
</select>
48+
</div>
49+
50+
<div class="umb-forms-mapping-remove -no-margin-right">
51+
<a href="" ng-click="vm.deleteMapping($index)"><i class="icon-trash"></i></a>
52+
</div>
3653
</div>
37-
</div>
3854

39-
<div ng-if="mapping.hubspotField" style="margin-bottom:15px;">
40-
<strong>Description:</strong><br/>
41-
{{ vm.getHubspotFieldDescription(mapping.hubspotField) }}
55+
<div ng-if="mapping.hubspotField" style="margin-bottom:15px;">
56+
<strong>Description:</strong><br />
57+
{{ vm.getHubspotFieldDescription(mapping.hubspotField) }}
58+
</div>
4259
</div>
4360
</div>
44-
</div>
4561

46-
<umb-button type="button" action="vm.addMapping()" label="Add mapping"></umb-button>
62+
<umb-button type="button" action="vm.addMapping()" label="Add mapping"></umb-button>
63+
64+
</div>
4765

4866
</div>

src/Umbraco.Forms.Integrations.Crm.Hubspot/App_Plugins/UmbracoForms.Integrations/Crm/Hubspot/hubspot.resource.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
function hubspotResource($http, umbRequestHelper) {
22

3-
return {
3+
return {
4+
isAuthorizationConfigured: function () {
5+
return umbRequestHelper.resourcePromise(
6+
$http.get(
7+
umbRequestHelper.getApiUrl(
8+
"umbracoFormsIntegrationsCrmHubspotBaseUrl",
9+
"IsAuthorizationConfigured")),
10+
'Failed to get Hubspot authentication status');
11+
},
12+
getAuthenticationUrl: function () {
13+
return umbRequestHelper.resourcePromise(
14+
$http.get(
15+
umbRequestHelper.getApiUrl(
16+
"umbracoFormsIntegrationsCrmHubspotBaseUrl",
17+
"GetAuthenticationUrl")),
18+
'Failed to get Hubspot authentication URL');
19+
},
20+
authorize: function (authorizationCode) {
21+
return umbRequestHelper.resourcePromise(
22+
$http.post(
23+
umbRequestHelper.getApiUrl(
24+
"umbracoFormsIntegrationsCrmHubspotBaseUrl",
25+
"Authorize"), { code: authorizationCode }),
26+
'Failed to authentication with HubSpot');
27+
},
428
getAllProperties: function () {
529
return umbRequestHelper.resourcePromise(
630
$http.get(

src/Umbraco.Forms.Integrations.Crm.Hubspot/App_Plugins/UmbracoForms.Integrations/Crm/Hubspot/hubspotfields.component.js

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,36 @@ angular
1010
}
1111
);
1212

13-
function HubSpotFieldsController($routeParams, umbracoFormsIntegrationsCrmHubspotResource, pickerResource) {
13+
function HubSpotFieldsController($routeParams, umbracoFormsIntegrationsCrmHubspotResource, pickerResource, notificationsService) {
1414
var vm = this;
1515

16+
vm.authorizationCode = "";
17+
vm.authenticationUrl = "";
18+
vm.loading = false;
19+
vm.isAuthorizationConfigured = false;
20+
21+
function getFieldsForMapping() {
22+
23+
// Get the fields for the form.
24+
var formId = $routeParams.id;
25+
if (formId !== -1) {
26+
pickerResource.getAllFields(formId).then(function (response) {
27+
vm.fields = response.data;
28+
});
29+
}
30+
31+
// Get the HubSpot contact fields.
32+
umbracoFormsIntegrationsCrmHubspotResource.getAllProperties().then(function (response) {
33+
vm.hubspotFields = response.map(x => {
34+
return {
35+
value: x.name,
36+
name: x.label,
37+
description: x.description
38+
}
39+
});
40+
});
41+
}
42+
1643
vm.$onInit = function() {
1744

1845
if (!vm.setting.value) {
@@ -21,28 +48,41 @@ function HubSpotFieldsController($routeParams, umbracoFormsIntegrationsCrmHubspo
2148
vm.mappings = JSON.parse(vm.setting.value);
2249
}
2350

24-
var formId = $routeParams.id;
25-
if (formId !== -1){
26-
27-
// Get the fields for the form.
28-
pickerResource.getAllFields(formId).then(function (response) {
29-
vm.fields = response.data;
30-
});
51+
umbracoFormsIntegrationsCrmHubspotResource.isAuthorizationConfigured().then(function (response) {
52+
vm.loading = false;
53+
if (response) {
54+
console.log('ok');
55+
vm.isAuthorizationConfigured = true;
56+
getFieldsForMapping();
57+
}
58+
else {
59+
vm.isAuthorizationConfigured = false;
60+
umbracoFormsIntegrationsCrmHubspotResource.getAuthenticationUrl().then(function (response) {
61+
vm.authenticationUrl = response;
62+
});
63+
}
64+
});
65+
}
3166

32-
// Get the HubSpot contact fields.
33-
umbracoFormsIntegrationsCrmHubspotResource.getAllProperties().then(function (response) {
34-
vm.hubspotFields = response.map(x => {
35-
return {
36-
value: x.name,
37-
name: x.label,
38-
description: x.description
39-
}
67+
vm.authorize = function () {
68+
umbracoFormsIntegrationsCrmHubspotResource.authorize(vm.authorizationCode).then(function (response) {
69+
if (response.success) {
70+
vm.isAuthorizationConfigured = true;
71+
getFieldsForMapping();
72+
} else {
73+
notificationsService.showNotification({
74+
type: 2,
75+
header: "Authorization failed",
76+
message: response.errorMessage
4077
});
41-
});
42-
}
78+
}
79+
});
4380
}
4481

45-
vm.getHubspotFieldDescription = function(value) {
82+
vm.getHubspotFieldDescription = function (value) {
83+
if (!vm.hubspotFields) {
84+
return "";
85+
}
4686
var item = vm.hubspotFields.find(x => {
4787
return x.value === value;
4888
});
@@ -51,7 +91,7 @@ function HubSpotFieldsController($routeParams, umbracoFormsIntegrationsCrmHubspo
5191
return item.description;
5292
}
5393

54-
return '';
94+
return "";
5595
}
5696

5797
vm.addMapping = function () {

src/Umbraco.Forms.Integrations.Crm.Hubspot/Controllers/HubspotController.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System.Collections.Generic;
22
using System.Threading.Tasks;
3+
using System.Web.Http;
4+
using Umbraco.Forms.Integrations.Crm.Hubspot.Models.Dtos;
35
using Umbraco.Forms.Integrations.Crm.Hubspot.Models.Responses;
46
using Umbraco.Forms.Integrations.Crm.Hubspot.Services;
57
using Umbraco.Web.Editors;
@@ -17,11 +19,16 @@ public HubspotController(IContactService contactService)
1719
_contactService = contactService;
1820
}
1921

20-
/// <summary>
21-
/// ~/Umbraco/[YourAreaName]/[YourControllerName]
22-
/// ~/Umbraco/FormsExtensions/Hubspot/GetAllProperties
23-
/// </summary>
24-
/// <returns></returns>
22+
[HttpGet]
23+
public bool IsAuthorizationConfigured() => _contactService.IsAuthorizationConfigured();
24+
25+
[HttpGet]
26+
public string GetAuthenticationUrl() => _contactService.GetAuthenticationUrl();
27+
28+
[HttpPost]
29+
public async Task<AuthorizationResult> Authorize([FromBody] AuthorizationRequest request) => await _contactService.Authorize(request.Code);
30+
31+
[HttpGet]
2532
public async Task<IEnumerable<Property>> GetAllProperties() => await _contactService.GetContactProperties();
2633
}
2734
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Newtonsoft.Json;
2+
3+
namespace Umbraco.Forms.Integrations.Crm.Hubspot.Models.Dtos
4+
{
5+
public class AuthorizationRequest
6+
{
7+
[JsonProperty("code")]
8+
public string Code { get; set; }
9+
}
10+
}

0 commit comments

Comments
 (0)