You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sb...@apache.org on 2015/10/09 16:47:03 UTC

[2/7] ignite git commit: Fix REST authentication.

Fix REST authentication.


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/f025714e
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/f025714e
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/f025714e

Branch: refs/heads/ignite-1093-2
Commit: f025714ef0d21ffff7ebf6088c254f26dd9aa3fc
Parents: f60cba7
Author: ashutak <as...@gridgain.com>
Authored: Wed Oct 7 16:11:18 2015 +0300
Committer: ashutak <as...@gridgain.com>
Committed: Wed Oct 7 16:11:18 2015 +0300

----------------------------------------------------------------------
 .../JettyRestProcessorAbstractSelfTest.java     |  27 +-
 .../apache/ignite/IgniteSystemProperties.java   |   3 +
 .../processors/rest/GridRestProcessor.java      | 356 +++++++++++++++++--
 .../handlers/cache/GridCacheCommandHandler.java |   2 +-
 .../ignite/internal/util/IgniteUtils.java       |   2 +-
 5 files changed, 341 insertions(+), 49 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/f025714e/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorAbstractSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorAbstractSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorAbstractSelfTest.java
index 8db4cd7..ac0edff 100644
--- a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorAbstractSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorAbstractSelfTest.java
@@ -84,6 +84,13 @@ public abstract class JettyRestProcessorAbstractSelfTest extends AbstractRestPro
     protected abstract int restPort();
 
     /**
+     * @return Security enabled flag. Should be the same with {@code ctx.security().enabled()}.
+     */
+    protected boolean securityEnabled() {
+        return false;
+    }
+
+    /**
      * @param params Command parameters.
      * @return Returned content.
      * @throws Exception If failed.
@@ -133,7 +140,7 @@ public abstract class JettyRestProcessorAbstractSelfTest extends AbstractRestPro
         return "\\{\\\"affinityNodeId\\\":\\\"\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}\\\"\\," +
             "\\\"error\\\":\\\"\\\"\\," +
             "\\\"response\\\":\\\"" + res + "\\\"\\," +
-            "\\\"sessionToken\\\":\\\"\\\"," +
+            "\\\"sessionToken\\\":\\\"" + (securityEnabled() && success ? ".+" : "") + "\\\"," +
             "\\\"successStatus\\\":" + (success ? 0 : 1) + "\\}";
     }
 
@@ -157,7 +164,7 @@ public abstract class JettyRestProcessorAbstractSelfTest extends AbstractRestPro
     private String integerPattern(int res, boolean success) {
         return "\\{\\\"error\\\":\\\"\\\"\\," +
             "\\\"response\\\":" + res + "\\," +
-            "\\\"sessionToken\\\":\\\"\\\"," +
+            "\\\"sessionToken\\\":\\\"" + (securityEnabled() && success ? ".+" : "") + "\\\"," +
             "\\\"successStatus\\\":" + (success ? 0 : 1) + "\\}";
     }
 
@@ -170,7 +177,7 @@ public abstract class JettyRestProcessorAbstractSelfTest extends AbstractRestPro
         return "\\{\\\"affinityNodeId\\\":\\\"\\\"\\," +
             "\\\"error\\\":\\\"\\\"\\," +
             "\\\"response\\\":" + res + "\\," +
-            "\\\"sessionToken\\\":\\\"\\\"," +
+            "\\\"sessionToken\\\":\\\"" + (securityEnabled() && success ? ".+" : "") + "\\\"," +
             "\\\"successStatus\\\":" + (success ? 0 : 1) + "\\}";
     }
 
@@ -183,7 +190,7 @@ public abstract class JettyRestProcessorAbstractSelfTest extends AbstractRestPro
         return "\\{\\\"affinityNodeId\\\":\\\"\\\"\\," +
             "\\\"error\\\":\\\"\\\"\\," +
             "\\\"response\\\":" + res + "\\," +
-            "\\\"sessionToken\\\":\\\"\\\"," +
+            "\\\"sessionToken\\\":\\\"" + (securityEnabled() && success ? ".+" : "") + "\\\"," +
             "\\\"successStatus\\\":" + (success ? 0 : 1) + "\\}";
     }
 
@@ -196,7 +203,7 @@ public abstract class JettyRestProcessorAbstractSelfTest extends AbstractRestPro
         return "\\{\\\"affinityNodeId\\\":\\\"\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}\\\"\\," +
             "\\\"error\\\":\\\"\\\"\\," +
             "\\\"response\\\":" + res + "\\," +
-            "\\\"sessionToken\\\":\\\"\\\"," +
+            "\\\"sessionToken\\\":\\\"" + (securityEnabled() && success ? ".+" : "") + "\\\"," +
             "\\\"successStatus\\\":" + (success ? 0 : 1) + "\\}";
     }
 
@@ -209,7 +216,7 @@ public abstract class JettyRestProcessorAbstractSelfTest extends AbstractRestPro
         return "\\{\\\"affinityNodeId\\\":\\\"\\\"\\," +
             "\\\"error\\\":\\\"\\\"\\," +
             "\\\"response\\\":" + res + "\\," +
-            "\\\"sessionToken\\\":\\\"\\\"," +
+            "\\\"sessionToken\\\":\\\"" + (securityEnabled() && success ? ".+" : "") + "\\\"," +
             "\\\"successStatus\\\":" + (success ? 0 : 1) + "\\}";
     }
 
@@ -222,7 +229,7 @@ public abstract class JettyRestProcessorAbstractSelfTest extends AbstractRestPro
         return "\\{\\\"affinityNodeId\\\":\\\"(\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12})?\\\"\\," +
             "\\\"error\\\":\\\"\\\"\\," +
             "\\\"response\\\":" + res + "\\," +
-            "\\\"sessionToken\\\":\\\"\\\"," +
+            "\\\"sessionToken\\\":\\\"" + (securityEnabled() && success ? ".+" : "") + "\\\"," +
             "\\\"successStatus\\\":" + (success ? 0 : 1) + "\\}";
     }
 
@@ -234,7 +241,7 @@ public abstract class JettyRestProcessorAbstractSelfTest extends AbstractRestPro
     private String pattern(String res, boolean success) {
         return "\\{\\\"error\\\":\\\"" + (!success ? ".+" : "") + "\\\"\\," +
             "\\\"response\\\":" + res + "\\," +
-            "\\\"sessionToken\\\":\\\"\\\"," +
+            "\\\"sessionToken\\\":\\\"" + (securityEnabled() && success ? ".+" : "") + "\\\"," +
             "\\\"successStatus\\\":" + (success ? 0 : 1) + "\\}";
     }
 
@@ -246,7 +253,7 @@ public abstract class JettyRestProcessorAbstractSelfTest extends AbstractRestPro
     private String stringPattern(String res, boolean success) {
         return "\\{\\\"error\\\":\\\"" + (!success ? ".+" : "") + "\\\"\\," +
             "\\\"response\\\":\\\"" + res + "\\\"\\," +
-            "\\\"sessionToken\\\":\\\"\\\"," +
+            "\\\"sessionToken\\\":\\\"" + (securityEnabled() && success ? ".+" : "") + "\\\"," +
             "\\\"successStatus\\\":" + (success ? 0 : 1) + "\\}";
     }
 
@@ -1316,4 +1323,4 @@ public abstract class JettyRestProcessorAbstractSelfTest extends AbstractRestPro
             return id;
         }
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/f025714e/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
index 1e4c8b7..5d3b08b 100644
--- a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
+++ b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
@@ -96,6 +96,9 @@ public final class IgniteSystemProperties {
      */
     public static final String IGNITE_JETTY_LOG_NO_OVERRIDE = "IGNITE_JETTY_LOG_NO_OVERRIDE";
 
+    /** This property allow rewriting default ({@code 30}) rest session expire time (in seconds). */
+    public static final String IGNITE_REST_SESSION_TIMEOUT = "IGNITE_REST_SESSION_TIMEOUT";
+
     /**
      * This property allows to override maximum count of task results stored on one node
      * in REST processor.

http://git-wip-us.apache.org/repos/asf/ignite/blob/f025714e/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestProcessor.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestProcessor.java
index d606ba4..d54c8bb 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestProcessor.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.processors.rest;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.EnumMap;
 import java.util.HashMap;
@@ -30,7 +31,9 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicLong;
 import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteSystemProperties;
 import org.apache.ignite.configuration.ConnectorConfiguration;
 import org.apache.ignite.configuration.ConnectorMessageInterceptor;
 import org.apache.ignite.internal.GridKernalContext;
@@ -53,8 +56,10 @@ import org.apache.ignite.internal.processors.security.SecurityContext;
 import org.apache.ignite.internal.util.GridSpinReadWriteLock;
 import org.apache.ignite.internal.util.future.GridFinishedFuture;
 import org.apache.ignite.internal.util.typedef.C1;
+import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.X;
 import org.apache.ignite.internal.util.typedef.internal.LT;
+import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.internal.util.worker.GridWorker;
 import org.apache.ignite.internal.util.worker.GridWorkerFuture;
@@ -65,6 +70,7 @@ import org.apache.ignite.plugin.security.AuthenticationContext;
 import org.apache.ignite.plugin.security.SecurityCredentials;
 import org.apache.ignite.plugin.security.SecurityException;
 import org.apache.ignite.plugin.security.SecurityPermission;
+import org.apache.ignite.thread.IgniteThread;
 import org.jsr166.LongAdder8;
 
 import static org.apache.ignite.internal.processors.rest.GridRestResponse.STATUS_AUTH_FAILED;
@@ -80,8 +86,11 @@ public class GridRestProcessor extends GridProcessorAdapter {
     private static final String HTTP_PROTO_CLS =
         "org.apache.ignite.internal.processors.rest.protocols.http.jetty.GridJettyRestProtocol";
 
-    /** */
-    public static final byte[] ZERO_BYTES = new byte[0];
+    /** Delay between sessions timeout checks. */
+    private static final int SES_TIMEOUT_CHECK_DELAY = 1_000;
+
+    /** Default session timout. */
+    private static final int DEFAULT_SES_TIMEOUT = 30_000;
 
     /** Protocols. */
     private final Collection<GridRestProtocol> protos = new ArrayList<>();
@@ -98,8 +107,14 @@ public class GridRestProcessor extends GridProcessorAdapter {
     /** Workers count. */
     private final LongAdder8 workersCnt = new LongAdder8();
 
-    /** SecurityContext map. */
-    private ConcurrentMap<UUID, SecurityContext> sesMap = new ConcurrentHashMap<>();
+    /** ClientId-SessionId map. */
+    private final ConcurrentMap<UUID, UUID> clientId2SesId = new ConcurrentHashMap<>();
+
+    /** SessionId-Session map. */
+    private final ConcurrentMap<UUID, Session> sesId2Ses = new ConcurrentHashMap<>();
+
+    /** */
+    private final Thread sesTimeoutCheckerThread;
 
     /** Protocol handler. */
     private final GridRestProtocolHandler protoHnd = new GridRestProtocolHandler() {
@@ -112,6 +127,9 @@ public class GridRestProcessor extends GridProcessorAdapter {
         }
     };
 
+    /** Session time to live. */
+    private final long sesTtl;
+
     /**
      * @param req Request.
      * @return Future.
@@ -195,22 +213,40 @@ public class GridRestProcessor extends GridProcessorAdapter {
         if (log.isDebugEnabled())
             log.debug("Received request from client: " + req);
 
-        SecurityContext subjCtx = null;
-
         if (ctx.security().enabled()) {
+            Session ses;
+
             try {
-                subjCtx = authenticate(req);
+                ses = session(req);
+            }
+            catch (IgniteCheckedException e) {
+                GridRestResponse res = new GridRestResponse(STATUS_FAILED, e.getMessage());
 
-                authorize(req, subjCtx);
+                return new GridFinishedFuture<>(res);
+            }
+
+            assert ses != null;
+
+            req.clientId(ses.clientId);
+            req.sessionToken(U.uuidToBytes(ses.sesId));
+
+            if (log.isDebugEnabled())
+                log.debug("Next clientId and sessionToken were extracted according to request: " +
+                    "[clientId="+req.clientId()+", sesTok="+Arrays.toString(req.sessionToken())+"]");
+
+            SecurityContext secCtx0 = ses.secCtx;
+
+            try {
+                if (secCtx0 == null)
+                    ses.secCtx = secCtx0 = authenticate(req);
+
+                authorize(req, secCtx0);
             }
             catch (SecurityException e) {
-                assert subjCtx != null;
+                assert secCtx0 != null;
 
                 GridRestResponse res = new GridRestResponse(STATUS_SECURITY_CHECK_FAILED, e.getMessage());
 
-                updateSession(req, subjCtx);
-                res.sessionTokenBytes(ZERO_BYTES);
-
                 return new GridFinishedFuture<>(res);
             }
             catch (IgniteCheckedException e) {
@@ -228,16 +264,18 @@ public class GridRestProcessor extends GridProcessorAdapter {
             return new GridFinishedFuture<>(
                 new IgniteCheckedException("Failed to find registered handler for command: " + req.command()));
 
-        final SecurityContext subjCtx0 = subjCtx;
-
         return res.chain(new C1<IgniteInternalFuture<GridRestResponse>, GridRestResponse>() {
             @Override public GridRestResponse apply(IgniteInternalFuture<GridRestResponse> f) {
                 GridRestResponse res;
 
+                boolean failed = false;
+
                 try {
                     res = f.get();
                 }
                 catch (Exception e) {
+                    failed = true;
+
                     if (!X.hasCause(e, VisorClusterGroupEmptyException.class))
                         LT.error(log, e, "Failed to handle request: " + req.command());
 
@@ -249,10 +287,8 @@ public class GridRestProcessor extends GridProcessorAdapter {
 
                 assert res != null;
 
-                if (ctx.security().enabled()) {
-                    updateSession(req, subjCtx0);
-                    res.sessionTokenBytes(ZERO_BYTES);
-                }
+                if (ctx.security().enabled() && !failed)
+                    res.sessionTokenBytes(req.sessionToken());
 
                 interceptResponse(res, req);
 
@@ -262,10 +298,137 @@ public class GridRestProcessor extends GridProcessorAdapter {
     }
 
     /**
+     * @param req Request.
+     * @return Not null session.
+     * @throws IgniteCheckedException If failed.
+     */
+    private Session session(final GridRestRequest req) throws IgniteCheckedException {
+        final UUID clientId = req.clientId();
+        final byte[] sesTok = req.sessionToken();
+
+        while (true) {
+            if (F.isEmpty(sesTok) && clientId == null) {
+                Session ses = Session.random();
+
+                UUID oldSesId = clientId2SesId.put(ses.clientId, ses.sesId);
+
+                assert oldSesId == null : "Got an illegal state for request: " + req;
+
+                Session oldSes = sesId2Ses.put(ses.sesId, ses);
+
+                assert oldSes == null : "Got an illegal state for request: " + req;
+
+                return ses;
+            }
+
+            if (F.isEmpty(sesTok) && clientId != null) {
+                UUID sesId = clientId2SesId.get(clientId);
+
+                if (sesId == null) {
+                    Session ses = Session.fromClientId(clientId);
+
+                    if (clientId2SesId.putIfAbsent(ses.clientId, ses.sesId) != null)
+                        continue; // Another thread already register session with the clientId.
+
+                    Session prevSes = sesId2Ses.put(ses.sesId, ses);
+
+                    assert prevSes == null : "Got an illegal state for request: " + req;
+
+                    return ses;
+                }
+                else {
+                    Session ses = sesId2Ses.get(sesId);
+
+                    if (ses == null || !ses.touch())
+                        continue; // Need to wait while timeout thread complete removing of timed out sessions.
+
+                    return ses;
+                }
+            }
+
+            if (!F.isEmpty(sesTok) && clientId == null) {
+                UUID sesId = U.bytesToUuid(sesTok, 0);
+
+                Session ses = sesId2Ses.get(sesId);
+
+                if (ses == null)
+                    throw new IgniteCheckedException("Failed to handle request - unknown session token " +
+                        "(maybe expired session) [sesTok=" + U.byteArray2HexString(sesTok) + "]");
+
+                if (!ses.touch())
+                    continue; // Need to wait while timeout thread complete removing of timed out sessions.
+
+                return ses;
+            }
+
+            if (!F.isEmpty(sesTok) && clientId != null) {
+                UUID sesId = clientId2SesId.get(clientId);
+
+                if (sesId == null || !sesId.equals(U.bytesToUuid(sesTok, 0)))
+                    throw new IgniteCheckedException("Failed to handle request - unsupported case (misamatched " +
+                        "clientId and session token) [clientId=" + clientId + ", sesTok=" +
+                        U.byteArray2HexString(sesTok) + "]");
+
+                Session ses = sesId2Ses.get(sesId);
+
+                if (ses == null)
+                    throw new IgniteCheckedException("Failed to handle request - unknown session token " +
+                        "(maybe expired session) [sesTok=" + U.byteArray2HexString(sesTok) + "]");
+
+                if (!ses.touch())
+                    continue; // Need to wait while timeout thread complete removing of timed out sessions.
+
+                return ses;
+            }
+
+            assert false : "Got an unreachable state.";
+        }
+    }
+
+    /**
      * @param ctx Context.
      */
     public GridRestProcessor(GridKernalContext ctx) {
         super(ctx);
+
+        long sesExpTime0;
+        String sesExpTime = null;
+
+        try {
+            sesExpTime = System.getProperty(IgniteSystemProperties.IGNITE_REST_SESSION_TIMEOUT);
+
+            if (sesExpTime != null)
+                sesExpTime0 = Long.valueOf(sesExpTime) * 1000;
+            else
+                sesExpTime0 = DEFAULT_SES_TIMEOUT;
+        }
+        catch (NumberFormatException ignore) {
+            U.warn(log, "Failed parsing IGNITE_REST_SESSION_TIMEOUT system variable [IGNITE_REST_SESSION_TIMEOUT="
+                + sesExpTime + "]");
+
+            sesExpTime0 = DEFAULT_SES_TIMEOUT;
+        }
+
+        sesTtl = sesExpTime0;
+
+        sesTimeoutCheckerThread = new IgniteThread(ctx.gridName(), "session-timeout-worker",
+            new GridWorker(ctx.gridName(), "session-timeout-worker", log) {
+                @Override protected void body() throws InterruptedException {
+                    while (!isCancelled()) {
+                        Thread.sleep(SES_TIMEOUT_CHECK_DELAY);
+
+                        for (Map.Entry<UUID, Session> e : sesId2Ses.entrySet()) {
+                            Session ses = e.getValue();
+
+                            if (ses.isTimedOut(sesTtl)) {
+                                sesId2Ses.remove(ses.sesId, ses);
+
+                                clientId2SesId.remove(ses.clientId, ses.sesId);
+                            }
+                        }
+                    }
+                }
+            });
     }
 
     /** {@inheritDoc} */
@@ -310,6 +473,10 @@ public class GridRestProcessor extends GridProcessorAdapter {
             for (GridRestProtocol proto : protos)
                 proto.onKernalStart();
 
+            sesTimeoutCheckerThread.setDaemon(true);
+
+            sesTimeoutCheckerThread.start();
+
             startLatch.countDown();
 
             if (log.isDebugEnabled())
@@ -334,6 +501,8 @@ public class GridRestProcessor extends GridProcessorAdapter {
                 }
             }
 
+            U.interrupt(sesTimeoutCheckerThread);
+
             if (interrupted)
                 Thread.currentThread().interrupt();
 
@@ -483,13 +652,8 @@ public class GridRestProcessor extends GridProcessorAdapter {
      * @throws IgniteCheckedException If authentication failed.
      */
     private SecurityContext authenticate(GridRestRequest req) throws IgniteCheckedException {
-        UUID clientId = req.clientId();
-        SecurityContext secCtx = clientId == null ? null : sesMap.get(clientId);
+        assert req.clientId() != null;
 
-        if (secCtx != null)
-            return secCtx;
-
-        // Authenticate client if invalid session.
         AuthenticationContext authCtx = new AuthenticationContext();
 
         authCtx.subjectType(REMOTE_CLIENT);
@@ -531,18 +695,6 @@ public class GridRestProcessor extends GridProcessorAdapter {
     }
 
     /**
-     * Update session.
-     * @param req REST request.
-     * @param sCtx Security context.
-     */
-    private void updateSession(GridRestRequest req, SecurityContext sCtx) {
-        if (sCtx != null) {
-            UUID id = req.clientId();
-            sesMap.put(id, sCtx);
-        }
-    }
-
-    /**
      * @param req REST request.
      * @param sCtx Security context.
      * @throws SecurityException If authorization failed.
@@ -719,4 +871,134 @@ public class GridRestProcessor extends GridProcessorAdapter {
         X.println(">>>   protosSize: " + protos.size());
         X.println(">>>   handlersSize: " + handlers.size());
     }
-}
\ No newline at end of file
+
+    /**
+     * Session.
+     */
+    private static class Session {
+        /** Expiration flag. It's a final state of lastToucnTime. */
+        private static final Long TIMEDOUT_FLAG = 0L;
+
+        /** Client id. */
+        private final UUID clientId;
+
+        /** Session token id. */
+        private final UUID sesId;
+
+        /** Security context. */
+        private volatile SecurityContext secCtx;
+
+        /**
+         * Time when session is used last time.
+         * If this time was set at TIMEDOUT_FLAG, then it should never be changed.
+         */
+        private final AtomicLong lastTouchTime = new AtomicLong(U.currentTimeMillis());
+
+        /**
+         * @param clientId Client ID.
+         * @param sesId session ID.
+         */
+        private Session(UUID clientId, UUID sesId) {
+            this.clientId = clientId;
+            this.sesId = sesId;
+        }
+
+        /**
+         * Static constructor.
+         *
+         * @return New session instance with random client ID and random session ID.
+         */
+        static Session random() {
+            return new Session(UUID.randomUUID(), UUID.randomUUID());
+        }
+
+        /**
+         * Static constructor.
+         *
+         * @param clientId Client ID.
+         * @return New session instance with given client ID and random session ID.
+         */
+        static Session fromClientId(UUID clientId) {
+            return new Session(clientId, UUID.randomUUID());
+        }
+
+        /**
+         * Static constructor.
+         *
+         * @param sesTokId Session token ID.
+         * @return New session instance with random client ID and given session ID.
+         */
+        static Session fromSessionToken(UUID sesTokId) {
+            return new Session(UUID.randomUUID(), sesTokId);
+        }
+
+        /**
+         * Checks expiration of session and if expired then sets TIMEDOUT_FLAG.
+         *
+         * @param sesTimeout Session timeout.
+         * @return <code>True</code> if expired.
+         * @see #touch()
+         */
+        boolean isTimedOut(long sesTimeout) {
+            long time0 = lastTouchTime.get();
+
+            if (time0 == TIMEDOUT_FLAG)
+                return true;
+
+            return U.currentTimeMillis() - time0 > sesTimeout && lastTouchTime.compareAndSet(time0, TIMEDOUT_FLAG);
+        }
+
+        /**
+         * Checks whether session at expired state (EPIRATION_FLAG) or not, if not then tries to update last touch time.
+         *
+         * @return {@code False} if session timed out (not successfully touched).
+         * @see #isTimedOut(long)
+         */
+        boolean touch() {
+            while (true) {
+                long time0 = lastTouchTime.get();
+
+                if (time0 == TIMEDOUT_FLAG)
+                    return false;
+
+                boolean success = lastTouchTime.compareAndSet(time0, U.currentTimeMillis());
+
+                if (success)
+                    return true;
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o)
+                return true;
+
+            if (!(o instanceof Session))
+                return false;
+
+            Session ses = (Session)o;
+
+            if (clientId != null ? !clientId.equals(ses.clientId) : ses.clientId != null)
+                return false;
+
+            if (sesId != null ? !sesId.equals(ses.sesId) : ses.sesId != null)
+                return false;
+
+            return true;
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            int res = clientId != null ? clientId.hashCode() : 0;
+
+            res = 31 * res + (sesId != null ? sesId.hashCode() : 0);
+
+            return res;
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return S.toString(Session.class, this);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/f025714e/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/cache/GridCacheCommandHandler.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/cache/GridCacheCommandHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/cache/GridCacheCommandHandler.java
index b3af2f2..9d32c17 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/cache/GridCacheCommandHandler.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/cache/GridCacheCommandHandler.java
@@ -1391,4 +1391,4 @@ public class GridCacheCommandHandler extends GridRestCommandHandlerAdapter {
             return c.sizeAsync(new CachePeekMode[]{CachePeekMode.PRIMARY});
         }
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/f025714e/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
index e5090cb..3c1913a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
@@ -9302,4 +9302,4 @@ public abstract class IgniteUtils {
             throw new IgniteInterruptedCheckedException(e);
         }
     }
-}
\ No newline at end of file
+}