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