Skip to content

Commit e8ec920

Browse files
authored
Merge pull request #144 from docusign/feature/added-pkce-authorization
Added pkce authorization to the flow
2 parents 2c3fbbb + a7b4857 commit e8ec920

File tree

10 files changed

+334
-129
lines changed

10 files changed

+334
-129
lines changed

pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@
3030
<rooms.version>1.4.3</rooms.version>
3131
<click.version>1.5.0</click.version>
3232
<monitor.version>1.4.0</monitor.version>
33-
<admin.version>2.0.0-RC1</admin.version>
34-
<webforms.version>1.0.2-RC12</webforms.version>
35-
<maestro.version>2.0.0-RC1</maestro.version>
33+
<admin.version>2.0.0-RC2</admin.version>
34+
<webforms.version>2.0.0-RC1</webforms.version>
35+
<maestro.version>2.0.0</maestro.version>
3636
<swagger-core-version>2.2.22</swagger-core-version>
3737
<jackson-version>2.17.2</jackson-version>
3838
<jersey2.version>3.1.8</jersey2.version>

src/main/java/com/docusign/DSConfiguration.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ public class DSConfiguration {
7070
@Value("${spring.security.oauth2.client.registration.jwt.client-id}")
7171
private String userId;
7272

73+
@Value("${spring.security.oauth2.client.registration.acg.client-secret}")
74+
private String secretUserId;
75+
76+
@Value("${spring.security.oauth2.client.provider.acg.token-uri}")
77+
private String tokenEndpoint;
78+
79+
@Value("${spring.security.oauth2.client.provider.acg.authorization-uri}")
80+
private String authorizationEndpoint;
81+
7382
@Value("${jwt.grant.sso.redirect-url}")
7483
private String jwtRedirectURL;
7584

@@ -158,7 +167,8 @@ public ManifestStructure getCodeExamplesText() {
158167
}
159168

160169
try {
161-
codeExamplesText = new ObjectMapper().readValue(loadFileData(codeExamplesManifest), ManifestStructure.class);
170+
codeExamplesText = new ObjectMapper().readValue(loadFileData(codeExamplesManifest),
171+
ManifestStructure.class);
162172
} catch (Exception e) {
163173
e.printStackTrace();
164174
}
@@ -172,8 +182,8 @@ public String loadFileData(String linkToManifestFile) throws Exception {
172182
httpConnection.setRequestMethod(HttpMethod.GET);
173183

174184
httpConnection.setRequestProperty(
175-
HttpHeaders.CONTENT_TYPE,
176-
String.valueOf(MediaType.APPLICATION_JSON));
185+
HttpHeaders.CONTENT_TYPE,
186+
String.valueOf(MediaType.APPLICATION_JSON));
177187

178188
int responseCode = httpConnection.getResponseCode();
179189

src/main/java/com/docusign/WebSecurityConfig.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
1111
import org.springframework.security.web.savedrequest.RequestCache;
1212

13+
import com.docusign.core.security.CustomAuthenticationFailureHandler;
14+
1315
@EnableWebSecurity
1416
public class WebSecurityConfig {
1517

@@ -28,24 +30,22 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
2830
try {
2931
authorize
3032
.antMatchers("/", "/error**", "/assets/**", "/ds/mustAuthenticate**",
31-
"/ds/authenticate**", "/ds/selectApi**", "/con001")
33+
"/ds/authenticate**", "/ds/selectApi**", "/con001", "/pkce")
3234
.permitAll()
3335
.anyRequest().authenticated()
3436
.and()
3537
.exceptionHandling()
3638
.authenticationEntryPoint(
37-
new LoginUrlAuthenticationEntryPoint("/ds/mustAuthenticate")
38-
);
39+
new LoginUrlAuthenticationEntryPoint("/ds/mustAuthenticate"));
3940
} catch (Exception e) {
4041
throw new RuntimeException(e);
4142
}
4243
})
4344
.requestCache().requestCache(requestCache()).and()
44-
.oauth2Login(Customizer.withDefaults())
45+
.oauth2Login(login -> login.failureHandler(new CustomAuthenticationFailureHandler()))
4546
.oauth2Client(Customizer.withDefaults())
4647
.logout(logout -> logout
47-
.logoutSuccessUrl("/")
48-
)
48+
.logoutSuccessUrl("/"))
4949
.csrf().disable();
5050

5151
return http.build();

src/main/java/com/docusign/core/controller/IndexController.java

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.docusign.core.model.AuthType;
88
import com.docusign.core.model.Session;
99
import com.docusign.core.model.User;
10+
import com.docusign.core.security.acg.ACGAuthenticationMethod;
1011
import com.docusign.core.security.jwt.JWTAuthenticationMethod;
1112
import org.apache.commons.lang3.StringUtils;
1213
import org.springframework.beans.factory.annotation.Autowired;
@@ -94,7 +95,7 @@ public String index(ModelMap model, HttpServletResponse response) throws Excepti
9495
}
9596

9697
if (config.getQuickstart().equals("true") && config.getSelectedApiIndex().equals(ApiIndex.ESIGNATURE) &&
97-
!(SecurityContextHolder.getContext().getAuthentication() instanceof OAuth2AuthenticationToken)) {
98+
!(SecurityContextHolder.getContext().getAuthentication() instanceof OAuth2AuthenticationToken)) {
9899
String site = ApiIndex.ESIGNATURE.getPathOfFirstExample();
99100
response.setStatus(response.SC_MOVED_TEMPORARILY);
100101
response.setHeader(LOCATION_HEADER, site);
@@ -112,7 +113,8 @@ public String index(ModelMap model, HttpServletResponse response) throws Excepti
112113
}
113114

114115
@GetMapping(path = "/ds/mustAuthenticate")
115-
public ModelAndView mustAuthenticateController(ModelMap model, HttpServletRequest req, HttpServletResponse resp) throws IOException {
116+
public ModelAndView mustAuthenticateController(ModelMap model, HttpServletRequest req, HttpServletResponse resp)
117+
throws IOException {
116118
model.addAttribute(LAUNCHER_TEXTS, config.getCodeExamplesText().SupportingTexts);
117119
model.addAttribute(ATTR_TITLE, config.getCodeExamplesText().SupportingTexts.LoginPage.LoginButton);
118120

@@ -125,7 +127,8 @@ public ModelAndView mustAuthenticateController(ModelMap model, HttpServletReques
125127
return new ModelAndView(new JWTAuthenticationMethod().loginUsingJWT(config, session, redirectURL));
126128
}
127129

128-
boolean isRedirectToMonitor = redirectURL.toLowerCase().contains("/m") && !redirectURL.toLowerCase().contains("/mae");
130+
boolean isRedirectToMonitor = redirectURL.toLowerCase().contains("/m") &&
131+
!redirectURL.toLowerCase().contains("/mae");
129132
if (session.isRefreshToken() || config.getQuickstart().equals("true")) {
130133
config.setQuickstart("false");
131134

@@ -148,32 +151,52 @@ private ModelAndView checkForMonitorRedirects(String redirectURL) {
148151
return new ModelAndView(new JWTAuthenticationMethod().loginUsingJWT(config, session, redirectURL));
149152
}
150153

154+
@GetMapping("/pkce")
155+
public RedirectView pkce(String code, String state, HttpServletRequest req, HttpServletResponse resp)
156+
throws Exception {
157+
String redirectURL = getRedirectURLForJWTAuthentication(req, resp);
158+
RedirectView redirect;
159+
try {
160+
redirect = new ACGAuthenticationMethod().exchangeCodeForToken(code, config, session, redirectURL);
161+
} catch (Exception e) {
162+
redirect = getRedirectView(AuthType.AGC);
163+
this.session.setIsPKCEWorking(false);
164+
}
165+
166+
return redirect;
167+
}
168+
151169
@PostMapping("/ds/authenticate")
152-
public RedirectView authenticate(ModelMap model, @RequestBody MultiValueMap<String, String> formParams, HttpServletRequest req, HttpServletResponse resp) throws IOException {
170+
public RedirectView authenticate(ModelMap model, @RequestBody MultiValueMap <String, String> formParams,
171+
HttpServletRequest req, HttpServletResponse resp) throws Exception {
153172
if (!formParams.containsKey("selectAuthType")) {
154173
model.addAttribute("message", "Select option with selectAuthType name must be provided.");
155174
return new RedirectView("pages/error");
156175
}
157176

158177
String redirectURL = getRedirectURLForJWTAuthentication(req, resp);
159178

160-
List<String> selectAuthTypeObject = formParams.get("selectAuthType");
179+
List <String> selectAuthTypeObject = formParams.get("selectAuthType");
161180
AuthType authTypeSelected = AuthType.valueOf(selectAuthTypeObject.get(0));
162181

163182
if (authTypeSelected.equals(AuthType.JWT)) {
164183
this.session.setAuthTypeSelected(AuthType.JWT);
165184
return new JWTAuthenticationMethod().loginUsingJWT(config, session, redirectURL);
166185
} else {
167186
this.session.setAuthTypeSelected(AuthType.AGC);
168-
return getRedirectView(authTypeSelected);
187+
if (this.session.getIsPKCEWorking()) {
188+
return new ACGAuthenticationMethod().initiateAuthorization(config);
189+
} else {
190+
return getRedirectView(authTypeSelected);
191+
}
169192
}
170193
}
171194

172195
private String getRedirectURLForJWTAuthentication(HttpServletRequest req, HttpServletResponse resp) {
173196
SavedRequest savedRequest = requestCache.getRequest(req, resp);
174197

175-
String[] examplesCodes = new String[]{
176-
ApiIndex.CLICK.getExamplesPathCode(),
198+
String[] examplesCodes = new String[] {
199+
ApiIndex.CLICK.getExamplesPathCode(),
177200
ApiIndex.ESIGNATURE.getExamplesPathCode(),
178201
ApiIndex.MONITOR.getExamplesPathCode(),
179202
ApiIndex.ADMIN.getExamplesPathCode(),
@@ -185,10 +208,10 @@ private String getRedirectURLForJWTAuthentication(HttpServletRequest req, HttpSe
185208
Integer indexOfExampleCodeInRedirect = StringUtils.indexOfAny(savedRequest.getRedirectUrl(), examplesCodes);
186209

187210
if (indexOfExampleCodeInRedirect != -1) {
188-
Boolean hasNumbers = savedRequest.getRedirectUrl().substring(indexOfExampleCodeInRedirect).matches(".*\\d.*");
211+
Boolean hasNumbers = savedRequest.getRedirectUrl().substring(indexOfExampleCodeInRedirect)
212+
.matches(".*\\d.*");
189213

190-
return "GET".equals(savedRequest.getMethod()) && hasNumbers ?
191-
savedRequest.getRedirectUrl() : "/";
214+
return "GET".equals(savedRequest.getMethod()) && hasNumbers ? savedRequest.getRedirectUrl() : "/";
192215
}
193216
}
194217

@@ -197,8 +220,8 @@ private String getRedirectURLForJWTAuthentication(HttpServletRequest req, HttpSe
197220

198221
@GetMapping(path = "/ds-return")
199222
public String returnController(@RequestParam(value = ATTR_STATE, required = false) String state,
200-
@RequestParam(value = ATTR_EVENT, required = false) String event,
201-
@RequestParam(required = false) String envelopeId, ModelMap model) {
223+
@RequestParam(value = ATTR_EVENT, required = false) String event,
224+
@RequestParam(required = false) String envelopeId, ModelMap model) {
202225
model.addAttribute(LAUNCHER_TEXTS, config.getCodeExamplesText().SupportingTexts);
203226
model.addAttribute(ATTR_TITLE, "Return from DocuSign");
204227
model.addAttribute(ATTR_EVENT, event);
@@ -221,4 +244,4 @@ private String getLoginPath(AuthType authTypeSelected) {
221244
}
222245
return loginPath;
223246
}
224-
}
247+
}

src/main/java/com/docusign/core/model/Session.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
import java.util.UUID;
1313

1414
@Component
15-
@Scope(value = WebApplicationContext.SCOPE_SESSION,
16-
proxyMode = ScopedProxyMode.TARGET_CLASS)
15+
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
1716
@Data
1817
public class Session implements Serializable {
1918
private static final long serialVersionUID = 2695379118371574037L;
@@ -75,4 +74,6 @@ public class Session implements Serializable {
7574
private String instanceId;
7675

7776
private Boolean isWorkflowPublished = false;
77+
78+
private Boolean isPKCEWorking = true;
7879
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.docusign.core.security;
2+
3+
import org.springframework.security.core.AuthenticationException;
4+
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
5+
import javax.servlet.ServletException;
6+
import javax.servlet.http.HttpServletRequest;
7+
import javax.servlet.http.HttpServletResponse;
8+
import java.io.IOException;
9+
10+
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
11+
12+
@Override
13+
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
14+
AuthenticationException exception) throws IOException, ServletException {
15+
String code = request.getParameter("code");
16+
String state = request.getParameter("state");
17+
18+
if (code != null) {
19+
response.sendRedirect("/pkce?code=" + code + "&state=" + state);
20+
} else {
21+
response.sendRedirect("/login?error=true");
22+
}
23+
}
24+
}

src/main/java/com/docusign/core/security/jwt/JWTOAuth2User.java renamed to src/main/java/com/docusign/core/security/JWTOAuth2User.java

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.docusign.core.security.jwt;
1+
package com.docusign.core.security;
22

33
import com.docusign.esign.client.auth.OAuth;
44
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -9,9 +9,9 @@
99
import java.util.*;
1010

1111
public class JWTOAuth2User implements OAuth2User {
12-
private List<GrantedAuthority> authorities;
12+
private List <GrantedAuthority> authorities;
1313

14-
private Map<String, Object> attributes;
14+
private Map <String, Object> attributes;
1515

1616
private String sub;
1717

@@ -25,27 +25,27 @@ public class JWTOAuth2User implements OAuth2User {
2525

2626
private String email;
2727

28-
private List<Map<String, Object>> accounts;
28+
private List <Map <String, Object>> accounts;
2929

3030
private String created;
3131

3232
@Override
33-
public Collection<? extends GrantedAuthority> getAuthorities() {
33+
public Collection <? extends GrantedAuthority> getAuthorities() {
3434
return this.authorities;
3535
}
3636

37-
public void setAuthorities(List<String> scopes) {
37+
public void setAuthorities(List < String > scopes) {
3838
String authoritiesString = "ROLE_USER";
39-
for (String scope : scopes) {
39+
for (String scope: scopes) {
4040
authoritiesString += ",SCOPE_" + scope;
4141
}
4242
authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(authoritiesString);
4343
}
4444

4545
@Override
46-
public Map<String, Object> getAttributes() {
46+
public Map <String, Object> getAttributes() {
4747
if (this.attributes == null) {
48-
this.attributes = new HashMap<>();
48+
this.attributes = new HashMap <> ();
4949
this.attributes.put("sub", this.getSub());
5050
this.attributes.put("name", this.getName());
5151
this.attributes.put("given_name", this.getGivenName());
@@ -115,15 +115,15 @@ public void setEmail(String email) {
115115
this.email = email;
116116
}
117117

118-
public List<Map<String, Object>> getAccounts() {
118+
public List <Map <String, Object>> getAccounts() {
119119
return this.accounts;
120120
}
121121

122-
public void setAccounts(List<OAuth.Account> accounts) {
123-
this.accounts = new ArrayList<>();
124-
for (OAuth.Account account : accounts) {
122+
public void setAccounts(List <OAuth.Account> accounts) {
123+
this.accounts = new ArrayList <> ();
124+
for (OAuth.Account account: accounts) {
125125
ObjectMapper mapObject = new ObjectMapper();
126-
Map<String, Object> mapObj = mapObject.convertValue(account, Map.class);
126+
Map <String, Object> mapObj = mapObject.convertValue(account, Map.class);
127127
this.accounts.add(mapObj);
128128
}
129129
}

0 commit comments

Comments
 (0)