Skip to content

Commit 9c23866

Browse files
committed
UY-1489 add private key validation to OAuth IdP editor
1 parent 7191f47 commit 9c23866

File tree

9 files changed

+144
-62
lines changed

9 files changed

+144
-62
lines changed

engine/src/main/java/pl/edu/icm/unity/engine/endpoint/EndpointsUpdater.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ private EndpointInstance createEndpointInstance(Endpoint endpointInDB) throws En
151151
return loader.createEndpointInstance(endpointInDB);
152152
} catch (ConfigurationException e)
153153
{
154+
log.error("Can not create endpoint instance" ,e);
154155
throw new EndpointConfigurationException(endpointInDB, e);
155156
}
156157
}

oauth/src/main/java/pl/edu/icm/unity/oauth/as/console/OAuthEditorGeneralTab.java

Lines changed: 81 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@
55

66
package pl.edu.icm.unity.oauth.as.console;
77

8+
import static io.imunity.vaadin.elements.CSSVars.TEXT_FIELD_BIG;
9+
import static io.imunity.vaadin.elements.CSSVars.TEXT_FIELD_MEDIUM;
10+
import static io.imunity.vaadin.elements.CssClassNames.IDP_INFO_LAYOUT;
11+
import static io.imunity.vaadin.elements.CssClassNames.MEDIUM_VAADIN_FORM_ITEM_LABEL;
12+
13+
import java.nio.charset.StandardCharsets;
14+
import java.security.PrivateKey;
15+
import java.security.interfaces.ECPrivateKey;
16+
import java.security.interfaces.RSAPrivateKey;
17+
import java.util.Collection;
18+
import java.util.HashSet;
19+
import java.util.List;
20+
import java.util.Optional;
21+
import java.util.Set;
22+
import java.util.function.Consumer;
23+
import java.util.stream.Collectors;
24+
825
import com.google.common.base.Strings;
926
import com.google.common.collect.ImmutableSet;
1027
import com.nimbusds.jose.JWSAlgorithm;
@@ -31,19 +48,26 @@
3148
import com.vaadin.flow.data.binder.Binder;
3249
import com.vaadin.flow.data.binder.ValidationResult;
3350
import com.vaadin.flow.data.validator.IntegerRangeValidator;
51+
3452
import eu.unicore.util.httpclient.ServerHostnameCheckingMode;
3553
import io.imunity.console.utils.tprofile.OutputTranslationProfileFieldFactory;
36-
import io.imunity.vaadin.elements.*;
37-
import io.imunity.vaadin.elements.grid.GridWithEditorInDetails;
38-
import io.imunity.vaadin.endpoint.common.api.HtmlTooltipFactory;
39-
import io.imunity.vaadin.endpoint.common.api.SubViewSwitcher;
4054
import io.imunity.vaadin.auth.services.DefaultServiceDefinition;
4155
import io.imunity.vaadin.auth.services.ServiceEditorBase;
4256
import io.imunity.vaadin.auth.services.ServiceEditorComponent;
57+
import io.imunity.vaadin.elements.CustomValuesMultiSelectComboBox;
58+
import io.imunity.vaadin.elements.EnumComboBox;
59+
import io.imunity.vaadin.elements.LocalizedTextFieldDetails;
60+
import io.imunity.vaadin.elements.NoSpaceValidator;
61+
import io.imunity.vaadin.elements.grid.GridWithEditorInDetails;
62+
import io.imunity.vaadin.endpoint.common.api.HtmlTooltipFactory;
63+
import io.imunity.vaadin.endpoint.common.api.SubViewSwitcher;
64+
import io.imunity.vaadin.endpoint.common.exceptions.FormValidationException;
65+
import pl.edu.icm.unity.base.exceptions.EngineException;
4366
import pl.edu.icm.unity.base.exceptions.WrongArgumentException;
4467
import pl.edu.icm.unity.base.i18n.I18nString;
4568
import pl.edu.icm.unity.base.identity.IdentityType;
4669
import pl.edu.icm.unity.base.message.MessageSource;
70+
import pl.edu.icm.unity.engine.api.PKIManagement;
4771
import pl.edu.icm.unity.engine.api.endpoint.EndpointPathValidator;
4872
import pl.edu.icm.unity.oauth.as.OAuthASProperties.AccessTokenFormat;
4973
import pl.edu.icm.unity.oauth.as.OAuthASProperties.RefreshTokenIssuePolicy;
@@ -52,17 +76,6 @@
5276
import pl.edu.icm.unity.oauth.as.OAuthSystemScopeProvider;
5377
import pl.edu.icm.unity.oauth.as.token.OAuthTokenEndpoint;
5478
import pl.edu.icm.unity.oauth.as.webauthz.OAuthAuthzWebEndpoint;
55-
import io.imunity.vaadin.endpoint.common.exceptions.FormValidationException;
56-
57-
import java.nio.charset.StandardCharsets;
58-
import java.util.*;
59-
import java.util.function.Consumer;
60-
import java.util.stream.Collectors;
61-
62-
import static io.imunity.vaadin.elements.CSSVars.TEXT_FIELD_BIG;
63-
import static io.imunity.vaadin.elements.CSSVars.TEXT_FIELD_MEDIUM;
64-
import static io.imunity.vaadin.elements.CssClassNames.IDP_INFO_LAYOUT;
65-
import static io.imunity.vaadin.elements.CssClassNames.MEDIUM_VAADIN_FORM_ITEM_LABEL;
6679

6780
/**
6881
* OAuth service editor general tab
@@ -72,7 +85,8 @@
7285
*/
7386
class OAuthEditorGeneralTab extends VerticalLayout implements ServiceEditorBase.EditorTab
7487
{
75-
private MessageSource msg;
88+
private final MessageSource msg;
89+
private final PKIManagement pkiManagement;
7690
private Binder<DefaultServiceDefinition> oauthWebAuthzBinder;
7791
private Binder<DefaultServiceDefinition> oauthTokenBinder;
7892
private Binder<OAuthServiceConfiguration> configBinder;
@@ -98,13 +112,14 @@ class OAuthEditorGeneralTab extends VerticalLayout implements ServiceEditorBase.
98112
private Set<String> validators;
99113
private final HtmlTooltipFactory htmlTooltipFactory;
100114

101-
OAuthEditorGeneralTab(MessageSource msg, HtmlTooltipFactory htmlTooltipFactory, String serverPrefix, Set<String> serverContextPaths,
115+
OAuthEditorGeneralTab(MessageSource msg, PKIManagement pkiManagement, HtmlTooltipFactory htmlTooltipFactory, String serverPrefix, Set<String> serverContextPaths,
102116
SubViewSwitcher subViewSwitcher, OutputTranslationProfileFieldFactory profileFieldFactory, boolean editMode,
103117
Set<String> credentials, Collection<IdentityType> identityTypes, List<String> attrTypes,
104118
List<String> usedEndpointsPaths, List<OAuthScope> systemScopes, Set<String> validators, Set<String> certificates)
105119
{
106120
this.msg = msg;
107-
121+
this.pkiManagement = pkiManagement;
122+
108123
this.editMode = editMode;
109124
this.credentials = credentials;
110125
this.idTypes = identityTypes;
@@ -406,17 +421,7 @@ private Component buildHeaderSection()
406421
credential.setItems(credentials);
407422
configBinder.forField(credential)
408423
.asRequired((v, c) ->
409-
{
410-
if (credential.isEnabled() && (v == null || v.isEmpty())
411-
&& !Family.HMAC_SHA.contains(JWSAlgorithm.parse(signingAlg.getValue()
412-
.toString())))
413-
{
414-
return ValidationResult.error(msg.getMessage("fieldRequired"));
415-
}
416-
417-
return ValidationResult.ok();
418-
419-
})
424+
validateCredential(v, credential.isEnabled(), signingAlg.getValue() != null ? signingAlg.getValue().toString() : null))
420425
.bind("credential");
421426

422427
credential.setEnabled(false);
@@ -461,6 +466,51 @@ private Component buildHeaderSection()
461466
return main;
462467
}
463468

469+
private PrivateKey getPrivateKey(String cred) throws EngineException
470+
{
471+
return pkiManagement.getCredential(cred).getKey();
472+
}
473+
474+
private ValidationResult validateCredential(String credential, boolean isEnabled, String signingAlg)
475+
{
476+
if (signingAlg == null)
477+
{
478+
return ValidationResult.ok();
479+
}
480+
481+
if (isEnabled && (credential == null || credential.isEmpty())
482+
&& !Family.HMAC_SHA.contains(JWSAlgorithm.parse(signingAlg)))
483+
{
484+
return ValidationResult.error(msg.getMessage("fieldRequired"));
485+
}
486+
487+
PrivateKey pk;
488+
try
489+
{
490+
pk = getPrivateKey(credential);
491+
} catch (EngineException e1)
492+
{
493+
return ValidationResult.error(msg.getMessage("OAuthEditorGeneralTab.credentialError"));
494+
}
495+
if (pk == null)
496+
{
497+
return ValidationResult.error(msg.getMessage("OAuthEditorGeneralTab.credentialError"));
498+
}
499+
500+
if (!(pk instanceof RSAPrivateKey) && Family.RSA.contains(JWSAlgorithm.parse(signingAlg)))
501+
{
502+
return ValidationResult.error(msg.getMessage("OAuthEditorGeneralTab.privateKeyError", "RSA", "RS"));
503+
}
504+
505+
if (!(pk instanceof ECPrivateKey) && Family.EC.contains(JWSAlgorithm.parse(signingAlg)))
506+
{
507+
return ValidationResult.error(msg.getMessage("OAuthEditorGeneralTab.privateKeyError", "EC", "ES"));
508+
}
509+
510+
511+
return ValidationResult.ok();
512+
}
513+
464514
private void refreshScope(boolean add, OIDCScopeValue value)
465515
{
466516
Optional<OAuthScopeBean> scope = configBinder.getBean()
@@ -626,6 +676,8 @@ private void refreshSigningControls()
626676
credential.setEnabled(false);
627677
signingAlg.setEnabled(false);
628678
}
679+
680+
configBinder.getBinding("credential").get().validate();
629681

630682
}
631683

oauth/src/main/java/pl/edu/icm/unity/oauth/as/console/OAuthServiceConfiguration.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import pl.edu.icm.unity.base.group.Group;
1616
import pl.edu.icm.unity.base.message.MessageSource;
1717
import pl.edu.icm.unity.base.translation.TranslationProfile;
18+
import pl.edu.icm.unity.engine.api.PKIManagement;
1819
import pl.edu.icm.unity.engine.api.TranslationProfileManagement;
1920
import pl.edu.icm.unity.engine.api.idp.CommonIdPProperties;
2021
import pl.edu.icm.unity.engine.api.idp.IdpPolicyAgreementsConfiguration;
@@ -112,7 +113,7 @@ public OAuthServiceConfiguration(MessageSource msg, List<Group> allGroups, OAuth
112113
trustedUpstreamAS = new ArrayList<>();
113114
}
114115

115-
public String toProperties(MessageSource msg)
116+
public String toProperties(MessageSource msg, PKIManagement pkiForValidation)
116117
{
117118
Properties raw = new Properties();
118119

@@ -293,7 +294,7 @@ public String toProperties(MessageSource msg)
293294
OAuthASProperties.P));
294295
}
295296

296-
OAuthASProperties oauthProperties = new OAuthASProperties(raw);
297+
OAuthASProperties oauthProperties = pkiForValidation != null ? new OAuthASProperties(raw, pkiForValidation, null) : new OAuthASProperties(raw);
297298
return oauthProperties.getAsString();
298299
}
299300

oauth/src/main/java/pl/edu/icm/unity/oauth/as/console/OAuthServiceController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,7 @@ private List<String> getAllUsernames() throws EngineException
710710
public ServiceEditor getEditor(SubViewSwitcher subViewSwitcher) throws EngineException
711711
{
712712

713-
return new OAuthServiceEditor(msg, subViewSwitcher, outputTranslationProfileFieldFactory,
713+
return new OAuthServiceEditor(msg, subViewSwitcher, outputTranslationProfileFieldFactory, pkiMan,
714714
advertisedAddrProvider.get().toString(), server.getUsedContextPaths(), imageService, notificationPresenter,
715715
fileStorageService, serverConfig,
716716
realmsMan.getRealms().stream().map(r -> r.getName()).collect(Collectors.toList()),

oauth/src/main/java/pl/edu/icm/unity/oauth/as/console/OAuthServiceEditor.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import pl.edu.icm.unity.base.group.Group;
2222
import pl.edu.icm.unity.base.identity.IdentityType;
2323
import pl.edu.icm.unity.base.message.MessageSource;
24+
import pl.edu.icm.unity.engine.api.PKIManagement;
2425
import pl.edu.icm.unity.engine.api.authn.AuthenticatorInfo;
2526
import pl.edu.icm.unity.engine.api.authn.AuthenticatorSupportService;
2627
import pl.edu.icm.unity.engine.api.config.UnityServerConfiguration;
@@ -66,10 +67,12 @@ class OAuthServiceEditor implements ServiceEditor
6667
private final Set<String> validators;
6768
private final Set<String> certificates;
6869
private final HtmlTooltipFactory htmlTooltipFactory;
70+
private final PKIManagement pkiManagement;
6971

7072
OAuthServiceEditor(MessageSource msg,
7173
SubViewSwitcher subViewSwitcher,
7274
OutputTranslationProfileFieldFactory outputTranslationProfileFieldFactory,
75+
PKIManagement pkiManagement,
7376
String serverPrefix,
7477
Set<String> serverContextPaths,
7578
VaadinLogoImageLoader imageService,
@@ -95,6 +98,7 @@ class OAuthServiceEditor implements ServiceEditor
9598
Set<String> certificates, HtmlTooltipFactory htmlTooltipFactory)
9699
{
97100
this.msg = msg;
101+
this.pkiManagement = pkiManagement;
98102
this.notificationPresenter = notificationPresenter;
99103
this.allRealms = allRealms;
100104
this.authenticators = authenticators;
@@ -126,7 +130,7 @@ class OAuthServiceEditor implements ServiceEditor
126130
@Override
127131
public ServiceEditorComponent getEditor(ServiceDefinition endpoint)
128132
{
129-
OAuthEditorGeneralTab generalTab = new OAuthEditorGeneralTab(msg, htmlTooltipFactory, serverPrefix, serverContextPaths,
133+
OAuthEditorGeneralTab generalTab = new OAuthEditorGeneralTab(msg, pkiManagement, htmlTooltipFactory, serverPrefix, serverContextPaths,
130134
subViewSwitcher, outputTranslationProfileFieldFactory, endpoint != null, credentials, idTypes,
131135
allAttributes, usedPaths, scopeService.getSystemScopes(), validators, certificates);
132136
OAuthEditorClientsTab clientsTab = new OAuthEditorClientsTab(msg, serverConfig,
@@ -141,7 +145,7 @@ public ServiceEditorComponent getEditor(ServiceDefinition endpoint)
141145

142146
PolicyAgreementsTab policyAgreementTab = new PolicyAgreementsTab(msg, policyDocuments);
143147

144-
editor = new OAuthServiceEditorComponent(msg, generalTab, clientsTab, usersTab, webAuthTab, policyAgreementTab,
148+
editor = new OAuthServiceEditorComponent(msg, pkiManagement, generalTab, clientsTab, usersTab, webAuthTab, policyAgreementTab,
145149
fileStorageService, imageService, scopeService, endpoint, allGroups, systemClientsSupplier);
146150
return editor;
147151
}

oauth/src/main/java/pl/edu/icm/unity/oauth/as/console/OAuthServiceEditorComponent.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
package pl.edu.icm.unity.oauth.as.console;
77

88
import com.vaadin.flow.data.binder.Binder;
9+
10+
import eu.unicore.util.configuration.ConfigurationException;
911
import io.imunity.vaadin.auth.services.DefaultServiceDefinition;
1012
import io.imunity.vaadin.auth.services.ServiceDefinition;
1113
import io.imunity.vaadin.auth.services.ServiceEditorBase;
@@ -19,6 +21,7 @@
1921
import pl.edu.icm.unity.base.group.Group;
2022
import pl.edu.icm.unity.base.i18n.I18nString;
2123
import pl.edu.icm.unity.base.message.MessageSource;
24+
import pl.edu.icm.unity.engine.api.PKIManagement;
2225
import pl.edu.icm.unity.engine.api.files.FileStorageService;
2326
import pl.edu.icm.unity.oauth.as.OAuthScopesService;
2427
import pl.edu.icm.unity.oauth.as.console.OAuthClient.OAuthClientsBean;
@@ -46,17 +49,19 @@ class OAuthServiceEditorComponent extends ServiceEditorBase
4649
private final Binder<ServiceWebConfiguration> webConfigBinder;
4750
private final Binder<OAuthClientsBean> clientsBinder;
4851
private final FileStorageService fileStorageService;
52+
private final PKIManagement pkiManagement;
4953
private final Group generatedIdPGroup;
5054
private final boolean editMode;
5155

52-
OAuthServiceEditorComponent(MessageSource msg, OAuthEditorGeneralTab generalTab, OAuthEditorClientsTab clientsTab,
56+
OAuthServiceEditorComponent(MessageSource msg, PKIManagement pkiMan, OAuthEditorGeneralTab generalTab, OAuthEditorClientsTab clientsTab,
5357
IdpEditorUsersTab usersTab, WebServiceAuthenticationTab webAuthTab, PolicyAgreementsTab policyAgreementTab,
5458
FileStorageService fileStorageService, VaadinLogoImageLoader imageAccessService,
5559
OAuthScopesService scopeService, ServiceDefinition toEdit,
5660
List<Group> allGroups, Function<String, List<OAuthClient>> systemClientsSupplier)
5761
{
5862
super(msg);
5963
this.fileStorageService = fileStorageService;
64+
this.pkiManagement = pkiMan;
6065
editMode = toEdit != null;
6166
oauthServiceWebAuthzBinder = new Binder<>(DefaultServiceDefinition.class);
6267
oauthConfigBinder = new Binder<>(OAuthServiceConfiguration.class);
@@ -193,13 +198,21 @@ ServiceDefinition getServiceDefiniton() throws FormValidationException
193198
}
194199

195200
DefaultServiceDefinition webAuthz = oauthServiceWebAuthzBinder.getBean();
196-
VaadinEndpointProperties prop = new VaadinEndpointProperties(
197-
webConfigBinder.getBean().toProperties(msg, fileStorageService, webAuthz.getName()));
198-
webAuthz.setConfiguration(oauthConfigBinder.getBean().toProperties(msg) + "\n" + prop.getAsString());
199-
200-
DefaultServiceDefinition token = oauthServiceTokenBinder.getBean();
201-
token.setConfiguration(oauthConfigBinder.getBean().toProperties(msg));
202-
201+
DefaultServiceDefinition token;
202+
try
203+
{
204+
VaadinEndpointProperties prop = new VaadinEndpointProperties(webConfigBinder.getBean()
205+
.toProperties(msg, fileStorageService, webAuthz.getName()));
206+
webAuthz.setConfiguration(oauthConfigBinder.getBean()
207+
.toProperties(msg, pkiManagement) + "\n" + prop.getAsString());
208+
209+
token = oauthServiceTokenBinder.getBean();
210+
token.setConfiguration(oauthConfigBinder.getBean()
211+
.toProperties(msg, pkiManagement));
212+
} catch (ConfigurationException e)
213+
{
214+
throw new FormValidationException(e.getMessage());
215+
}
203216
if (token.getName() == null || token.getName().isEmpty())
204217
{
205218
token.setName(webAuthz.getName() + TOKEN_SERVICE_NAME_SUFFIX);

oauth/src/main/resources/messages/oauth/messages.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,9 @@ OAuthEditorGeneralTab.refreshTokenIssuePolicy=Refresh token issue policy:
182182
OAuthEditorGeneralTab.refreshTokenIssuePolicy.NEVER=Never
183183
OAuthEditorGeneralTab.refreshTokenIssuePolicy.ALWAYS=Always
184184
OAuthEditorGeneralTab.refreshTokenIssuePolicy.OFFLINE_SCOPE_BASED=Offline scope based
185+
OAuthEditorGeneralTab.privateKeyError=The private key must be {0} if one of {1} signingAlgorithm is used
186+
OAuthEditorGeneralTab.credentialError=Invalid credential
187+
185188

186189
OAuthEditorGeneralTab.importantURLs=Important URLs:
187190
OAuthEditorGeneralTab.tokenEndpointPath=Token Endpoint:

0 commit comments

Comments
 (0)