Skip to content

Commit a4db6da

Browse files
authored
Automatically refresh secrets values in Office365 Source plugin (#6105)
Refresh Office365 secrets from secrets plugin. Move secretValue assignemnet to refresh() Signed-off-by: Brendan Benner <bbenner@amazon.com>
1 parent bb2dfa1 commit a4db6da

14 files changed

Lines changed: 430 additions & 119 deletions

File tree

data-prepper-plugins/aws-plugin/src/main/java/org/opensearch/dataprepper/plugins/aws/AwsPluginConfigVariable.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public void setValue(Object newValue) {
5050
@Override
5151
public void refresh() {
5252
secretsSupplier.refresh(secretId);
53+
this.secretValue = secretsSupplier.retrieveValue(secretId, secretKey);
5354
}
5455

5556

data-prepper-plugins/aws-plugin/src/test/java/org/opensearch/dataprepper/plugins/aws/AwsPluginConfigVariableTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ void testSetValueSuccess(final String input) {
8888
void testRefreshSecretsWithKey() {
8989
objectUnderTest.refresh();
9090
verify(secretsSupplier, times(1)).refresh(secretId);
91+
verify(secretsSupplier, times(1)).retrieveValue(secretId, secretKey);
9192
}
9293

9394
}

data-prepper-plugins/saas-source-plugins/microsoft-office365-source/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ dependencies {
2929
annotationProcessor 'org.projectlombok:lombok:1.18.30'
3030

3131
testImplementation project(':data-prepper-test:test-common')
32+
testImplementation testLibs.awaitility
3233

3334
implementation(libs.spring.context) {
3435
exclude group: 'commons-logging', module: 'commons-logging'
3536
}
3637
implementation(libs.spring.web)
37-
}
38+
}

data-prepper-plugins/saas-source-plugins/microsoft-office365-source/src/main/java/org/opensearch/dataprepper/plugins/source/microsoft_office365/Office365Source.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,11 @@ public Office365Source(final PluginMetrics pluginMetrics,
7373
public void start(Buffer<Record<Event>> buffer) {
7474
LOG.info("Starting Office365 Source Plugin...");
7575
try {
76+
// Initialize credentials with retry logic - this will not throw exceptions
77+
// but will retry infinitely
7678
office365AuthProvider.initCredentials();
79+
80+
// Only proceed with service initialization if credentials are available
7781
office365Service.initializeSubscriptions();
7882
super.start(buffer);
7983
} catch (Exception e) {

data-prepper-plugins/saas-source-plugins/microsoft-office365-source/src/main/java/org/opensearch/dataprepper/plugins/source/microsoft_office365/auth/AuthenticationConfiguration.java

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,5 @@
1717
public class AuthenticationConfiguration {
1818
@JsonProperty("oauth2")
1919
@NotNull
20-
private OAuth2Credentials oauth2;
21-
22-
@Getter
23-
public static class OAuth2Credentials {
24-
@JsonProperty("client_id")
25-
@NotNull
26-
private String clientId;
27-
28-
@JsonProperty("client_secret")
29-
@NotNull
30-
private String clientSecret;
31-
}
20+
private Oauth2Config oauth2;
3221
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
8+
*
9+
*/
10+
11+
package org.opensearch.dataprepper.plugins.source.microsoft_office365.auth;
12+
13+
import com.fasterxml.jackson.annotation.JsonProperty;
14+
import lombok.Getter;
15+
import org.opensearch.dataprepper.model.plugin.PluginConfigVariable;
16+
17+
@Getter
18+
public class Oauth2Config {
19+
@JsonProperty("client_id")
20+
private PluginConfigVariable clientId;
21+
22+
@JsonProperty("client_secret")
23+
private PluginConfigVariable clientSecret;
24+
}

data-prepper-plugins/saas-source-plugins/microsoft-office365-source/src/main/java/org/opensearch/dataprepper/plugins/source/microsoft_office365/auth/Office365AuthenticationInterface.java

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@
99

1010
package org.opensearch.dataprepper.plugins.source.microsoft_office365.auth;
1111

12+
import org.opensearch.dataprepper.plugins.source.source_crawler.auth.AuthenticationInterface;
13+
1214
/**
1315
* Interface for Office 365 authentication provider.
16+
* Extends the generic SaasAuthenticationProvider with Office 365-specific methods.
1417
*/
15-
public interface Office365AuthenticationInterface {
18+
public interface Office365AuthenticationInterface extends AuthenticationInterface {
1619
/**
1720
* Gets the tenant ID for the Office 365 application.
1821
*
@@ -26,14 +29,4 @@ public interface Office365AuthenticationInterface {
2629
* @return The access token
2730
*/
2831
String getAccessToken();
29-
30-
/**
31-
* Initializes the credentials.
32-
*/
33-
void initCredentials();
34-
35-
/**
36-
* Renews the credentials.
37-
*/
38-
void renewCredentials();
3932
}

data-prepper-plugins/saas-source-plugins/microsoft-office365-source/src/main/java/org/opensearch/dataprepper/plugins/source/microsoft_office365/auth/Office365AuthenticationProvider.java

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
import org.springframework.http.ResponseEntity;
2121
import org.springframework.stereotype.Component;
2222
import org.springframework.web.client.RestTemplate;
23+
import org.springframework.util.StringUtils;
2324

2425
import java.time.Instant;
2526
import java.util.Map;
27+
import java.util.concurrent.atomic.AtomicBoolean;
2628

2729
/**
2830
* OAuth2 implementation of the Office365AuthProvider.
@@ -36,34 +38,28 @@ public class Office365AuthenticationProvider implements Office365AuthenticationI
3638
"&client_id=%s" +
3739
"&client_secret=%s" +
3840
"&scope=%s";
39-
41+
4042
private final RestTemplate restTemplate = new RestTemplate();
41-
private final String clientId;
42-
private final String clientSecret;
4343
private final String tenantId;
44+
private final Office365SourceConfig office365SourceConfig;
45+
private String accessToken;
4446
private final Object lock = new Object();
47+
private final Object accessTokenFetchLock = new Object();
48+
private final AtomicBoolean credentialsInitialized = new AtomicBoolean(false);
4549

4650
@Getter
47-
private String accessToken;
4851
private Instant expireTime = Instant.ofEpochMilli(0);
4952

5053
public Office365AuthenticationProvider(Office365SourceConfig config) {
51-
this.clientId = config.getAuthenticationConfiguration().getOauth2().getClientId();
52-
this.clientSecret = config.getAuthenticationConfiguration().getOauth2().getClientSecret();
5354
this.tenantId = config.getTenantId();
55+
this.office365SourceConfig = config;
5456
}
5557

5658
@Override
5759
public String getTenantId() {
5860
return this.tenantId;
5961
}
6062

61-
@Override
62-
public void initCredentials() {
63-
log.info("Initializing credentials.");
64-
renewCredentials();
65-
}
66-
6763
@Override
6864
public void renewCredentials() {
6965
synchronized(lock) {
@@ -72,10 +68,13 @@ public void renewCredentials() {
7268
HttpHeaders headers = new HttpHeaders();
7369
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
7470

75-
String payload = String.format(ACCESS_TOKEN_REQUEST_BODY, clientId, clientSecret, MANAGEMENT_API_SCOPE);
71+
Oauth2Config oAuthConfig = office365SourceConfig.getAuthenticationConfiguration().getOauth2();
72+
oAuthConfig.getClientId().refresh();
73+
oAuthConfig.getClientSecret().refresh();
74+
String payload = String.format(ACCESS_TOKEN_REQUEST_BODY, (String) oAuthConfig.getClientId().getValue(), (String) oAuthConfig.getClientSecret().getValue(), MANAGEMENT_API_SCOPE);
7675

7776
HttpEntity<String> entity = new HttpEntity<>(payload, headers);
78-
String tokenEndpoint = String.format(TOKEN_URL, tenantId);
77+
String tokenEndpoint = String.format(TOKEN_URL, office365SourceConfig.getTenantId());
7978

8079
ResponseEntity<Map> response = RetryHandler.executeWithRetry(
8180
() -> restTemplate.postForEntity(tokenEndpoint, entity, Map.class),
@@ -95,4 +94,26 @@ public void renewCredentials() {
9594
log.info("Received new access token. Expires in {} seconds", expiresIn);
9695
}
9796
}
97+
98+
@Override
99+
public String getAccessToken() {
100+
if (!StringUtils.hasLength(accessToken)) {
101+
synchronized (accessTokenFetchLock) {
102+
if (!StringUtils.hasLength(accessToken)) {
103+
initCredentials();
104+
}
105+
}
106+
}
107+
return accessToken;
108+
}
109+
110+
@Override
111+
public boolean isCredentialsInitialized() {
112+
return credentialsInitialized.get();
113+
}
114+
115+
@Override
116+
public void setCredentialsInitialized(boolean initialized) {
117+
credentialsInitialized.set(initialized);
118+
}
98119
}

data-prepper-plugins/saas-source-plugins/microsoft-office365-source/src/test/java/org/opensearch/dataprepper/plugins/source/microsoft_office365/Office365RestClientTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import org.mockito.Mock;
1717
import org.mockito.junit.jupiter.MockitoExtension;
1818
import org.opensearch.dataprepper.metrics.PluginMetrics;
19-
import org.opensearch.dataprepper.plugins.source.microsoft_office365.auth.Office365AuthenticationProvider;
19+
import org.opensearch.dataprepper.plugins.source.microsoft_office365.auth.Office365AuthenticationInterface;
2020
import org.opensearch.dataprepper.plugins.source.microsoft_office365.models.AuditLogsResponse;
2121
import org.opensearch.dataprepper.test.helper.ReflectivelySetField;
2222
import org.springframework.core.ParameterizedTypeReference;
@@ -54,7 +54,7 @@ class Office365RestClientTest {
5454
@Mock
5555
private RestTemplate restTemplate;
5656
@Mock
57-
private Office365AuthenticationProvider authConfig;
57+
private Office365AuthenticationInterface authConfig;
5858

5959
private final PluginMetrics pluginMetrics = PluginMetrics.fromNames("Office365RestClientTest", "microsoft-office365");
6060

@@ -291,4 +291,4 @@ void testTokenRenewal() {
291291
assertEquals("Bearer token-0", requestTokens.get(0), "First request should use token-0");
292292
assertEquals("Bearer token-1", requestTokens.get(1), "Second request should use token-1");
293293
}
294-
}
294+
}

data-prepper-plugins/saas-source-plugins/microsoft-office365-source/src/test/java/org/opensearch/dataprepper/plugins/source/microsoft_office365/Office365SourceConfigTest.java

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,54 +9,86 @@
99

1010
package org.opensearch.dataprepper.plugins.source.microsoft_office365;
1111

12-
import com.fasterxml.jackson.databind.ObjectMapper;
12+
import org.junit.jupiter.api.BeforeEach;
1313
import org.junit.jupiter.api.Test;
14+
import org.junit.jupiter.api.extension.ExtendWith;
15+
import org.mockito.Mock;
16+
import org.mockito.junit.jupiter.MockitoExtension;
17+
import org.opensearch.dataprepper.model.plugin.PluginConfigVariable;
18+
import org.opensearch.dataprepper.plugins.source.microsoft_office365.auth.AuthenticationConfiguration;
19+
import org.opensearch.dataprepper.plugins.source.microsoft_office365.auth.Oauth2Config;
1420

15-
import java.util.HashMap;
16-
import java.util.Map;
21+
import java.lang.reflect.Field;
1722

1823
import static org.junit.jupiter.api.Assertions.assertEquals;
1924
import static org.junit.jupiter.api.Assertions.assertFalse;
2025
import static org.junit.jupiter.api.Assertions.assertNotNull;
26+
import static org.mockito.Mockito.lenient;
2127

28+
@ExtendWith(MockitoExtension.class)
2229
class Office365SourceConfigTest {
2330
private final String tenantId = "test-tenant-id";
2431
private final String clientId = "test-client-id";
2532
private final String clientSecret = "test-client-secret";
2633

27-
private Office365SourceConfig createConfig() throws Exception {
28-
Map<String, Object> configMap = new HashMap<>();
29-
configMap.put("tenant_id", tenantId);
30-
31-
// Set up authentication configuration
32-
Map<String, Object> authMap = new HashMap<>();
33-
Map<String, String> oauth2Map = new HashMap<>();
34-
oauth2Map.put("client_id", clientId);
35-
oauth2Map.put("client_secret", clientSecret);
36-
authMap.put("oauth2", oauth2Map);
37-
configMap.put("authentication", authMap);
38-
39-
// Convert to JSON and back to create config object
40-
ObjectMapper objectMapper = new ObjectMapper();
41-
String jsonConfig = objectMapper.writeValueAsString(configMap);
42-
return objectMapper.readValue(jsonConfig, Office365SourceConfig.class);
34+
@Mock
35+
private PluginConfigVariable mockClientId;
36+
37+
@Mock
38+
private PluginConfigVariable mockClientSecret;
39+
40+
@Mock
41+
private AuthenticationConfiguration mockAuthConfig;
42+
43+
@Mock
44+
private Oauth2Config mockOauth2Config;
45+
46+
private Office365SourceConfig config;
47+
48+
@BeforeEach
49+
void setUp() throws Exception {
50+
// Set up lenient mocks to avoid UnnecessaryStubbingException
51+
lenient().when(mockClientId.getValue()).thenReturn(clientId);
52+
lenient().when(mockClientSecret.getValue()).thenReturn(clientSecret);
53+
lenient().when(mockOauth2Config.getClientId()).thenReturn(mockClientId);
54+
lenient().when(mockOauth2Config.getClientSecret()).thenReturn(mockClientSecret);
55+
lenient().when(mockAuthConfig.getOauth2()).thenReturn(mockOauth2Config);
56+
57+
// Create config and inject mocked dependencies using reflection
58+
config = new Office365SourceConfig();
59+
setField(config, "tenantId", tenantId);
60+
setField(config, "authenticationConfiguration", mockAuthConfig);
4361
}
4462

45-
@Test
46-
void testGetters() throws Exception {
47-
Office365SourceConfig config = createConfig();
63+
private void setField(Object target, String fieldName, Object value) throws Exception {
64+
Field field = target.getClass().getDeclaredField(fieldName);
65+
field.setAccessible(true);
66+
field.set(target, value);
67+
}
4868

69+
@Test
70+
void testGetters() {
4971
assertEquals(tenantId, config.getTenantId());
5072
assertNotNull(config.getAuthenticationConfiguration());
51-
assertEquals(clientId, config.getAuthenticationConfiguration().getOauth2().getClientId());
52-
assertEquals(clientSecret, config.getAuthenticationConfiguration().getOauth2().getClientSecret());
73+
assertEquals(mockClientId, config.getAuthenticationConfiguration().getOauth2().getClientId());
74+
assertEquals(mockClientSecret, config.getAuthenticationConfiguration().getOauth2().getClientSecret());
5375
}
5476

5577
@Test
56-
void testDefaultValues() throws Exception {
57-
Office365SourceConfig config = createConfig();
58-
78+
void testDefaultValues() {
5979
assertFalse(config.isAcknowledgments());
6080
assertEquals(2, config.getNumberOfWorkers());
6181
}
62-
}
82+
83+
@Test
84+
void testGetClientIdValue() {
85+
String actualClientId = (String) config.getAuthenticationConfiguration().getOauth2().getClientId().getValue();
86+
assertEquals(clientId, actualClientId);
87+
}
88+
89+
@Test
90+
void testGetClientSecretValue() {
91+
String actualClientSecret = (String) config.getAuthenticationConfiguration().getOauth2().getClientSecret().getValue();
92+
assertEquals(clientSecret, actualClientSecret);
93+
}
94+
}

0 commit comments

Comments
 (0)