You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ak...@apache.org on 2018/03/23 02:55:17 UTC

ignite git commit: IGNITE-8002 REST: Added support of new authentication via new API.

Repository: ignite
Updated Branches:
  refs/heads/master 14f7bcea5 -> 921f0cf9b


IGNITE-8002 REST: Added support of new authentication via new API.


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

Branch: refs/heads/master
Commit: 921f0cf9b3ab6454339fa57b929093f56372c61e
Parents: 14f7bce
Author: Alexey Kuznetsov <ak...@apache.org>
Authored: Fri Mar 23 09:55:13 2018 +0700
Committer: Alexey Kuznetsov <ak...@apache.org>
Committed: Fri Mar 23 09:55:13 2018 +0700

----------------------------------------------------------------------
 .../client/suite/IgniteClientTestSuite.java     |   2 +
 .../JettyRestProcessorAbstractSelfTest.java     |   4 +-
 ...ettyRestProcessorAuthenticationSelfTest.java | 172 +++++++++++++++++++
 .../processors/rest/GridRestCommand.java        |  15 +-
 .../processors/rest/GridRestProcessor.java      | 132 +++++++++-----
 .../handlers/user/UserActionCommandHandler.java |  97 +++++++++++
 .../rest/handlers/user/package-info.java        |  22 +++
 .../protocols/tcp/GridTcpRestNioListener.java   |   7 +-
 .../rest/request/GridRestRequest.java           |  21 ++-
 .../rest/request/RestUserActionRequest.java     |  64 +++++++
 .../http/jetty/GridJettyRestHandler.java        |  24 ++-
 11 files changed, 507 insertions(+), 53 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/921f0cf9/modules/clients/src/test/java/org/apache/ignite/internal/client/suite/IgniteClientTestSuite.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/client/suite/IgniteClientTestSuite.java b/modules/clients/src/test/java/org/apache/ignite/internal/client/suite/IgniteClientTestSuite.java
index 223268c..79fcf38 100644
--- a/modules/clients/src/test/java/org/apache/ignite/internal/client/suite/IgniteClientTestSuite.java
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/client/suite/IgniteClientTestSuite.java
@@ -49,6 +49,7 @@ import org.apache.ignite.internal.client.util.ClientByteUtilsTest;
 import org.apache.ignite.internal.client.util.ClientConsistentHashSelfTest;
 import org.apache.ignite.internal.client.util.ClientJavaHasherSelfTest;
 import org.apache.ignite.internal.processors.rest.ClientMemcachedProtocolSelfTest;
+import org.apache.ignite.internal.processors.rest.JettyRestProcessorAuthenticationSelfTest;
 import org.apache.ignite.internal.processors.rest.JettyRestProcessorSignedSelfTest;
 import org.apache.ignite.internal.processors.rest.JettyRestProcessorUnsignedSelfTest;
 import org.apache.ignite.internal.processors.rest.RestBinaryProtocolSelfTest;
@@ -86,6 +87,7 @@ public class IgniteClientTestSuite extends TestSuite {
         // Test jetty rest processor
         suite.addTestSuite(JettyRestProcessorSignedSelfTest.class);
         suite.addTestSuite(JettyRestProcessorUnsignedSelfTest.class);
+        suite.addTestSuite(JettyRestProcessorAuthenticationSelfTest.class);
 
         // Test TCP rest processor with original memcache client.
         suite.addTestSuite(ClientMemcachedProtocolSelfTest.class);

http://git-wip-us.apache.org/repos/asf/ignite/blob/921f0cf9/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 ece467a..5dc44c4 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
@@ -281,13 +281,13 @@ public abstract class JettyRestProcessorAbstractSelfTest extends AbstractRestPro
      * @param content Content to check.
      * @param err Error message.
      */
-    private void assertResponseContainsError(String content, String err) throws IOException {
+    protected void assertResponseContainsError(String content, String err) throws IOException {
         assertFalse(F.isEmpty(content));
         assertNotNull(err);
 
         JsonNode node = JSON_MAPPER.readTree(content);
 
-        assertEquals(STATUS_FAILED, node.get("successStatus").asInt());
+        assertTrue(node.get("successStatus").asInt() != STATUS_SUCCESS);
         assertTrue(node.get("response").isNull());
         assertTrue(node.get("error").asText().contains(err));
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/921f0cf9/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorAuthenticationSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorAuthenticationSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorAuthenticationSelfTest.java
new file mode 100644
index 0000000..ca62091
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorAuthenticationSelfTest.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.rest;
+
+import java.util.concurrent.Callable;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.apache.ignite.configuration.DataRegionConfiguration;
+import org.apache.ignite.configuration.DataStorageConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.processors.authentication.IgniteAccessControlException;
+import org.apache.ignite.internal.processors.authentication.IgniteAuthenticationProcessor;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.testframework.GridTestUtils;
+
+import static org.apache.ignite.configuration.WALMode.NONE;
+
+/**
+ * Test REST with enabled authentication.
+ */
+public class JettyRestProcessorAuthenticationSelfTest extends JettyRestProcessorUnsignedSelfTest {
+    /** */
+    private static final String DFLT_LOGIN = "ignite";
+
+    /** */
+    private static final String DFLT_PWD = "ignite";
+
+    /** */
+    private String login = DFLT_LOGIN;
+
+    /** */
+    private String pwd = DFLT_PWD;
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        U.resolveWorkDirectory(U.defaultWorkDirectory(), "db", true);
+
+        super.beforeTestsStarted();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        login = DFLT_LOGIN;
+        pwd = DFLT_PWD;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void initCache() {
+        // We need to activate cluster.
+        grid(0).cluster().active(true);
+
+        super.initCache();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
+
+        cfg.setAuthenticationEnabled(true);
+
+        // For now persistence is needed because of current authentication implementation.
+        DataStorageConfiguration dsCfg = new DataStorageConfiguration()
+            .setDefaultDataRegionConfiguration(new DataRegionConfiguration()
+                .setMaxSize(100 * 1024 * 1024))
+            .setWalMode(NONE);
+
+        DataRegionConfiguration testDataRegionCfg = new DataRegionConfiguration();
+        testDataRegionCfg.setName("testDataRegion");
+        testDataRegionCfg.setMaxSize(10 * 1024 * 1024);
+
+        DataRegionConfiguration authDataRegionCfg = new DataRegionConfiguration();
+        authDataRegionCfg.setName("authDataRegion")
+            .setMaxSize(10 * 1024 * 1024)
+            .setPersistenceEnabled(true);
+
+        dsCfg.setDataRegionConfigurations(testDataRegionCfg, authDataRegionCfg);
+
+        cfg.setDataStorageConfiguration(dsCfg);
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected String restUrl() {
+        String url = super.restUrl();
+
+        if (!F.isEmpty(login)) {
+            url += "ignite.login=" + login;
+
+            if (!F.isEmpty(pwd))
+                url += "&ignite.password=" + pwd;
+
+            url += '&';
+        }
+
+        return url;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testMissingCredentials() throws Exception {
+        login = null;
+        pwd = null;
+
+        String ret = content(null, GridRestCommand.VERSION);
+
+        assertResponseContainsError(ret, "The user name or password is incorrect");
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testAddUpdateRemoveUser() throws Exception {
+        // Add user.
+        String ret = content(null, GridRestCommand.ADD_USER,
+            "user", "user1",
+            "password", "password1");
+
+        JsonNode res = jsonResponse(ret);
+
+        assertTrue(res.asBoolean());
+
+        IgniteAuthenticationProcessor auth = grid(0).context().authentication();
+
+        assertNotNull(auth.authenticate("user1", "password1"));
+
+        // Update user password.
+        ret = content(null, GridRestCommand.UPDATE_USER,
+            "user", "user1",
+            "password", "password2");
+
+        res = jsonResponse(ret);
+
+        assertTrue(res.asBoolean());
+
+        assertNotNull(auth.authenticate("user1", "password2"));
+
+        // Remove user.
+        ret = content(null, GridRestCommand.REMOVE_USER,
+            "user", "user1");
+
+        res = jsonResponse(ret);
+
+        assertTrue(res.asBoolean());
+
+        GridTestUtils.assertThrows(log, new Callable<Object>() {
+            @Override public Object call() throws Exception {
+                grid(0).context().authentication().authenticate("user1", "password1");
+
+                return null;
+            }
+        }, IgniteAccessControlException.class, "The user name or password is incorrect");
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/921f0cf9/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestCommand.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestCommand.java
index 2b53b54..0b9a662 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestCommand.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestCommand.java
@@ -166,7 +166,16 @@ public enum GridRestCommand {
     CLUSTER_INACTIVE("inactive"),
 
     /** */
-    CLUSTER_CURRENT_STATE("currentstate");
+    CLUSTER_CURRENT_STATE("currentstate"),
+
+    /** */
+    ADD_USER("adduser"),
+
+    /** */
+    REMOVE_USER("removeuser"),
+
+    /** */
+    UPDATE_USER("updateuser");
 
     /** Enum values. */
     private static final GridRestCommand[] VALS = values();
@@ -174,9 +183,7 @@ public enum GridRestCommand {
     /** Key to enum map. */
     private static final Map<String, GridRestCommand> cmds = new HashMap<>();
 
-    /**
-     * Map keys to commands.
-     */
+    // Map keys to commands.
     static {
         for (GridRestCommand cmd : values())
             cmds.put(cmd.key(), cmd);

http://git-wip-us.apache.org/repos/asf/ignite/blob/921f0cf9/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 de9e63b..4b8497e 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
@@ -34,6 +34,7 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.LongAdder;
+import org.apache.ignite.IgniteAuthenticationException;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteSystemProperties;
 import org.apache.ignite.configuration.ConnectorConfiguration;
@@ -41,6 +42,7 @@ import org.apache.ignite.configuration.ConnectorMessageInterceptor;
 import org.apache.ignite.internal.GridKernalContext;
 import org.apache.ignite.internal.IgniteInternalFuture;
 import org.apache.ignite.internal.processors.GridProcessorAdapter;
+import org.apache.ignite.internal.processors.authentication.AuthorizationContext;
 import org.apache.ignite.internal.processors.rest.client.message.GridClientTaskResultBean;
 import org.apache.ignite.internal.processors.rest.handlers.GridRestCommandHandler;
 import org.apache.ignite.internal.processors.rest.handlers.cache.GridCacheCommandHandler;
@@ -50,6 +52,7 @@ import org.apache.ignite.internal.processors.rest.handlers.log.GridLogCommandHan
 import org.apache.ignite.internal.processors.rest.handlers.query.QueryCommandHandler;
 import org.apache.ignite.internal.processors.rest.handlers.task.GridTaskCommandHandler;
 import org.apache.ignite.internal.processors.rest.handlers.top.GridTopologyCommandHandler;
+import org.apache.ignite.internal.processors.rest.handlers.user.UserActionCommandHandler;
 import org.apache.ignite.internal.processors.rest.handlers.version.GridVersionCommandHandler;
 import org.apache.ignite.internal.processors.rest.protocols.tcp.GridTcpRestProtocol;
 import org.apache.ignite.internal.processors.rest.request.GridRestCacheRequest;
@@ -218,7 +221,10 @@ public class GridRestProcessor extends GridProcessorAdapter {
         if (log.isDebugEnabled())
             log.debug("Received request from client: " + req);
 
-        if (ctx.security().enabled()) {
+        boolean authenticationEnabled = ctx.authentication().enabled();
+        boolean securityEnabled = ctx.security().enabled();
+
+        if (authenticationEnabled || securityEnabled) {
             Session ses;
 
             try {
@@ -237,25 +243,54 @@ public class GridRestProcessor extends GridProcessorAdapter {
 
             if (log.isDebugEnabled())
                 log.debug("Next clientId and sessionToken were extracted according to request: " +
-                    "[clientId="+req.clientId()+", sesTok="+Arrays.toString(req.sessionToken())+"]");
+                    "[clientId=" + req.clientId() + ", sesTok=" + Arrays.toString(req.sessionToken()) + "]");
 
-            SecurityContext secCtx0 = ses.secCtx;
+            if (securityEnabled) {
+                SecurityContext secCtx0 = ses.secCtx;
 
-            try {
-                if (secCtx0 == null)
-                    ses.secCtx = secCtx0 = authenticate(req);
+                try {
+                    if (secCtx0 == null)
+                        ses.secCtx = secCtx0 = authenticate(req);
 
-                authorize(req, secCtx0);
+                    authorize(req, secCtx0);
+                }
+                catch (SecurityException e) {
+                    assert secCtx0 != null;
+
+                    return new GridFinishedFuture<>(new GridRestResponse(STATUS_SECURITY_CHECK_FAILED, e.getMessage()));
+                }
+                catch (IgniteCheckedException e) {
+                    return new GridFinishedFuture<>(new GridRestResponse(STATUS_AUTH_FAILED, e.getMessage()));
+                }
             }
-            catch (SecurityException e) {
-                assert secCtx0 != null;
+            else {
+                AuthorizationContext authCtx0 = ses.authCtx;
 
-                GridRestResponse res = new GridRestResponse(STATUS_SECURITY_CHECK_FAILED, e.getMessage());
+                try {
+                    if (authCtx0 == null) {
+                        SecurityCredentials creds = credentials(req);
 
-                return new GridFinishedFuture<>(res);
-            }
-            catch (IgniteCheckedException e) {
-                return new GridFinishedFuture<>(new GridRestResponse(STATUS_AUTH_FAILED, e.getMessage()));
+                        String login = null;
+
+                        if (creds.getLogin() instanceof String)
+                            login = (String)creds.getLogin();
+
+                        String pwd = null;
+
+                        if (creds.getPassword() instanceof String)
+                            pwd = (String)creds.getPassword();
+
+                        if (F.isEmpty(login) || F.isEmpty(pwd))
+                            throw new IgniteAuthenticationException("The user name or password is incorrect");
+
+                        ses.authCtx = ctx.authentication().authenticate(login, pwd);
+
+                        req.authorizationContext(ses.authCtx);
+                    }
+                }
+                catch (IgniteCheckedException e) {
+                    return new GridFinishedFuture<>(new GridRestResponse(STATUS_AUTH_FAILED, e.getMessage()));
+                }
             }
         }
 
@@ -469,6 +504,7 @@ public class GridRestProcessor extends GridProcessorAdapter {
             addHandler(new QueryCommandHandler(ctx));
             addHandler(new GridLogCommandHandler(ctx));
             addHandler(new GridChangeStateCommandHandler(ctx));
+            addHandler(new UserActionCommandHandler(ctx));
 
             // Start protocols.
             startTcpProtocol();
@@ -645,7 +681,9 @@ public class GridRestProcessor extends GridProcessorAdapter {
                     break;
             }
         }
-    }    /**
+    }
+
+    /**
      * Applies interceptor to a response object.
      * Specially handler {@link Map} and {@link Collection} responses.
      *
@@ -679,6 +717,35 @@ public class GridRestProcessor extends GridProcessorAdapter {
     }
 
     /**
+     * Extract credentials from request.
+     *
+     * @param req Request.
+     * @return Security credentials.
+     */
+    private SecurityCredentials credentials(GridRestRequest req) {
+        Object creds = req.credentials();
+
+        if (creds instanceof SecurityCredentials)
+            return  (SecurityCredentials)creds;
+
+        if (creds instanceof String) {
+            String credStr = (String)creds;
+
+            int idx = credStr.indexOf(':');
+
+            return idx >= 0 && idx < credStr.length() ?
+                new SecurityCredentials(credStr.substring(0, idx), credStr.substring(idx + 1)) :
+                new SecurityCredentials(credStr, null);
+        }
+
+        SecurityCredentials cred = new SecurityCredentials();
+
+        cred.setUserObject(creds);
+
+        return cred;
+    }
+
+    /**
      * Authenticates remote client.
      *
      * @param req Request to authenticate.
@@ -693,37 +760,16 @@ public class GridRestProcessor extends GridProcessorAdapter {
         authCtx.subjectType(REMOTE_CLIENT);
         authCtx.subjectId(req.clientId());
         authCtx.nodeAttributes(Collections.<String, Object>emptyMap());
-
-        SecurityCredentials cred;
-
-        if (req.credentials() instanceof SecurityCredentials)
-            cred = (SecurityCredentials)req.credentials();
-        else if (req.credentials() instanceof String) {
-            String credStr = (String)req.credentials();
-
-            int idx = credStr.indexOf(':');
-
-            cred = idx >= 0 && idx < credStr.length() ?
-                new SecurityCredentials(credStr.substring(0, idx), credStr.substring(idx + 1)) :
-                new SecurityCredentials(credStr, null);
-        }
-        else {
-            cred = new SecurityCredentials();
-
-            cred.setUserObject(req.credentials());
-        }
-
         authCtx.address(req.address());
-
-        authCtx.credentials(cred);
+        authCtx.credentials(credentials(req));
 
         SecurityContext subjCtx = ctx.security().authenticate(authCtx);
 
         if (subjCtx == null) {
             if (req.credentials() == null)
                 throw new IgniteCheckedException("Failed to authenticate remote client (secure session SPI not set?): " + req);
-            else
-                throw new IgniteCheckedException("Failed to authenticate remote client (invalid credentials?): " + req);
+
+            throw new IgniteCheckedException("Failed to authenticate remote client (invalid credentials?): " + req);
         }
 
         return subjCtx;
@@ -814,6 +860,9 @@ public class GridRestProcessor extends GridProcessorAdapter {
             case CLUSTER_CURRENT_STATE:
             case CLUSTER_ACTIVE:
             case CLUSTER_INACTIVE:
+            case ADD_USER:
+            case REMOVE_USER:
+            case UPDATE_USER:
                 break;
 
             default:
@@ -935,6 +984,9 @@ public class GridRestProcessor extends GridProcessorAdapter {
         /** Security context. */
         private volatile SecurityContext secCtx;
 
+        /** Authorization context. */
+        private volatile AuthorizationContext authCtx;
+
         /**
          * @param clientId Client ID.
          * @param sesId session ID.
@@ -977,7 +1029,7 @@ public class GridRestProcessor extends GridProcessorAdapter {
          * Checks expiration of session and if expired then sets TIMEDOUT_FLAG.
          *
          * @param sesTimeout Session timeout.
-         * @return <code>True</code> if expired.
+         * @return {@code True} if expired.
          * @see #touch()
          */
         boolean isTimedOut(long sesTimeout) {

http://git-wip-us.apache.org/repos/asf/ignite/blob/921f0cf9/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/user/UserActionCommandHandler.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/user/UserActionCommandHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/user/UserActionCommandHandler.java
new file mode 100644
index 0000000..40870a3
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/user/UserActionCommandHandler.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.rest.handlers.user;
+
+import java.util.Collection;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.processors.authentication.AuthorizationContext;
+import org.apache.ignite.internal.processors.authentication.IgniteAuthenticationProcessor;
+import org.apache.ignite.internal.processors.rest.GridRestCommand;
+import org.apache.ignite.internal.processors.rest.GridRestResponse;
+import org.apache.ignite.internal.processors.rest.handlers.GridRestCommandHandlerAdapter;
+import org.apache.ignite.internal.processors.rest.request.GridRestRequest;
+import org.apache.ignite.internal.processors.rest.request.RestUserActionRequest;
+import org.apache.ignite.internal.util.future.GridFinishedFuture;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+import static org.apache.ignite.internal.processors.rest.GridRestCommand.ADD_USER;
+import static org.apache.ignite.internal.processors.rest.GridRestCommand.REMOVE_USER;
+import static org.apache.ignite.internal.processors.rest.GridRestCommand.UPDATE_USER;
+
+/**
+ * User actions handler.
+ */
+public class UserActionCommandHandler extends GridRestCommandHandlerAdapter {
+    /** Commands. */
+    private static final Collection<GridRestCommand> SUPPORTED_COMMANDS = U.sealList(ADD_USER, REMOVE_USER, UPDATE_USER);
+
+    /**
+     * @param ctx Context.
+     */
+    public UserActionCommandHandler(GridKernalContext ctx) {
+        super(ctx);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Collection<GridRestCommand> supportedCommands() {
+        return SUPPORTED_COMMANDS;
+    }
+
+    /** {@inheritDoc} */
+    @Override public IgniteInternalFuture<GridRestResponse> handleAsync(GridRestRequest req) {
+        assert req != null;
+
+        if (log.isDebugEnabled())
+            log.debug("Handling topology REST request: " + req);
+
+        RestUserActionRequest req0 = (RestUserActionRequest)req;
+
+        try {
+            GridRestCommand cmd = req.command();
+
+            IgniteAuthenticationProcessor authentication = ctx.authentication();
+
+            AuthorizationContext.context(req.authorizationContext());
+
+            switch (cmd) {
+                case ADD_USER:
+                    authentication.addUser(req0.user(), req0.password());
+                    break;
+
+                case REMOVE_USER:
+                    authentication.removeUser(req0.user());
+                    break;
+
+                case UPDATE_USER:
+                    authentication.updateUser(req0.user(), req0.password());
+                    break;
+            }
+
+            if (log.isDebugEnabled())
+                log.debug("Handled topology REST request [req=" + req + ']');
+
+            return new GridFinishedFuture<>(new GridRestResponse(true));
+        }
+        catch (Throwable e) {
+            log.error("Failed to handle REST request [req=" + req + ']', e);
+
+            return new GridFinishedFuture<>(e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/921f0cf9/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/user/package-info.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/user/package-info.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/user/package-info.java
new file mode 100644
index 0000000..79cfd99
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/user/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * <!-- Package description. -->
+ * REST user commands.
+ */
+package org.apache.ignite.internal.processors.rest.handlers.user;

http://git-wip-us.apache.org/repos/asf/ignite/blob/921f0cf9/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/GridTcpRestNioListener.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/GridTcpRestNioListener.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/GridTcpRestNioListener.java
index a44cc7c..23c25c7 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/GridTcpRestNioListener.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/GridTcpRestNioListener.java
@@ -99,9 +99,7 @@ public class GridTcpRestNioListener extends GridNioServerListenerAdapter<GridCli
     /** Supported protocol versions. */
     private static final Collection<Short> SUPP_VERS = new HashSet<>();
 
-    /**
-     * Fills {@code cacheCmdMap}.
-     */
+    // Fills {@code cacheCmdMap}.
     static {
         cacheCmdMap.put(PUT, CACHE_PUT);
         cacheCmdMap.put(PUT_ALL, CACHE_PUT_ALL);
@@ -356,7 +354,8 @@ public class GridTcpRestNioListener extends GridNioServerListenerAdapter<GridCli
                 restTopReq.command(TOPOLOGY);
 
             restReq = restTopReq;
-        }else if (msg instanceof GridClientStateRequest) {
+        }
+        else if (msg instanceof GridClientStateRequest) {
             GridClientStateRequest req = (GridClientStateRequest)msg;
 
             GridRestChangeStateRequest restChangeReq = new GridRestChangeStateRequest();

http://git-wip-us.apache.org/repos/asf/ignite/blob/921f0cf9/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestRequest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestRequest.java
index 57cb91b..2d90733 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestRequest.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestRequest.java
@@ -19,9 +19,11 @@ package org.apache.ignite.internal.processors.rest.request;
 
 import java.net.InetSocketAddress;
 import java.util.UUID;
+import org.apache.ignite.internal.processors.authentication.AuthorizationContext;
 import org.apache.ignite.internal.processors.rest.GridRestCommand;
 import org.apache.ignite.internal.util.tostring.GridToStringExclude;
 import org.apache.ignite.internal.util.typedef.internal.S;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Grid command request.
@@ -46,6 +48,9 @@ public class GridRestRequest {
     /** Command. */
     private GridRestCommand cmd;
 
+    /** */
+    private AuthorizationContext authCtx;
+
     /**
      * @return Destination ID.
      */
@@ -142,8 +147,22 @@ public class GridRestRequest {
         this.addr = addr;
     }
 
+    /**
+     * @return Authorization context.
+     */
+    @Nullable public AuthorizationContext authorizationContext() {
+        return authCtx;
+    }
+
+    /**
+     * @param authCtx Authorization context.
+     */
+    public void authorizationContext(AuthorizationContext authCtx) {
+        this.authCtx = authCtx;
+    }
+
     /** {@inheritDoc} */
     @Override public String toString() {
         return S.toString(GridRestRequest.class, this);
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/921f0cf9/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/RestUserActionRequest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/RestUserActionRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/RestUserActionRequest.java
new file mode 100644
index 0000000..bf569df
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/RestUserActionRequest.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.rest.request;
+
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * User request.
+ */
+public class RestUserActionRequest extends GridRestRequest {
+    /** User name. */
+    private String user;
+
+    /** Password. */
+    private String pwd;
+
+    /**
+     * @param user User name.
+     */
+    public void user(String user) {
+        this.user = user;
+    }
+
+    /**
+     * @return User name.
+     */
+    public String user() {
+        return user;
+    }
+
+    /**
+     * @param pwd User password.
+     */
+    public void password(String pwd) {
+        this.pwd = pwd;
+    }
+
+    /**
+     * @return User password.
+     */
+    public String password() {
+        return pwd;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(RestUserActionRequest.class, this, super.toString());
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/921f0cf9/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java
----------------------------------------------------------------------
diff --git a/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java b/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java
index 85398cc..b3fbddd 100644
--- a/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java
+++ b/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java
@@ -55,6 +55,7 @@ import org.apache.ignite.internal.processors.rest.request.GridRestRequest;
 import org.apache.ignite.internal.processors.rest.request.GridRestTaskRequest;
 import org.apache.ignite.internal.processors.rest.request.GridRestTopologyRequest;
 import org.apache.ignite.internal.processors.rest.request.RestQueryRequest;
+import org.apache.ignite.internal.processors.rest.request.RestUserActionRequest;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.lang.IgniteClosure;
@@ -100,6 +101,12 @@ public class GridJettyRestHandler extends AbstractHandler {
     private static final String WRITE_SYNCHRONIZATION_MODE_PARAM = "writeSynchronizationMode";
 
     /** */
+    private static final String IGNITE_LOGIN = "ignite.login";
+
+    /** */
+    private static final String IGNITE_PASSWORD = "ignite.password";
+
+    /** */
     private static final String  TEMPLATE_NAME_PARAM = "templateName";
 
     /** */
@@ -724,6 +731,19 @@ public class GridJettyRestHandler extends AbstractHandler {
                 break;
             }
 
+            case ADD_USER:
+            case REMOVE_USER:
+            case UPDATE_USER: {
+                RestUserActionRequest restReq0 = new RestUserActionRequest();
+
+                restReq0.user((String)params.get("user"));
+                restReq0.password((String)params.get("password"));
+
+                restReq = restReq0;
+
+                break;
+            }
+
             case EXECUTE_SQL_QUERY:
             case EXECUTE_SQL_FIELDS_QUERY: {
                 RestQueryRequest restReq0 = new RestQueryRequest();
@@ -820,9 +840,9 @@ public class GridJettyRestHandler extends AbstractHandler {
 
         restReq.command(cmd);
 
-        if (params.containsKey("ignite.login") || params.containsKey("ignite.password")) {
+        if (params.containsKey(IGNITE_LOGIN) || params.containsKey(IGNITE_PASSWORD)) {
             SecurityCredentials cred = new SecurityCredentials(
-                (String)params.get("ignite.login"), (String)params.get("ignite.password"));
+                (String)params.get(IGNITE_LOGIN), (String)params.get(IGNITE_PASSWORD));
 
             restReq.credentials(cred);
         }