001/*
002 *  Copyright 2017 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.core.authentication.token;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.io.UnsupportedEncodingException;
021import java.sql.Connection;
022import java.sql.PreparedStatement;
023import java.sql.ResultSet;
024import java.sql.SQLException;
025import java.sql.Timestamp;
026import java.sql.Types;
027import java.util.ArrayList;
028import java.util.Base64;
029import java.util.Collections;
030import java.util.Date;
031import java.util.List;
032import java.util.Map;
033import java.util.Set;
034import java.util.stream.Collectors;
035
036import org.apache.avalon.framework.activity.Initializable;
037import org.apache.avalon.framework.component.Component;
038import org.apache.avalon.framework.service.ServiceException;
039import org.apache.avalon.framework.service.ServiceManager;
040import org.apache.avalon.framework.service.Serviceable;
041import org.apache.commons.codec.digest.DigestUtils;
042import org.apache.commons.io.IOUtils;
043import org.apache.commons.lang3.RandomStringUtils;
044import org.apache.commons.lang3.StringUtils;
045
046import org.ametys.core.datasource.ConnectionHelper;
047import org.ametys.core.datasource.dbtype.SQLDatabaseTypeExtensionPoint;
048import org.ametys.core.ui.Callable;
049import org.ametys.core.user.CurrentUserProvider;
050import org.ametys.core.user.UserIdentity;
051import org.ametys.core.user.UserManager;
052import org.ametys.core.util.JSONUtils;
053import org.ametys.runtime.config.Config;
054import org.ametys.runtime.plugin.component.AbstractLogEnabled;
055
056/**
057 * The component to handle temporary authentication token.<br>
058 * Token can only be used once and are available for a short time only.
059 */
060public class AuthenticationTokenManager extends AbstractLogEnabled implements Component, Serviceable, Initializable
061{
062    /** The avalon role */
063    public static final String ROLE = AuthenticationTokenManager.class.getName();
064
065    /** The separator in token */
066    public static final String TOKEN_SEPARATOR = "#";
067
068    /** The user token type */
069    public static final String USER_TOKEN_TYPE = "User";
070    
071    /** all fields without login and population_id */
072    private static final String TOKEN_SQL_GET_FIELDS = "id, token, salt, creation_date, end_date, last_update_date, nb_uses_left, auto_renew_duration, context, type, token_comment";
073    
074    /** all fields without id and last_update_date */
075    private static final String TOKEN_SQL_SET_FIELDS = "login, population_id, token, salt, creation_date, end_date, nb_uses_left, auto_renew_duration, context, type, token_comment";
076
077    private ServiceManager _manager;
078
079    private CurrentUserProvider _currentUserProvider;
080
081    private String _datasourceId;
082    
083    private SQLDatabaseTypeExtensionPoint _sqlDatabaseTypeExtensionPoint;
084    
085    private JSONUtils _jsonUtils;
086    
087    private UserManager _userManager;
088
089    public void service(ServiceManager manager) throws ServiceException
090    {
091        _manager = manager;
092        _sqlDatabaseTypeExtensionPoint = (SQLDatabaseTypeExtensionPoint) manager.lookup(SQLDatabaseTypeExtensionPoint.ROLE);
093        _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE);
094        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
095    }
096
097    public void initialize() throws Exception
098    {
099        _datasourceId = Config.getInstance() != null ? Config.getInstance().getValue("runtime.assignments.authenticationtokens") : "";
100    }
101
102    private CurrentUserProvider _getCurrentUserProvider() throws RuntimeException
103    {
104        if (_currentUserProvider == null)
105        {
106            try
107            {
108                _currentUserProvider = (CurrentUserProvider) _manager.lookup(CurrentUserProvider.ROLE);
109            }
110            catch (ServiceException e)
111            {
112                throw new RuntimeException(e);
113            }
114        }
115        return _currentUserProvider;
116    }
117
118    /**
119     * Get the existing tokens for the connected user
120     * 
121     * @param type The type of tokens to return. null to return all.
122     * @return The tokens
123     * @throws RuntimeException If there is no user connected or if there is a
124     *             database error
125     */
126    public List<Token> getTokens(String type) throws RuntimeException
127    {
128        return getTokens(_getCurrentUserProvider().getUser(), type);
129    }
130
131    /**
132     * Get the existing tokens for this user
133     * 
134     * @param type The type of tokens to return. null to return all.
135     * @param user The user. Cannot be null
136     * @return The tokens identifier and associated comment
137     * @throws RuntimeException If the user is null or if there is a database
138     *             error
139     */
140    public List<Token> getTokens(UserIdentity user, String type) throws RuntimeException
141    {
142        if (user == null)
143        {
144            throw new RuntimeException("Cannot generate a temporary authentication token for a null user");
145        }
146
147        List<Token> tokens = new ArrayList<>();
148
149        Connection connection = null;
150        try
151        {
152            connection = ConnectionHelper.getConnection(_datasourceId);
153
154            // Delete old entries
155            _deleteOldTokens(connection);
156
157            try (PreparedStatement selectStatement = _getSelectUserTokenStatement(connection, user.getLogin(), user.getPopulationId(), type);
158                    ResultSet resultSet = selectStatement.executeQuery())
159            {
160                // Find the database entry using this token
161                while (resultSet.next())
162                {
163                    Token token = _getTokenFromResultSet(resultSet, connection);
164                    tokens.add(token);
165                }
166            }
167        }
168        catch (Exception e)
169        {
170            getLogger().error("Communication error with the database", e);
171        }
172        finally
173        {
174            ConnectionHelper.cleanup(connection);
175        }
176
177        return tokens;
178    }
179    
180    /**
181     * Generates a new token for the current user
182     * 
183     * @param duration The time the token is valid in seconds. 0 means for ever
184     *            and moreover the ticket will be reusable.
185     * @param type The type of token. Mandatory but can be anything you want
186     *            between 1 to 32 characters. Such as "Cookie".
187     * @param comment An optional token comment to remember the reason of its
188     *            creation
189     * @return The token
190     * @throws RuntimeException If the user is not authenticated, or if there is
191     *             a database error
192     */
193    public String generateToken(long duration, String type, String comment) throws RuntimeException
194    {
195        return generateToken(_getCurrentUserProvider().getUser(), duration, type, comment);
196    }
197
198    /**
199     * Generates a new token
200     * 
201     * @param user The user that will be authenticated with the token
202     * @param duration The time the token is valid in seconds. 0 means for ever
203     *            and moreover the ticket will be reusable
204     * @param type The type of token. Mandatory but can be anything you want
205     *            between 1 to 32 characters. Such as "Cookie".
206     * @param comment An optional token comment to remember the reason of its
207     *            creation
208     * @return The token
209     * @throws RuntimeException If the user is null or if there is a database
210     *             error or if duration is negative
211     */
212    public String generateToken(UserIdentity user, long duration, String type, String comment) throws RuntimeException
213    {
214        return generateToken(user, duration, false, null, null, type, comment);
215    }
216    /**
217     * Generates a new token
218     * 
219     * @param user The user that will be authenticated with the token
220     * @param duration The time the token is valid in seconds. 0 means for ever
221     *            and moreover the ticket will be reusable
222     * @param nbUsesLeft number of available uses (null for no limit)
223     * @param type The type of token. Mandatory but can be anything you want
224     *            between 1 to 32 characters. Such as "Cookie".
225     * @param comment An optional token comment to remember the reason of its
226     *            creation
227     * @return The token
228     * @throws RuntimeException If the user is null or if there is a database
229     *             error or if duration is negative
230     */
231    public String generateToken(UserIdentity user, long duration, Integer nbUsesLeft, String type, String comment) throws RuntimeException
232    {
233        return generateToken(user, duration, false, nbUsesLeft, null, type, comment);
234    }
235    
236    /**
237     * Generates a new token
238     * 
239     * @param user The user that will be authenticated with the token
240     * @param duration The time the token is valid in seconds. 0 means for ever
241     *            and moreover the ticket will be reusable
242     * @param autoRenewDuration true to automatically renew token if used before it's expiration
243     * @param nbUsesLeft number of available uses (null for no limit)
244     * @param contexts contexts where the token can be used
245     * @param type The type of token. Mandatory but can be anything you want
246     *            between 1 to 32 characters. Such as "Cookie".
247     * @param comment An optional token comment to remember the reason of its
248     *            creation
249     * @return The token
250     * @throws RuntimeException If the user is null or if there is a database
251     *             error or if duration is negative
252     */
253    public String generateToken(UserIdentity user, long duration, boolean autoRenewDuration, Integer nbUsesLeft, Set<String> contexts, String type, String comment) throws RuntimeException
254    {
255        if (user == null)
256        {
257            throw new RuntimeException("Cannot generate a temporary authentication token for a null user");
258        }
259        else if (duration < 0)
260        {
261            throw new RuntimeException("Cannot generate a token for a negative duration [" + duration + "]");
262        }
263
264        String token = RandomStringUtils.randomAlphanumeric(duration == 0 ? 64 : 16);
265        String salt = RandomStringUtils.randomAlphanumeric(48);
266        Timestamp creationDateTime = new Timestamp(new Date().getTime());
267        Timestamp endTime = duration > 0 ? new Timestamp(System.currentTimeMillis() + duration * 1000) : null;
268        String hashedTokenAndSalt = DigestUtils.sha512Hex(token + salt);
269
270        
271        _generateToken(user, duration, autoRenewDuration, nbUsesLeft, contexts, type, comment, hashedTokenAndSalt, salt, creationDateTime, endTime);
272
273        String fullToken = user.getPopulationId() + TOKEN_SEPARATOR + user.getLogin() + TOKEN_SEPARATOR + token;
274        
275        try
276        {
277            return Base64.getEncoder().withoutPadding().encodeToString(fullToken.getBytes("UTF-8"));
278        }
279        catch (UnsupportedEncodingException e)
280        {
281            // can't occur ...
282            throw new RuntimeException(e);
283        }
284    }
285    
286    private void _generateToken(UserIdentity user, long duration, boolean autoRenewDuration, Integer nbUsesLeft, Set<String> contexts, String type, String comment, String hashedTokenAndSalt, String salt, Timestamp creationDateTime, Timestamp endTime) throws RuntimeException
287    {
288        ResultSet rs = null;
289        Connection connection = null;
290        PreparedStatement statement = null;
291        try
292        {
293            connection = ConnectionHelper.getConnection(_datasourceId);
294            String dbType = ConnectionHelper.getDatabaseType(connection);
295            if (ConnectionHelper.DATABASE_ORACLE.equals(dbType))
296            {
297                statement = connection.prepareStatement("SELECT seq_authenticationtoken.nextval FROM dual");
298                rs = statement.executeQuery();
299    
300                String id = null;
301                if (rs.next())
302                {
303                    id = rs.getString(1);
304                }
305                ConnectionHelper.cleanup(rs);
306                ConnectionHelper.cleanup(statement);
307                statement = connection.prepareStatement(
308                        "INSERT INTO Authentication_Token (id, " + TOKEN_SQL_SET_FIELDS + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
309                int nextParam = 1;
310                statement.setString(nextParam++, id);
311                statement.setString(nextParam++, user.getLogin());
312                statement.setString(nextParam++, user.getPopulationId());
313                statement.setString(nextParam++, hashedTokenAndSalt);
314                statement.setString(nextParam++, salt);
315                statement.setTimestamp(nextParam++, creationDateTime);
316                statement.setTimestamp(nextParam++, endTime);
317                if (nbUsesLeft == null)
318                {
319                    statement.setNull(nextParam++, java.sql.Types.INTEGER);
320                }
321                else
322                {
323                    statement.setInt(nextParam++, nbUsesLeft);
324                }
325    
326                if (!autoRenewDuration)
327                {
328                    statement.setNull(nextParam++, java.sql.Types.BIGINT);
329                }
330                else
331                {
332                    statement.setLong(nextParam++, duration);
333                }
334                
335                if (contexts == null)
336                {
337                    statement.setNull(nextParam++, java.sql.Types.VARCHAR);
338                }
339                else
340                {
341                    String contextString = _contextsToString(contexts);
342                    statement.setString(nextParam++, contextString);
343                }
344                statement.setString(nextParam++, type);
345                if (comment == null)
346                {
347                    statement.setNull(nextParam++, Types.BLOB);
348                }
349                else
350                {
351                    _sqlDatabaseTypeExtensionPoint.setBlob(dbType, statement, nextParam++, comment);
352                }
353            }
354            else
355            {
356                statement = connection.prepareStatement(
357                        "INSERT INTO Authentication_Token (" + TOKEN_SQL_SET_FIELDS + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
358                int nextParam = 1;
359                statement.setString(nextParam++, user.getLogin());
360                statement.setString(nextParam++, user.getPopulationId());
361                statement.setString(nextParam++, hashedTokenAndSalt);
362                statement.setString(nextParam++, salt);
363                statement.setTimestamp(nextParam++, creationDateTime);
364                statement.setTimestamp(nextParam++, endTime);
365                
366                if (nbUsesLeft == null)
367                {
368                    statement.setNull(nextParam++, java.sql.Types.INTEGER);
369                }
370                else
371                {
372                    statement.setInt(nextParam++, nbUsesLeft);
373                }
374    
375                if (!autoRenewDuration)
376                {
377                    statement.setNull(nextParam++, java.sql.Types.BIGINT);
378                }
379                else
380                {
381                    statement.setLong(nextParam++, duration);
382                }
383                
384                if (contexts == null)
385                {
386                    statement.setNull(nextParam++, java.sql.Types.VARCHAR);
387                }
388                else
389                {
390                    String contextString = _contextsToString(contexts);
391                    statement.setString(nextParam++, contextString);
392                }
393                
394                statement.setString(nextParam++, type);
395                _sqlDatabaseTypeExtensionPoint.setBlob(dbType, statement, nextParam++, comment);
396            }
397            
398            statement.executeUpdate();
399        }
400        catch (SQLException | UnsupportedEncodingException e)
401        {
402            throw new RuntimeException(e);
403        }
404        finally
405        {
406            ConnectionHelper.cleanup(rs);
407            ConnectionHelper.cleanup(connection);
408        }
409    }
410    
411    private String _contextsToString(Set<String> contexts)
412    {
413        return _jsonUtils.convertObjectToJson(contexts);
414    }
415    
416    private Set<String> _convertStringToContexts(String contexts)
417    {
418        if (contexts == null)
419        {
420            return Collections.EMPTY_SET;
421        }
422        try
423        {
424            List<Object> jsonList = _jsonUtils.convertJsonToList(contexts);
425            Set<String> result = jsonList.stream()
426                .filter(String.class::isInstance)
427                .map(String.class::cast)
428                .collect(Collectors.toSet());
429            
430            return result;
431        }
432        catch (IllegalArgumentException e)
433        {
434            // The input is not a json array, so it is just a string
435            return Collections.singleton(contexts);
436        }
437    }
438
439    private UserIdentity _validateToken(String encodedToken, String context, boolean forceRemove)
440    {
441        String token;
442        try
443        {
444            token = new String(Base64.getDecoder().decode(encodedToken), "UTF-8");
445        }
446        catch (UnsupportedEncodingException e)
447        {
448            // can't occur ...
449            throw new RuntimeException(e);
450        }
451        catch (Exception e)
452        {
453            // Exception occured during token decoding
454            return null;
455        }
456        
457        String[] split = StringUtils.split(token, TOKEN_SEPARATOR);
458        if (split == null || split.length != 3)
459        {
460            return null;
461        }
462
463        String populationId = split[0];
464        String login = split[1];
465        String tokenPart = split[2];
466
467        Connection connection = null;
468        try
469        {
470            connection = ConnectionHelper.getConnection(_datasourceId);
471
472            // Delete old entries
473            _deleteOldTokens(connection);
474
475            try (
476                    PreparedStatement selectStatement = _getSelectUserTokenStatement(connection, login, populationId, null);
477                    ResultSet resultSet = selectStatement.executeQuery()
478                )
479            {
480                // Find the database entry using this token
481                while (resultSet.next())
482                {
483                    if (resultSet.getString("token").equals(DigestUtils.sha512Hex(tokenPart + resultSet.getString("salt"))))
484                    {
485                        Token tokenToUpdate = _getTokenFromResultSet(resultSet, connection);
486                        // If the token doesn't have a context, this one is always valid
487                        // Else, the token context must be equal to the token context the context method parameter
488                        if (tokenToUpdate.getContexts() != null && tokenToUpdate.getContexts().size() > 0 && !tokenToUpdate.getContexts().contains(context))
489                        {
490                            continue;
491                        }
492                        
493                        // Delete it
494                        if (forceRemove)
495                        {
496                            _deleteUserToken(connection, tokenToUpdate.getId());
497                        }
498                        else
499                        {
500                            _updateUserToken(connection, tokenToUpdate);
501                        }
502                        
503                        // Control that the user exists
504                        UserIdentity identity = new UserIdentity(login, populationId);
505                        if (_userManager.getUser(identity) != null)
506                        {
507                            return identity;
508                        }
509                    }
510                }
511            }
512        }
513        catch (Exception e)
514        {
515            getLogger().error("Communication error with the database", e);
516        }
517        finally
518        {
519            ConnectionHelper.cleanup(connection);
520        }
521
522        return null;
523    }
524
525    /**
526     * Check if a token is valid and return the user
527     * 
528     * @param token The token to validate
529     * @return The user associated to the valid token, null otherwise
530     */
531    public UserIdentity validateToken(String token)
532    {
533        return validateToken(token, null);
534    }
535    
536    /**
537     * Check if a token is valid and return the user
538     * @param token The token to validate
539     * @param context context to validate the token with
540     * @return The user associated to the valid token, null otherwise
541     */
542    public UserIdentity validateToken(String token, String context)
543    {
544        return _validateToken(token, context, false);
545    }
546
547    /**
548     * Destroy the given token
549     * 
550     * @param token The token to remove
551     * @param context context of the token (null for no context)
552     */
553    public void deleteTokenByValue(String token, String context)
554    {
555        _validateToken(token, context, true);
556    }
557
558    /**
559     * Destroy the given token
560     * 
561     * @param tokenId The token identifier to remove
562     */
563    public void deleteTokenById(Integer tokenId)
564    {
565        Connection connection = null;
566        try
567        {
568            connection = ConnectionHelper.getConnection(_datasourceId);
569
570            _deleteUserToken(connection, tokenId);
571        }
572        catch (SQLException e)
573        {
574            throw new RuntimeException("Could not delete the authentication token with identifier " + tokenId, e);
575        }
576        finally
577        {
578            ConnectionHelper.cleanup(connection);
579        }
580    }
581
582    /**
583     * Generates the sql statement that deletes the entries of the users token
584     * database that are old
585     * 
586     * @param connection the database's session
587     * @throws SQLException if a sql exception occurs
588     */
589    private void _deleteOldTokens(Connection connection) throws SQLException
590    {
591        try (PreparedStatement statement = connection.prepareStatement("DELETE FROM Authentication_Token WHERE end_date < ? OR nb_uses_left = ?"))
592        {
593            statement.setTimestamp(1, new Timestamp(System.currentTimeMillis()));
594            statement.setInt(2, 0);
595            statement.executeUpdate();
596        }
597    }
598
599    /**
600     * Generates the statement that selects the users having the specified login
601     * in the Authentication_Token table
602     * 
603     * @param connection the database's session
604     * @param login The login of the user
605     * @param populationId The populationId of the user
606     * @param type The type to filter or null to get all
607     * @return the retrieve statement
608     * @throws SQLException if a sql exception occurs
609     */
610    private PreparedStatement _getSelectUserTokenStatement(Connection connection, String login, String populationId, String type) throws SQLException
611    {
612        String sqlRequest = "SELECT " + TOKEN_SQL_GET_FIELDS + " FROM Authentication_Token WHERE login=? AND population_id=?"
613                + (type != null ? " AND type=?" : "");
614
615        PreparedStatement statement = connection.prepareStatement(sqlRequest);
616        int nextParam = 1;
617        statement.setString(nextParam++, login);
618        statement.setString(nextParam++, populationId);
619        if (type != null)
620        {
621            statement.setString(nextParam++, type);
622        }
623
624        return statement;
625    }
626    private Token _getTokenFromResultSet(ResultSet resultSet, Connection connection) throws SQLException, IOException
627    {
628        String dbType = ConnectionHelper.getDatabaseType(connection);
629        String comment = null;
630        try (InputStream blob = _sqlDatabaseTypeExtensionPoint.getBlob(dbType, resultSet, "token_comment"))
631        {
632            if (blob != null)
633            {
634                comment = IOUtils.toString(blob, "UTF-8");
635            }
636        }
637        
638        Integer nbUsesLeft = resultSet.getInt("nb_uses_left");
639        if (resultSet.wasNull())
640        {
641            nbUsesLeft = null;
642        }
643        
644        Long autoRenewDuration = resultSet.getLong("auto_renew_duration");
645        if (resultSet.wasNull())
646        {
647            autoRenewDuration = null;
648        }
649        String contextString = resultSet.getString("context");
650        Set<String> contexts = _convertStringToContexts(contextString);
651        Token token = new Token(resultSet.getInt("id"), resultSet.getString("type"), comment, resultSet.getTimestamp("creation_date"),
652                resultSet.getTimestamp("end_date"), resultSet.getTimestamp("last_update_date"), nbUsesLeft, autoRenewDuration, contexts);
653        return token;
654    }
655
656    /**
657     * Deletes the database entry that has this token
658     * 
659     * @param connection the database's session
660     * @param id the token id
661     * @throws SQLException if an error occurred
662     */
663    private void _deleteUserToken(Connection connection, Integer id) throws SQLException
664    {
665        try (PreparedStatement statement = connection.prepareStatement("DELETE FROM Authentication_Token WHERE id = ?"))
666        {
667            statement.setInt(1, id);
668            statement.executeUpdate();
669        }
670    }
671
672    /**
673     * Update the last update date in the database
674     * 
675     * @param connection the database's session
676     * @param token the token
677     * @throws SQLException if an error occurred
678     */
679    private void _updateUserToken(Connection connection, Token token) throws SQLException
680    {
681        Integer nbUsesLeft = token.getNbUsesLeft();
682        if (nbUsesLeft != null && nbUsesLeft > 0)
683        {
684            nbUsesLeft--;
685        }
686
687        // Delete if no uses left
688        if (nbUsesLeft != null && nbUsesLeft == 0)
689        {
690            _deleteUserToken(connection, token.getId());
691        }
692        // Update last update_date and nb_use_left if needed
693        // And update end_date if the token have auto renew
694        else if (token.getAutoRenewDuration() != null)
695        {
696            Long autoRenewDuration = token.getAutoRenewDuration();
697            Timestamp endDate = new Timestamp(new Date().getTime() + autoRenewDuration * 1000);
698
699            try (PreparedStatement statement = connection.prepareStatement("UPDATE Authentication_Token SET last_update_date = ?, end_date = ?, nb_uses_left = ? WHERE id = ?"))
700            {
701                Timestamp lastUpdateDate = new Timestamp(new Date().getTime());
702                statement.setTimestamp(1, lastUpdateDate);
703                statement.setTimestamp(2, endDate);
704                if (nbUsesLeft == null)
705                {
706                    statement.setNull(3, java.sql.Types.INTEGER);
707                }
708                else
709                {
710                    statement.setInt(3, nbUsesLeft);
711                }
712                statement.setInt(4, token.getId());
713                statement.executeUpdate();
714            }
715        }
716        else
717        {
718            try (PreparedStatement statement = connection.prepareStatement("UPDATE Authentication_Token SET last_update_date = ?, nb_uses_left = ? WHERE id = ?"))
719            {
720                Timestamp lastUpdateDate = new Timestamp(new Date().getTime());
721                statement.setTimestamp(1, lastUpdateDate);
722                if (nbUsesLeft == null)
723                {
724                    statement.setNull(2, java.sql.Types.INTEGER);
725                }
726                else
727                {
728                    statement.setInt(2, nbUsesLeft);
729                }
730                statement.setInt(3, token.getId());
731                statement.executeUpdate();
732            }
733        }
734    }
735
736    /**
737     * Generate a new authentication token
738     * 
739     * @param parameters a map of the following parameters for the
740     *            authentication token : description
741     * @return The generated token
742     */
743    @Callable
744    public String generateAuthenticationToken(Map<String, Object> parameters)
745    {
746        String description = (String) parameters.get("description");
747        String generateToken = generateToken(0, USER_TOKEN_TYPE, description);
748
749        return generateToken;
750    }
751
752    /**
753     * Delete one or multiples authentication token
754     * 
755     * @param ids a list of authentication token ids
756     */
757    @Callable
758    public void deleteAuthenticationToken(List<Integer> ids)
759    {
760        for (Integer tokenId : ids)
761        {
762            deleteTokenById(tokenId);
763        }
764    }
765
766    /**
767     * An Ametys authentication token
768     */
769    public static class Token
770    {
771        /** The token identifier */
772        protected Integer _id;
773
774        /** The token type */
775        protected String _type;
776
777        /** The token associated comment */
778        protected String _comment;
779
780        /** The token creation date */
781        protected Date _creationDate;
782
783        /** The token end date */
784        protected Date _endDate;
785
786        /** The token last update date */
787        protected Date _lastUpdateDate;
788        
789        /** The token number of use left */
790        protected Integer _nbUsesLeft;
791        
792        /** The token auto renewal duration, in seconds */
793        protected Long _autoRenewDuration;
794        
795        /** The token context */
796        protected Set<String> _contexts;
797
798        /**
799         * Creates a Token
800         * 
801         * @param id The identifier
802         * @param type The type of token
803         * @param comment The comment. Can be null.
804         * @param creationDate The creation date. Can be null.
805         * @param endDate The end date. Can be null.
806         * @param lastUpdateDate The last update date. Can be null.
807         * @param nbUsesLeft nb of uses left for this token (null for no limitation)
808         * @param autoRenewDuration duration that will be added (from "now") each time the token is updated
809         * @param contexts contexts of the token (ressource id for example)
810         */
811        protected Token(Integer id, String type, String comment, Date creationDate, Date endDate, Date lastUpdateDate, Integer nbUsesLeft, Long autoRenewDuration, Set<String> contexts)
812        {
813            _id = id;
814            _type = type;
815            _comment = comment;
816            _creationDate = creationDate;
817            _endDate = endDate;
818            _lastUpdateDate = lastUpdateDate;
819            _nbUsesLeft = nbUsesLeft;
820            _autoRenewDuration = autoRenewDuration;
821            _contexts = contexts;
822        }
823
824        /**
825         * Get the token identifier
826         * 
827         * @return The identifier
828         */
829        public Integer getId()
830        {
831            return _id;
832        }
833
834        /**
835         * Get the token type
836         * 
837         * @return The type
838         */
839        public String getType()
840        {
841            return _type;
842        }
843
844        /**
845         * Get the associated creation comment
846         * 
847         * @return The comment or null
848         */
849        public String getComment()
850        {
851            return _comment;
852        }
853
854        /**
855         * Get the creation date of the token
856         * 
857         * @return The creation date or null
858         */
859        public Date getCreationDate()
860        {
861            return _creationDate;
862        }
863
864        /**
865         * Get the end date of the token validity
866         * 
867         * @return The end date or null
868         */
869        public Date getEndDate()
870        {
871            return _endDate;
872        }
873
874        /**
875         * Get the last update date of the token
876         * 
877         * @return The last update date or null
878         */
879        public Date getLastUpdateDate()
880        {
881            return _endDate;
882        }
883
884        /**
885         * Get the duration which the token should be automatically renewed on each use, in seconds
886         * 
887         * @return The auto renewal duration in seconds or null if not applicable
888         */
889        public Long getAutoRenewDuration()
890        {
891            return _autoRenewDuration;
892        }
893
894        /**
895         * Get the context on which the token can be used
896         * 
897         * @return The token contexts or null if not applicable
898         */
899        public Set<String> getContexts()
900        {
901            return _contexts;
902        }
903
904        /**
905         * Get the number of uses allowed for this token
906         * 
907         * @return The number of allowed uses or null if not applicable
908         */
909        public Integer getNbUsesLeft()
910        {
911            return _nbUsesLeft;
912        }
913        
914        
915    }
916}