3131import org .openhab .core .auth .Credentials ;
3232import org .openhab .core .auth .ManagedUser ;
3333import org .openhab .core .auth .User ;
34+ import org .openhab .core .auth .UserApiToken ;
35+ import org .openhab .core .auth .UserApiTokenCredentials ;
3436import org .openhab .core .auth .UserProvider ;
3537import org .openhab .core .auth .UserRegistry ;
38+ import org .openhab .core .auth .UserSession ;
3639import org .openhab .core .auth .UsernamePasswordCredentials ;
3740import org .openhab .core .common .registry .AbstractRegistry ;
3841import org .osgi .framework .BundleContext ;
@@ -56,7 +59,9 @@ public class UserRegistryImpl extends AbstractRegistry<User, String, UserProvide
5659
5760 private final Logger logger = LoggerFactory .getLogger (UserRegistryImpl .class );
5861
59- private static final int ITERATIONS = 65536 ;
62+ private static final int PASSWORD_ITERATIONS = 65536 ;
63+ private static final int APITOKEN_ITERATIONS = 1024 ;
64+ private static final String APITOKEN_PREFIX = "oh" ;
6065 private static final int KEY_LENGTH = 512 ;
6166 private static final String ALGORITHM = "PBKDF2WithHmacSHA512" ;
6267 private static final SecureRandom RAND = new SecureRandom ();
@@ -87,7 +92,7 @@ protected void unsetManagedProvider(ManagedUserProvider managedProvider) {
8792 @ Override
8893 public User register (String username , String password , Set <String > roles ) {
8994 String passwordSalt = generateSalt (KEY_LENGTH / 8 ).get ();
90- String passwordHash = hashPassword (password , passwordSalt ).get ();
95+ String passwordHash = hash (password , passwordSalt , PASSWORD_ITERATIONS ).get ();
9196 ManagedUser user = new ManagedUser (username , passwordSalt , passwordHash );
9297 user .setRoles (new HashSet <>(roles ));
9398 super .add (user );
@@ -106,11 +111,11 @@ private Optional<String> generateSalt(final int length) {
106111 return Optional .of (Base64 .getEncoder ().encodeToString (salt ));
107112 }
108113
109- private Optional <String > hashPassword (String password , String salt ) {
114+ private Optional <String > hash (String password , String salt , int iterations ) {
110115 char [] chars = password .toCharArray ();
111116 byte [] bytes = salt .getBytes ();
112117
113- PBEKeySpec spec = new PBEKeySpec (chars , bytes , ITERATIONS , KEY_LENGTH );
118+ PBEKeySpec spec = new PBEKeySpec (chars , bytes , iterations , KEY_LENGTH );
114119
115120 Arrays .fill (chars , Character .MIN_VALUE );
116121
@@ -119,7 +124,7 @@ private Optional<String> hashPassword(String password, String salt) {
119124 byte [] securePassword = fac .generateSecret (spec ).getEncoded ();
120125 return Optional .of (Base64 .getEncoder ().encodeToString (securePassword ));
121126 } catch (NoSuchAlgorithmException | InvalidKeySpecException e ) {
122- logger .error ("Exception encountered in hashPassword " , e );
127+ logger .error ("Exception encountered while hashing " , e );
123128 return Optional .empty ();
124129 } finally {
125130 spec .clearPassword ();
@@ -128,23 +133,132 @@ private Optional<String> hashPassword(String password, String salt) {
128133
129134 @ Override
130135 public Authentication authenticate (Credentials credentials ) throws AuthenticationException {
131- UsernamePasswordCredentials usernamePasswordCreds = (UsernamePasswordCredentials ) credentials ;
132- User user = this .get (usernamePasswordCreds .getUsername ());
133- if (user == null ) {
134- throw new AuthenticationException ("User not found: " + usernamePasswordCreds .getUsername ());
136+ if (credentials instanceof UsernamePasswordCredentials ) {
137+ UsernamePasswordCredentials usernamePasswordCreds = (UsernamePasswordCredentials ) credentials ;
138+ User user = get (usernamePasswordCreds .getUsername ());
139+ if (user == null ) {
140+ throw new AuthenticationException ("User not found: " + usernamePasswordCreds .getUsername ());
141+ }
142+
143+ ManagedUser managedUser = (ManagedUser ) user ;
144+ String hashedPassword = hash (usernamePasswordCreds .getPassword (), managedUser .getPasswordSalt (),
145+ PASSWORD_ITERATIONS ).get ();
146+ if (!hashedPassword .equals (managedUser .getPasswordHash ())) {
147+ throw new AuthenticationException ("Wrong password for user " + usernamePasswordCreds .getUsername ());
148+ }
149+
150+ return new Authentication (managedUser .getName (), managedUser .getRoles ().stream ().toArray (String []::new ));
151+ } else if (credentials instanceof UserApiTokenCredentials ) {
152+ UserApiTokenCredentials apiTokenCreds = (UserApiTokenCredentials ) credentials ;
153+ String [] apiTokenParts = apiTokenCreds .getApiToken ().split ("\\ ." );
154+ if (apiTokenParts .length != 3 || !APITOKEN_PREFIX .equals (apiTokenParts [0 ])) {
155+ throw new AuthenticationException ("Invalid API token format" );
156+ }
157+ for (User user : getAll ()) {
158+ ManagedUser managedUser = (ManagedUser ) user ;
159+ for (UserApiToken userApiToken : managedUser .getApiTokens ()) {
160+ // only check if the name in the token matches
161+ if (!userApiToken .getName ().equals (apiTokenParts [1 ])) {
162+ continue ;
163+ }
164+ String [] existingTokenHashAndSalt = userApiToken .getApiToken ().split (":" );
165+ String incomingTokenHash = hash (apiTokenCreds .getApiToken (), existingTokenHashAndSalt [1 ],
166+ APITOKEN_ITERATIONS ).get ();
167+
168+ if (incomingTokenHash .equals (existingTokenHashAndSalt [0 ])) {
169+ return new Authentication (managedUser .getName (),
170+ managedUser .getRoles ().stream ().toArray (String []::new ), userApiToken .getScope ());
171+ }
172+ }
173+ }
174+
175+ throw new AuthenticationException ("Unknown API token" );
176+ }
177+
178+ throw new IllegalArgumentException ("Invalid credential type" );
179+ }
180+
181+ @ Override
182+ public void changePassword (User user , String newPassword ) {
183+ if (!(user instanceof ManagedUser )) {
184+ throw new IllegalArgumentException ("User is not managed: " + user .getName ());
185+ }
186+
187+ ManagedUser managedUser = (ManagedUser ) user ;
188+ String passwordSalt = generateSalt (KEY_LENGTH / 8 ).get ();
189+ String passwordHash = hash (newPassword , passwordSalt , PASSWORD_ITERATIONS ).get ();
190+ managedUser .setPasswordSalt (passwordSalt );
191+ managedUser .setPasswordHash (passwordHash );
192+ update (user );
193+ }
194+
195+ @ Override
196+ public void addUserSession (User user , UserSession session ) {
197+ if (!(user instanceof ManagedUser )) {
198+ throw new IllegalArgumentException ("User is not managed: " + user .getName ());
199+ }
200+
201+ ManagedUser managedUser = (ManagedUser ) user ;
202+ managedUser .getSessions ().add (session );
203+ update (user );
204+ }
205+
206+ @ Override
207+ public void removeUserSession (User user , UserSession session ) {
208+ if (!(user instanceof ManagedUser )) {
209+ throw new IllegalArgumentException ("User is not managed: " + user .getName ());
135210 }
211+
212+ ManagedUser managedUser = (ManagedUser ) user ;
213+ managedUser .getSessions ().remove (session );
214+ update (user );
215+ }
216+
217+ @ Override
218+ public void clearSessions (User user ) {
136219 if (!(user instanceof ManagedUser )) {
137- throw new AuthenticationException ("User is not managed: " + usernamePasswordCreds . getUsername ());
220+ throw new IllegalArgumentException ("User is not managed: " + user . getName ());
138221 }
139222
140223 ManagedUser managedUser = (ManagedUser ) user ;
141- String hashedPassword = hashPassword (usernamePasswordCreds .getPassword (), managedUser .getPasswordSalt ()).get ();
142- if (!hashedPassword .equals (managedUser .getPasswordHash ())) {
143- throw new AuthenticationException ("Wrong password for user " + usernamePasswordCreds .getUsername ());
224+ managedUser .getSessions ().clear ();
225+ update (user );
226+ }
227+
228+ @ Override
229+ public String addUserApiToken (User user , String name , String scope ) {
230+ if (!(user instanceof ManagedUser )) {
231+ throw new IllegalArgumentException ("User is not managed: " + user .getName ());
232+ }
233+ if (!name .matches ("[a-zA-Z0-9]*" )) {
234+ throw new IllegalArgumentException ("API token name format invalid, alphanumeric characters only" );
144235 }
145236
146- Authentication authentication = new Authentication (managedUser .getName ());
147- return authentication ;
237+ ManagedUser managedUser = (ManagedUser ) user ;
238+ String tokenSalt = generateSalt (KEY_LENGTH / 8 ).get ();
239+ byte [] rnd = new byte [64 ];
240+ RAND .nextBytes (rnd );
241+ String token = APITOKEN_PREFIX + "." + name + "."
242+ + Base64 .getEncoder ().encodeToString (rnd ).replaceAll ("(\\ +|/|=)" , "" );
243+ String tokenHash = hash (token , tokenSalt , APITOKEN_ITERATIONS ).get ();
244+
245+ UserApiToken userApiToken = new UserApiToken (name , tokenHash + ":" + tokenSalt , scope );
246+
247+ managedUser .getApiTokens ().add (userApiToken );
248+ update (user );
249+
250+ return token ;
251+ }
252+
253+ @ Override
254+ public void removeUserApiToken (User user , UserApiToken userApiToken ) {
255+ if (!(user instanceof ManagedUser )) {
256+ throw new IllegalArgumentException ("User is not managed: " + user .getName ());
257+ }
258+
259+ ManagedUser managedUser = (ManagedUser ) user ;
260+ managedUser .getApiTokens ().remove (userApiToken );
261+ update (user );
148262 }
149263
150264 @ Override
0 commit comments