]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7231 Create new "Security" page in "My Space"
authorStas Vilchik <vilchiks@gmail.com>
Tue, 26 Jan 2016 14:46:59 +0000 (15:46 +0100)
committerStas Vilchik <vilchiks@gmail.com>
Thu, 28 Jan 2016 15:14:24 +0000 (16:14 +0100)
it/it-tests/src/test/java/it/Category1Suite.java
it/it-tests/src/test/java/it/user/MyAccountPageTest.java
it/it-tests/src/test/resources/user/MyAccountPageTest/should_change_password.html [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/app.js
server/sonar-web/src/main/js/apps/account/components/Nav.js
server/sonar-web/src/main/js/apps/account/components/Password.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/components/Security.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/components/Tokens.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/containers/SecurityContainer.js [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/account/index.html.erb
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 16333217c235074b9b71774a901413257edf1c39..5153eb1c298cd1eca4eb7a886223bd139e9bef98 100644 (file)
@@ -65,6 +65,7 @@ import it.settings.PropertySetsTest;
 import it.settings.SettingsTest;
 import it.settings.SettingsTestRestartingOrchestrator;
 import it.settings.SubCategoriesTest;
+import it.user.MyAccountPageTest;
 import org.junit.ClassRule;
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
@@ -110,7 +111,8 @@ import static util.ItUtils.xooPlugin;
   TimeMachineTest.class,
   // action plan
   ActionPlanTest.class,
-  ActionPlanUiTest.class
+  ActionPlanUiTest.class,
+  MyAccountPageTest.class
 })
 public class Category1Suite {
 
index e3425497689276a26621cb6edc139ea19607be42..5188171ec238ef59ae97275cdb321933748ed2f7 100644 (file)
@@ -22,26 +22,31 @@ package it.user;
 import com.sonar.orchestrator.Orchestrator;
 import com.sonar.orchestrator.selenium.Selenese;
 import it.Category1Suite;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.sonar.wsclient.SonarClient;
-import org.sonar.wsclient.user.UserParameters;
+import org.junit.*;
+import org.sonarqube.ws.client.PostRequest;
+import org.sonarqube.ws.client.WsClient;
 import util.selenium.SeleneseTest;
 
+import static util.ItUtils.newAdminWsClient;
+
 public class MyAccountPageTest {
 
   @ClassRule
   public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR;
+  private static WsClient adminWsClient;
 
   @BeforeClass
-  public static void initUser() {
+  public static void setUp() {
+    adminWsClient = newAdminWsClient(orchestrator);
+  }
+
+  @Before
+  public void initUser() {
     createUser("account-user", "User With Account", "user@example.com");
   }
 
-  @AfterClass
-  public static void deleteTestUser() {
+  @After
+  public void deleteTestUser() {
     deactivateUser("account-user");
   }
 
@@ -53,19 +58,27 @@ public class MyAccountPageTest {
     new SeleneseTest(selenese).runOn(orchestrator);
   }
 
+  @Test
+  public void should_change_password() throws Exception {
+    Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("should_change_password",
+      "/user/MyAccountPageTest/should_change_password.html"
+    ).build();
+    new SeleneseTest(selenese).runOn(orchestrator);
+  }
+
   private static void createUser(String login, String name, String email) {
-    SonarClient client = orchestrator.getServer().adminWsClient();
-    UserParameters userCreationParameters = UserParameters.create()
-      .login(login)
-      .name(name)
-      .email(email)
-      .password("password")
-      .passwordConfirmation("password");
-    client.userClient().create(userCreationParameters);
+    adminWsClient.wsConnector().call(
+      new PostRequest("api/users/create")
+        .setParam("login", login)
+        .setParam("name", name)
+        .setParam("email", email)
+        .setParam("password", "password"));
   }
 
-  private static void deactivateUser(String user) {
-    orchestrator.getServer().adminWsClient().userClient().deactivate(user);
+  private static void deactivateUser(String login) {
+    adminWsClient.wsConnector().call(
+      new PostRequest("api/users/deactivate")
+        .setParam("login", login));
   }
 
 }
diff --git a/it/it-tests/src/test/resources/user/MyAccountPageTest/should_change_password.html b/it/it-tests/src/test/resources/user/MyAccountPageTest/should_change_password.html
new file mode 100644 (file)
index 0000000..50da6be
--- /dev/null
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+  <link rel="selenium.base" href="http://localhost:49506"/>
+  <title>should_change_password</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+<td rowspan="1" colspan="3">should_change_password</td>
+</tr>
+</thead>
+<tbody>
+<tr>
+       <td>open</td>
+       <td>/sonar/sessions/login</td>
+       <td></td>
+</tr>
+<tr>
+       <td>type</td>
+       <td>login</td>
+       <td>account-user</td>
+</tr>
+<tr>
+       <td>type</td>
+       <td>password</td>
+       <td>password</td>
+</tr>
+<tr>
+       <td>clickAndWait</td>
+       <td>commit</td>
+       <td></td>
+</tr>
+<tr>
+       <td>open</td>
+       <td>/sonar/account/security</td>
+       <td></td>
+</tr>
+<tr>
+       <td>waitForElementPresent</td>
+       <td>id=change-password</td>
+       <td></td>
+</tr>
+<tr>
+       <td>click</td>
+       <td>id=change-password</td>
+       <td></td>
+</tr>
+<tr>
+       <td>waitForElementPresent</td>
+       <td>id=old_password</td>
+       <td></td>
+</tr>
+<tr>
+       <td>type</td>
+       <td>id=old_password</td>
+       <td>password</td>
+</tr>
+<tr>
+       <td>type</td>
+       <td>id=password</td>
+       <td>new_password</td>
+</tr>
+<tr>
+       <td>type</td>
+       <td>id=password_confirmation</td>
+       <td>new_password</td>
+</tr>
+<tr>
+       <td>click</td>
+       <td>commit</td>
+       <td></td>
+</tr>
+<tr>
+       <td>open</td>
+       <td>/sonar/sessions/logout</td>
+       <td></td>
+</tr>
+<tr>
+       <td>open</td>
+       <td>/sonar/sessions/login</td>
+       <td></td>
+</tr>
+<tr>
+       <td>type</td>
+       <td>login</td>
+       <td>account-user</td>
+</tr>
+<tr>
+       <td>type</td>
+       <td>password</td>
+       <td>new_password</td>
+</tr>
+<tr>
+       <td>clickAndWait</td>
+       <td>commit</td>
+       <td></td>
+</tr>
+<tr>
+       <td>waitForText</td>
+       <td>id=global-navigation</td>
+       <td>*User With Account*</td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
index 4a78a6a04fbb3cfc2f970d330169a8b4ca043025..fe969e8ad7d511f1ca537046f0288b2214ef6181 100644 (file)
@@ -27,6 +27,7 @@ import configureStore from './store/configureStore';
 import AccountApp from './containers/AccountApp';
 import Home from './components/Home';
 import NotificationsContainer from './containers/NotificationsContainer';
+import SecurityContainer from './containers/SecurityContainer';
 
 window.sonarqube.appStarted.then(options => {
   const el = document.querySelector(options.el);
@@ -46,6 +47,7 @@ window.sonarqube.appStarted.then(options => {
           <Route path="/" component={AccountApp}>
             <IndexRoute component={Home}/>
             <Route path="notifications" component={NotificationsContainer}/>
+            <Route path="security" component={SecurityContainer}/>
 
             <Redirect from="/index" to="/"/>
           </Route>
index e19213d3062b729f7f82f83ddfb7dc0706fb5770..4a49e1f752051a20b374033e99824bd09d265103 100644 (file)
@@ -43,6 +43,11 @@ const Nav = () => (
               {translate('my_account.notifications')}
             </IndexLink>
           </li>
+          <li>
+            <IndexLink to="security" activeClassName="active">
+              {translate('my_account.security')}
+            </IndexLink>
+          </li>
         </ul>
       </div>
     </nav>
diff --git a/server/sonar-web/src/main/js/apps/account/components/Password.js b/server/sonar-web/src/main/js/apps/account/components/Password.js
new file mode 100644 (file)
index 0000000..7da00b1
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React, { Component } from 'react';
+
+import ChangePasswordView from '../change-password-view';
+
+export default class Password extends Component {
+  handleChangePassword () {
+    new ChangePasswordView().render();
+  }
+
+  render () {
+    return (
+        <section>
+          <h2 className="spacer-bottom">Password</h2>
+          <button
+              id="change-password"
+              onClick={this.handleChangePassword.bind(this)}
+              type="submit">
+            Change Password
+          </button>
+        </section>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/account/components/Security.js b/server/sonar-web/src/main/js/apps/account/components/Security.js
new file mode 100644 (file)
index 0000000..684fb74
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+
+import Password from './Password';
+import Tokens from './Tokens';
+
+export default function Security ({ user }) {
+  return (
+      <div className="columns">
+        <div className="column-half">
+          <Tokens user={user}/>
+        </div>
+
+        {user.canChangePassword && (
+            <div className="column-half">
+              <Password/>
+            </div>
+        )}
+      </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/account/components/Tokens.js b/server/sonar-web/src/main/js/apps/account/components/Tokens.js
new file mode 100644 (file)
index 0000000..8c31703
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import Backbone from 'backbone';
+import React, { Component } from 'react';
+
+import TokensView from '../tokens-view';
+
+export default class Tokens extends Component {
+  componentDidMount () {
+    this.renderView();
+  }
+
+  componentWillUnmount () {
+    this.destroyView();
+  }
+
+  renderView () {
+    const account = new Backbone.Model({
+      id: this.props.user.login
+    });
+
+    this.tokensView = new TokensView({
+      el: this.refs.container,
+      model: account
+    }).render();
+  }
+
+  destroyView () {
+    if (this.destroyView) {
+      this.tokensView.destroy();
+    }
+  }
+
+  render () {
+    return <div ref="container"></div>;
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/account/containers/SecurityContainer.js b/server/sonar-web/src/main/js/apps/account/containers/SecurityContainer.js
new file mode 100644 (file)
index 0000000..4fdb31f
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+
+import Security from './../components/Security';
+
+export default function SecurityContainer () {
+  const { user } = window.sonarqube;
+
+  return <Security user={user}/>;
+}
index f5a9a100226b82250c3a13d5167e03be95992248..8a6209d7daa82d806786f4e5325623ee7b6101ae 100644 (file)
@@ -4,6 +4,7 @@
       login: '<%= escape_javascript current_user.login -%>',
       name: '<%= escape_javascript current_user.name -%>',
       email: '<%= escape_javascript current_user.email -%>',
+      canChangePassword: <%= User.editable_password? ? 'true' : 'false' -%>,
       groups: [
         <% current_user.groups.sort.each do |group| -%>
           '<%= escape_javascript group.name -%>',
index fad0339a29de1a77b2a66daef3ca1c7064da40e3..76b0d18e512ac8035fc9bcd1e8e0bd8d8e890799 100644 (file)
@@ -2161,6 +2161,7 @@ my_account.favorite_issue_filters=Favorite Issue Filters
 my_account.favorite_measure_filters=Favorite Measure Filters
 my_account.notifications=Notifications
 my_account.no_project_notifications=You have not set project notifications yet.
+my_account.security=Security