44import internal .util .credentials .WinPasswordVault ;
55import lombok .NonNull ;
66import nbbrd .design .DirectImpl ;
7+ import nbbrd .design .VisibleForTesting ;
8+ import nbbrd .io .sys .OS ;
79import nbbrd .io .text .Formatter ;
810import nbbrd .io .text .Parser ;
911import nbbrd .io .text .Property ;
2123import java .net .MalformedURLException ;
2224import java .net .PasswordAuthentication ;
2325import java .net .URI ;
24- import java .util .*;
26+ import java .util .Collection ;
27+ import java .util .HashSet ;
28+ import java .util .List ;
29+ import java .util .Set ;
2530import java .util .concurrent .CompletionException ;
2631import java .util .concurrent .ConcurrentHashMap ;
2732import java .util .concurrent .ConcurrentMap ;
2833import java .util .function .BiConsumer ;
34+ import java .util .logging .Level ;
2935
3036import static java .util .Collections .emptyList ;
3137import static nbbrd .io .function .IOFunction .unchecked ;
3844@ ServiceProvider
3945public final class MsalAuthenticator implements Authenticator {
4046
47+ @ PropertyDefinition
48+ public static final Property <String > UID_PROPERTY =
49+ Property .of (AUTHENTICATOR_PROPERTY_PREFIX + ".uid" , null , Parser .onString (), Formatter .onString ());
50+
4151 @ PropertyDefinition
4252 public static final Property <String > CLIENT_ID_PROPERTY =
4353 Property .of (AUTHENTICATOR_PROPERTY_PREFIX + ".clientId" , null , Parser .onString (), Formatter .onString ());
@@ -54,7 +64,7 @@ public final class MsalAuthenticator implements Authenticator {
5464 public static final Property <URI > REDIRECT_URI_PROPERTY =
5565 Property .of (AUTHENTICATOR_PROPERTY_PREFIX + ".redirectUri" , URI .create ("http://localhost" ), Parser .onURI (), Formatter .onURI ());
5666
57- private static final ConcurrentMap <String , IPublicClientApplication > cache = new ConcurrentHashMap <>();
67+ private final ConcurrentMap <String , IPublicClientApplication > cache = new ConcurrentHashMap <>();
5868
5969 @ Override
6070 public @ NonNull String getAuthenticatorId () {
@@ -70,7 +80,7 @@ public boolean isAuthenticatorAvailable() {
7080 public @ Nullable PasswordAuthentication getPasswordAuthenticationOrNull (@ NonNull WebSource source ) throws IOException {
7181 MsalConfig config = MsalConfig .parse (source );
7282 if (config != null ) {
73- IPublicClientApplication app = getClientApplication (source , config );
83+ IPublicClientApplication app = getClientApplication (config );
7484 return newToken (acquireToken (app , config .getScopes (), config .getRedirectUri ()).accessToken ());
7585 }
7686 return null ;
@@ -80,31 +90,45 @@ public boolean isAuthenticatorAvailable() {
8090 public void invalidateAuthentication (@ NonNull WebSource source ) throws IOException {
8191 MsalConfig config = MsalConfig .parse (source );
8292 if (config != null ) {
83- cache .remove (TypedId . getUniqueID ( source ));
93+ cache .remove (config . getUid ( ));
8494 }
8595 }
8696
8797 @ Override
8898 public @ NonNull Collection <String > getAuthenticatorProperties () {
8999 return keysOf (
100+ UID_PROPERTY ,
90101 CLIENT_ID_PROPERTY ,
91102 AUTHORITY_PROPERTY ,
92103 SCOPES_PROPERTY ,
93104 REDIRECT_URI_PROPERTY
94105 );
95106 }
96107
108+ @ VisibleForTesting
97109 @ lombok .Value
98- public static class MsalConfig {
110+ static class MsalConfig {
111+
112+ @ NonNull
113+ String uid ;
99114
115+ @ NonNull
100116 String clientId ;
117+
118+ @ NonNull
101119 String authority ;
120+
121+ @ NonNull
102122 Set <String > scopes ;
123+
124+ @ NonNull
103125 URI redirectUri ;
104126
105- public static MsalConfig parse (WebSource source ) throws IOException {
127+ public static @ Nullable MsalConfig parse (@ NonNull WebSource source ) throws IOException {
106128 if (AuthSchemes .MSAL_AUTH_SCHEME .equals (DriverProperties .AUTH_SCHEME_PROPERTY .get (source .getProperties ()))) {
129+ String uid = UID_PROPERTY .get (source .getProperties ());
107130 return new MsalConfig (
131+ uid != null && !uid .isEmpty () ? uid : TypedId .getUniqueID (source ),
108132 getNotNull (CLIENT_ID_PROPERTY , source ),
109133 getNotNull (AUTHORITY_PROPERTY , source ),
110134 new HashSet <>(getNotNull (SCOPES_PROPERTY , source )),
@@ -115,50 +139,47 @@ public static MsalConfig parse(WebSource source) throws IOException {
115139 }
116140 }
117141
118- private static IPublicClientApplication getClientApplication (WebSource source , MsalConfig config ) throws IOException {
142+ private IPublicClientApplication getClientApplication (MsalConfig config ) throws IOException {
119143 try {
120- return cache .computeIfAbsent (TypedId . getUniqueID ( source ), unchecked (key -> newClientApplication (source , config , key )));
144+ return cache .computeIfAbsent (config . getUid ( ), unchecked (uid -> newClientApplication (config )));
121145 } catch (UncheckedIOException ex ) {
122146 throw ex .getCause ();
123147 }
124148 }
125149
126- private static IPublicClientApplication newClientApplication (WebSource source , MsalConfig config , String key ) throws MalformedURLException {
127- return newClientApplication (config .getClientId (), config .getAuthority (), newTokenPersistence (key , key ));
128- }
129-
130- private static IPublicClientApplication newClientApplication (String clientId , String authority , ITokenCacheAccessAspect tokenPersistence ) throws MalformedURLException {
150+ private static IPublicClientApplication newClientApplication (MsalConfig config ) throws MalformedURLException {
131151 return PublicClientApplication
132- .builder (clientId )
133- .authority (authority )
134- .setTokenCacheAccessAspect (tokenPersistence )
152+ .builder (config . getClientId () )
153+ .authority (config . getAuthority () )
154+ .setTokenCacheAccessAspect (newTokenPersistence ( config . getUid (), config . getUid ()) )
135155 .build ();
136156 }
137157
138158 private static ITokenCacheAccessAspect newTokenPersistence (String resource , String userName ) {
139- // return OS.NAME.equals(OS.Name.WINDOWS)
140- // ? new VaultTokenPersistence(resource, userName, (msg, e) -> log.log(Level.SEVERE, msg, e))
141- // : NoOpTokenPersistence.INSTANCE;
142- return NoOpTokenPersistence .INSTANCE ;
159+ return OS .NAME .equals (OS .Name .WINDOWS )
160+ ? new VaultTokenPersistence (resource , userName , (msg , e ) -> log .log (Level .SEVERE , msg , e ))
161+ : NoOpTokenPersistence .INSTANCE ;
143162 }
144163
145164 private static IAuthenticationResult acquireToken (IPublicClientApplication app , Set <String > scopes , URI redirectUri ) throws IOException {
146- try {
147- return app .acquireTokenSilently (SilentParameters
148- .builder (scopes )
149- .account (app .getAccounts ().join ().stream ().findFirst ().orElse (null ))
150- .build ())
151- .join ();
152- } catch (CompletionException ex ) {
153- if (ex .getCause () instanceof MsalException ) {
154- return app .acquireToken (InteractiveRequestParameters
155- .builder (redirectUri )
156- .scopes (scopes )
157- .prompt (Prompt .SELECT_ACCOUNT )
165+ synchronized (app ) {
166+ try {
167+ return app .acquireTokenSilently (SilentParameters
168+ .builder (scopes )
169+ .account (app .getAccounts ().join ().stream ().findFirst ().orElse (null ))
158170 .build ())
159171 .join ();
160- } else {
161- throw new IOException (ex .getCause ());
172+ } catch (CompletionException ex ) {
173+ if (ex .getCause () instanceof MsalException ) {
174+ return app .acquireToken (InteractiveRequestParameters
175+ .builder (redirectUri )
176+ .scopes (scopes )
177+ .prompt (Prompt .SELECT_ACCOUNT )
178+ .build ())
179+ .join ();
180+ } else {
181+ throw new IOException (ex .getCause ());
182+ }
162183 }
163184 }
164185 }
@@ -198,7 +219,7 @@ public void beforeCacheAccess(ITokenCacheAccessContext context) {
198219 try (WinPasswordVault vault = WinPasswordVault .open ()) {
199220 WinPasswordVault .PasswordCredential credential = vault .get (resource );
200221 if (credential != null && credential .getUserName ().equals (userName )) {
201- context .tokenCache ().deserialize (Arrays . toString (credential .getPassword ()));
222+ context .tokenCache ().deserialize (String . valueOf (credential .getPassword ()));
202223 }
203224 } catch (IOException e ) {
204225 onError .accept ("Failed to access token cache from Windows Password Vault" , e );
@@ -209,6 +230,7 @@ public void beforeCacheAccess(ITokenCacheAccessContext context) {
209230 public void afterCacheAccess (ITokenCacheAccessContext context ) {
210231 if (context .hasCacheChanged ()) {
211232 try (WinPasswordVault vault = WinPasswordVault .open ()) {
233+ vault .invalidate (resource );
212234 vault .add (new WinPasswordVault .PasswordCredential (resource , userName , context .tokenCache ().serialize ().toCharArray ()));
213235 } catch (IOException e ) {
214236 onError .accept ("Failed to update token cache in Windows Password Vault" , e );
0 commit comments