Skip to content

Commit 2fa27b2

Browse files
committed
Added de-authentication option.
1 parent d8f14f0 commit 2fa27b2

File tree

9 files changed

+110
-51
lines changed

9 files changed

+110
-51
lines changed

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public async Task GetContactProperties_WithoutApiKeyConfigured_ReturnsEmptyColle
108108
var mockedKeyValueService = new Mock<IKeyValueService>();
109109
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, mockedAppCaches.Object, mockedKeyValueService.Object);
110110

111-
var result = await sut.GetContactProperties();
111+
var result = await sut.GetContactPropertiesAsync();
112112

113113
mockedLogger
114114
.Verify(x => x.Info(It.Is<Type>(y => y == typeof(HubspotContactService)), It.IsAny<string>()), Times.Once);
@@ -127,7 +127,7 @@ public async Task GetContactProperties_WithFailedRequest_ReturnsEmptyCollectionW
127127
var httpClient = CreateMockedHttpClient(HttpStatusCode.InternalServerError);
128128
HubspotContactService.ClientFactory = () => httpClient;
129129

130-
var result = await sut.GetContactProperties();
130+
var result = await sut.GetContactPropertiesAsync();
131131

132132
mockedLogger
133133
.Verify(x => x.Error(It.Is<Type>(y => y == typeof(HubspotContactService)), It.IsAny<string>(), It.IsAny<object[]>()), Times.Once);
@@ -146,7 +146,7 @@ public async Task GetContactProperties_WithSuccessfulRequest_ReturnsMappedAndOrd
146146
var httpClient = CreateMockedHttpClient(HttpStatusCode.OK, s_contactPropertiesResponse);
147147
HubspotContactService.ClientFactory = () => httpClient;
148148

149-
var result = await sut.GetContactProperties();
149+
var result = await sut.GetContactPropertiesAsync();
150150

151151
Assert.AreEqual(3, result.Count());
152152
Assert.AreEqual("Email,First Name,Last Name", string.Join(",", result.Select(x => x.Label)));
@@ -164,7 +164,7 @@ public async Task PostContact_WithoutApiKeyConfigured_ReturnsNotConfiguredWithLo
164164

165165
var record = new Record();
166166
var fieldMappings = new List<MappedProperty>();
167-
var result = await sut.PostContact(record, fieldMappings);
167+
var result = await sut.PostContactAsync(record, fieldMappings);
168168

169169
mockedLogger
170170
.Verify(x => x.Warn(It.Is<Type>(y => y == typeof(HubspotContactService)), It.IsAny<string>()), Times.Once);
@@ -185,7 +185,7 @@ public async Task PostContact_WithFailedRequest_ReturnsFailedWithLoggedError()
185185

186186
var record = new Record();
187187
var fieldMappings = new List<MappedProperty>();
188-
var result = await sut.PostContact(record, fieldMappings);
188+
var result = await sut.PostContactAsync(record, fieldMappings);
189189

190190
mockedLogger
191191
.Verify(x => x.Error(It.Is<Type>(y => y == typeof(HubspotContactService)), It.IsAny<string>()), Times.Once);
@@ -220,7 +220,7 @@ public async Task PostContact_WithSuccessfulRequest_ReturnSuccess()
220220
HubspotField = "firstname"
221221
}
222222
};
223-
var result = await sut.PostContact(record, fieldMappings);
223+
var result = await sut.PostContactAsync(record, fieldMappings);
224224

225225
mockedLogger
226226
.Verify(x => x.Warn(It.Is<Type>(y => y == typeof(HubspotContactService)), It.IsAny<string>(), It.IsAny<object[]>()), Times.Never);
@@ -250,7 +250,7 @@ public async Task PostContact_WithSuccessfulRequestAndUnmappedField_ReturnSucces
250250
HubspotField = "firstname"
251251
}
252252
};
253-
var result = await sut.PostContact(record, fieldMappings);
253+
var result = await sut.PostContactAsync(record, fieldMappings);
254254

255255
mockedLogger
256256
.Verify(x => x.Warn(It.Is<Type>(y => y == typeof(HubspotContactService)), It.IsAny<string>(), It.IsAny<object[]>()), Times.Once);

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

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

7-
<div ng-show="!vm.loading && !vm.isAuthorizationConfigured">
7+
<div ng-show="!vm.loading && vm.authorizationStatus === 'Unauthenticated'">
88
<p>Umbraco Forms is not configured with a HubSpot CRM account.</p>
99
<p>To do this you can either create and save an API key into the <i>UmbracoForms.config</i> file.</p>
1010
<p>Or you can follow the steps below to complete an OAuth connection.</p>
@@ -18,7 +18,7 @@
1818
<button ng-click="vm.authorize()" ng-disabled="vm.authorizationCode === ''">Authorize</button>
1919
</div>
2020

21-
<div ng-show="!vm.loading && vm.isAuthorizationConfigured">
21+
<div ng-show="!vm.loading && vm.authorizationStatus !== 'Unauthenticated'">
2222

2323
<div class="umb-forms-mappings" ng-show="vm.mappings.length > 0 && vm.hubspotFields.length > 0">
2424

@@ -61,6 +61,10 @@
6161

6262
<umb-button type="button" action="vm.addMapping()" label="Add mapping"></umb-button>
6363

64+
<div ng-show="vm.authorizationStatus === 'OAuth'" style="margin-top: 20px">
65+
<umb-button type="button" action="vm.deauthorize()" label="De-authorize from Hubspot"></umb-button>
66+
</div>
67+
6468
</div>
6569

6670
</div>

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,15 @@
2323
umbRequestHelper.getApiUrl(
2424
"umbracoFormsIntegrationsCrmHubspotBaseUrl",
2525
"Authorize"), { code: authorizationCode }),
26-
'Failed to authentication with HubSpot');
26+
'Failed to authorize with HubSpot');
27+
},
28+
deauthorize: function (authorizationCode) {
29+
return umbRequestHelper.resourcePromise(
30+
$http.post(
31+
umbRequestHelper.getApiUrl(
32+
"umbracoFormsIntegrationsCrmHubspotBaseUrl",
33+
"Deauthorize")),
34+
'Failed to de-authorize with HubSpot');
2735
},
2836
getAllProperties: function () {
2937
return umbRequestHelper.resourcePromise(

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

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ angular
1010
}
1111
);
1212

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

1616
vm.authorizationCode = "";
1717
vm.authenticationUrl = "";
1818
vm.loading = false;
19-
vm.isAuthorizationConfigured = false;
19+
vm.authorizationStatus = "Unauthenticated";
2020

2121
function getFieldsForMapping() {
2222

@@ -50,13 +50,12 @@ function HubSpotFieldsController($routeParams, umbracoFormsIntegrationsCrmHubspo
5050

5151
umbracoFormsIntegrationsCrmHubspotResource.isAuthorizationConfigured().then(function (response) {
5252
vm.loading = false;
53-
if (response) {
54-
console.log('ok');
55-
vm.isAuthorizationConfigured = true;
53+
if (response !== "Unauthenticated") {
54+
vm.authorizationStatus = response;
5655
getFieldsForMapping();
5756
}
5857
else {
59-
vm.isAuthorizationConfigured = false;
58+
vm.authorizationStatus = "Unauthenticated";
6059
umbracoFormsIntegrationsCrmHubspotResource.getAuthenticationUrl().then(function (response) {
6160
vm.authenticationUrl = response;
6261
});
@@ -67,7 +66,8 @@ function HubSpotFieldsController($routeParams, umbracoFormsIntegrationsCrmHubspo
6766
vm.authorize = function () {
6867
umbracoFormsIntegrationsCrmHubspotResource.authorize(vm.authorizationCode).then(function (response) {
6968
if (response.success) {
70-
vm.isAuthorizationConfigured = true;
69+
vm.authorizationStatus = "OAuth";
70+
vm.authorizationCode = "";
7171
notificationsService.showNotification({
7272
type: 0,
7373
header: "Authorization succeeded",
@@ -84,6 +84,42 @@ function HubSpotFieldsController($routeParams, umbracoFormsIntegrationsCrmHubspo
8484
});
8585
}
8686

87+
vm.deauthorize = function () {
88+
89+
var overlay = {
90+
view: "confirm",
91+
title: "Confirmation",
92+
content: "Are you sure you wish to disconnect your HubSpot account?",
93+
closeButtonLabel: "No",
94+
submitButtonLabel: "Yes",
95+
submitButtonStyle: "danger",
96+
close: function () {
97+
overlayService.close();
98+
},
99+
submit: function () {
100+
umbracoFormsIntegrationsCrmHubspotResource.deauthorize().then(function (response) {
101+
if (response.success) {
102+
vm.authorizationStatus = "Unauthenticated";
103+
notificationsService.showNotification({
104+
type: 0,
105+
header: "De-authorization succeeded",
106+
message: "Your Umbraco Forms installation is no longer connected to your HubSpot account",
107+
});
108+
getFieldsForMapping();
109+
} else {
110+
notificationsService.showNotification({
111+
type: 2,
112+
header: "De-authorization failed",
113+
message: response.errorMessage
114+
});
115+
}
116+
overlayService.close();
117+
});
118+
}
119+
};
120+
overlayService.open(overlay);
121+
}
122+
87123
vm.getHubspotFieldDescription = function (value) {
88124
if (!vm.hubspotFields) {
89125
return "";

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,18 @@ public HubspotController(IContactService contactService)
2020
}
2121

2222
[HttpGet]
23-
public bool IsAuthorizationConfigured() => _contactService.IsAuthorizationConfigured();
23+
public string IsAuthorizationConfigured() => _contactService.IsAuthorizationConfigured().ToString();
2424

2525
[HttpGet]
2626
public string GetAuthenticationUrl() => _contactService.GetAuthenticationUrl();
2727

2828
[HttpPost]
29-
public async Task<AuthorizationResult> Authorize([FromBody] AuthorizationRequest request) => await _contactService.Authorize(request.Code);
29+
public async Task<AuthorizationResult> Authorize([FromBody] AuthorizationRequest request) => await _contactService.AuthorizeAsync(request.Code);
30+
31+
[HttpPost]
32+
public AuthorizationResult Deauthorize() => _contactService.Deauthorize();
3033

3134
[HttpGet]
32-
public async Task<IEnumerable<Property>> GetAllProperties() => await _contactService.GetContactProperties();
35+
public async Task<IEnumerable<Property>> GetAllProperties() => await _contactService.GetContactPropertiesAsync();
3336
}
3437
}

src/Umbraco.Forms.Integrations.Crm.Hubspot/HubspotWorkflow.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public override WorkflowExecutionStatus Execute(Record record, RecordEventArgs e
4141
return WorkflowExecutionStatus.NotConfigured;
4242
}
4343

44-
var commandResult = _contactService.PostContact(record, fieldMappings).GetAwaiter().GetResult();
44+
var commandResult = _contactService.PostContactAsync(record, fieldMappings).GetAwaiter().GetResult();
4545
switch (commandResult)
4646
{
4747
case CommandResult.NotConfigured:
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
namespace Umbraco.Forms.Integrations.Crm.Hubspot.Services
22
{
3-
public enum HubspotAuthenticationMode
3+
public enum AuthenticationMode
44
{
55
Unauthenticated,
66
ApiKey,
77
OAuth
88
}
99

10-
public class HubspotAuthentication
10+
public class AuthenticationDetail
1111
{
1212
public string ApiKey { get; set; }
1313

1414
public string RefreshToken { get; set; }
1515

16-
public HubspotAuthenticationMode Mode =>
16+
public AuthenticationMode Mode =>
1717
!string.IsNullOrEmpty(ApiKey)
18-
? HubspotAuthenticationMode.ApiKey
18+
? AuthenticationMode.ApiKey
1919
: !string.IsNullOrEmpty(RefreshToken)
20-
? HubspotAuthenticationMode.OAuth
21-
: HubspotAuthenticationMode.Unauthenticated;
20+
? AuthenticationMode.OAuth
21+
: AuthenticationMode.Unauthenticated;
2222
}
2323
}

src/Umbraco.Forms.Integrations.Crm.Hubspot/Services/HubspotContactService.cs

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ public HubspotContactService(IFacadeConfiguration configuration, ILogger logger,
5252
_keyValueService = keyValueService;
5353
}
5454

55-
public bool IsAuthorizationConfigured() => TryGetConfiguredAuthenticationDetails(out HubspotAuthentication _);
55+
public AuthenticationMode IsAuthorizationConfigured() => GetConfiguredAuthenticationDetails().Mode;
5656

5757
public string GetAuthenticationUrl() => string.Format(InstallUrlFormat, OAauthClientId, OAuthRedirectUrl, OAuthScopes);
5858

59-
public async Task<AuthorizationResult> Authorize(string code)
59+
public async Task<AuthorizationResult> AuthorizeAsync(string code)
6060
{
6161
var tokenRequest = new GetTokenRequest
6262
{
@@ -86,9 +86,17 @@ public async Task<AuthorizationResult> Authorize(string code)
8686
}
8787
}
8888

89-
public async Task<IEnumerable<Property>> GetContactProperties()
89+
public AuthorizationResult Deauthorize()
9090
{
91-
if (!TryGetConfiguredAuthenticationDetails(out HubspotAuthentication authenticationDetails))
91+
// Delete any saved refresh token.
92+
_keyValueService.SetValue(RefreshTokenDatabaseKey, string.Empty);
93+
return AuthorizationResult.AsSuccess();
94+
}
95+
96+
public async Task<IEnumerable<Property>> GetContactPropertiesAsync()
97+
{
98+
var authenticationDetails = GetConfiguredAuthenticationDetails();
99+
if (authenticationDetails.Mode == AuthenticationMode.Unauthenticated)
92100
{
93101
_logger.Info<HubspotContactService>("Cannot access HubSpot API via API key or OAuth, as neither a key has been configured nor a refresh token stored.");
94102
return Enumerable.Empty<Property>();
@@ -119,9 +127,10 @@ public async Task<IEnumerable<Property>> GetContactProperties()
119127
return properties.OrderBy(x => x.Label);
120128
}
121129

122-
public async Task<CommandResult> PostContact(Record record, List<MappedProperty> fieldMappings)
130+
public async Task<CommandResult> PostContactAsync(Record record, List<MappedProperty> fieldMappings)
123131
{
124-
if (!TryGetConfiguredAuthenticationDetails(out HubspotAuthentication authenticationDetails))
132+
var authenticationDetails = GetConfiguredAuthenticationDetails();
133+
if (authenticationDetails.Mode == AuthenticationMode.Unauthenticated)
125134
{
126135
_logger.Warn<HubspotContactService>("Cannot access HubSpot API via API key or OAuth, as neither a key has been configured nor a refresh token stored.");
127136
return CommandResult.NotConfigured;
@@ -166,22 +175,19 @@ public async Task<CommandResult> PostContact(Record record, List<MappedProperty>
166175
return CommandResult.Failed;
167176
}
168177

169-
private bool TryGetConfiguredAuthenticationDetails(out HubspotAuthentication authentication)
178+
private AuthenticationDetail GetConfiguredAuthenticationDetails()
170179
{
171-
authentication = new HubspotAuthentication();
180+
var authentication = new AuthenticationDetail();
172181
if (TryGetApiKey(out string apiKey))
173182
{
174183
authentication.ApiKey = apiKey;
175-
return true;
176184
}
177-
178-
if (TryGetSavedRefreshToken(out string refreshToken))
185+
else if (TryGetSavedRefreshToken(out string refreshToken))
179186
{
180-
authentication.RefreshToken= refreshToken;
181-
return true;
187+
authentication.RefreshToken = refreshToken;
182188
}
183189

184-
return false;
190+
return authentication;
185191
}
186192

187193
private bool TryGetApiKey(out string apiKey)
@@ -248,7 +254,7 @@ private async Task RefreshOAuthAccessToken(string refreshToken)
248254
private async Task<HttpResponseMessage> GetResponse(
249255
string url,
250256
HttpMethod httpMethod,
251-
HubspotAuthentication authenticationDetails = null,
257+
AuthenticationDetail authenticationDetails = null,
252258
object content = null,
253259
string contentType = null)
254260
{
@@ -264,10 +270,10 @@ private async Task<HttpResponseMessage> GetResponse(
264270
// Apply appropriate authentication details to the request. Either the API key as a querystring or the access token as a header.
265271
switch (authenticationDetails.Mode)
266272
{
267-
case HubspotAuthenticationMode.ApiKey:
268-
requestMessage.RequestUri = new Uri($"{CrmApiBaseUrl}{url}?hapikey={authenticationDetails.ApiKey}");
273+
case AuthenticationMode.ApiKey:
274+
requestMessage.RequestUri = new Uri($"{url}?hapikey={authenticationDetails.ApiKey}");
269275
break;
270-
case HubspotAuthenticationMode.OAuth:
276+
case AuthenticationMode.OAuth:
271277
requestMessage.Headers.Authorization =
272278
new AuthenticationHeaderValue("Bearer", await GetOAuthAccessTokenFromCacheOrRefreshToken(authenticationDetails.RefreshToken));
273279
break;
@@ -296,10 +302,10 @@ private static HttpContent CreateRequestContent(object data, string contentType)
296302
}
297303
}
298304

299-
private async Task<HandleFailedRequestResult> HandleFailedRequest(HttpStatusCode statusCode, string requestUrl, HttpMethod httpMethod, HubspotAuthentication authenticationDetails)
305+
private async Task<HandleFailedRequestResult> HandleFailedRequest(HttpStatusCode statusCode, string requestUrl, HttpMethod httpMethod, AuthenticationDetail authenticationDetails)
300306
{
301307
var result = new HandleFailedRequestResult();
302-
if (authenticationDetails.Mode == HubspotAuthenticationMode.OAuth)
308+
if (authenticationDetails.Mode == AuthenticationMode.OAuth)
303309
{
304310
if (statusCode == HttpStatusCode.Unauthorized)
305311
{

src/Umbraco.Forms.Integrations.Crm.Hubspot/Services/IContactService.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@ public enum CommandResult
1616

1717
public interface IContactService
1818
{
19-
bool IsAuthorizationConfigured();
19+
AuthenticationMode IsAuthorizationConfigured();
2020

2121
string GetAuthenticationUrl();
2222

23-
Task<AuthorizationResult> Authorize(string code);
23+
Task<AuthorizationResult> AuthorizeAsync(string code);
2424

25-
Task<IEnumerable<Property>> GetContactProperties();
25+
AuthorizationResult Deauthorize();
2626

27-
Task<CommandResult> PostContact(Record record, List<MappedProperty> fieldMappings);
27+
Task<IEnumerable<Property>> GetContactPropertiesAsync();
28+
29+
Task<CommandResult> PostContactAsync(Record record, List<MappedProperty> fieldMappings);
2830
}
2931
}

0 commit comments

Comments
 (0)