summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Godeps/Godeps.json5
-rw-r--r--Godeps/_workspace/src/github.com/gorilla/handlers/.travis.yml17
-rw-r--r--Godeps/_workspace/src/github.com/gorilla/handlers/LICENSE22
-rw-r--r--Godeps/_workspace/src/github.com/gorilla/handlers/README.md53
-rw-r--r--Godeps/_workspace/src/github.com/gorilla/handlers/canonical.go74
-rw-r--r--Godeps/_workspace/src/github.com/gorilla/handlers/compress.go145
-rw-r--r--Godeps/_workspace/src/github.com/gorilla/handlers/cors.go317
-rw-r--r--Godeps/_workspace/src/github.com/gorilla/handlers/doc.go9
-rw-r--r--Godeps/_workspace/src/github.com/gorilla/handlers/handlers.go403
-rw-r--r--Godeps/_workspace/src/github.com/gorilla/handlers/proxy_headers.go113
-rw-r--r--Godeps/_workspace/src/github.com/gorilla/handlers/recovery.go86
-rw-r--r--Makefile64
-rw-r--r--api/admin.go83
-rw-r--r--api/admin_test.go324
-rw-r--r--api/api.go78
-rw-r--r--api/api_test.go47
-rw-r--r--api/apitestlib.go223
-rw-r--r--api/authentication.go99
-rw-r--r--api/auto_channels.go12
-rw-r--r--api/auto_environment.go12
-rw-r--r--api/auto_posts.go2
-rw-r--r--api/auto_teams.go4
-rw-r--r--api/auto_users.go31
-rw-r--r--api/channel.go101
-rw-r--r--api/channel_benchmark_test.go115
-rw-r--r--api/channel_test.go368
-rw-r--r--api/command.go44
-rw-r--r--api/command_echo_test.go19
-rw-r--r--api/command_join.go2
-rw-r--r--api/command_join_test.go25
-rw-r--r--api/command_loadtest.go35
-rw-r--r--api/command_loadtest_test.go124
-rw-r--r--api/command_logout_test.go17
-rw-r--r--api/command_me_test.go23
-rw-r--r--api/command_msg.go8
-rw-r--r--api/command_msg_test.go40
-rw-r--r--api/command_shrug_test.go23
-rw-r--r--api/command_test.go119
-rw-r--r--api/context.go157
-rw-r--r--api/context_test.go51
-rw-r--r--api/export.go2
-rw-r--r--api/file.go34
-rw-r--r--api/file_benchmark_test.go13
-rw-r--r--api/file_test.go124
-rw-r--r--api/import.go6
-rw-r--r--api/license.go10
-rw-r--r--api/license_test.go3
-rw-r--r--api/oauth.go256
-rw-r--r--api/oauth_test.go26
-rw-r--r--api/post.go110
-rw-r--r--api/post_benchmark_test.go30
-rw-r--r--api/post_test.go385
-rw-r--r--api/preference.go13
-rw-r--r--api/preference_test.go75
-rw-r--r--api/server.go4
-rw-r--r--api/sharding.go79
-rw-r--r--api/slackimport.go3
-rw-r--r--api/team.go461
-rw-r--r--api/team_test.go245
-rw-r--r--api/user.go1399
-rw-r--r--api/user_test.go571
-rw-r--r--api/web_conn.go78
-rw-r--r--api/web_hub.go128
-rw-r--r--api/web_socket.go7
-rw-r--r--api/web_socket_test.go44
-rw-r--r--api/web_team_hub.go123
-rw-r--r--api/webhook.go50
-rw-r--r--api/webhook_test.go733
-rw-r--r--config/config.json5
-rw-r--r--einterfaces/ldap.go4
-rw-r--r--einterfaces/mfa.go2
-rw-r--r--i18n/en.json50
-rw-r--r--i18n/es.json20
-rw-r--r--i18n/fr.json16
-rw-r--r--i18n/pt.json20
-rw-r--r--manualtesting/manual_testing.go10
-rw-r--r--mattermost.go430
-rw-r--r--model/client.go308
-rw-r--r--model/command.go2
-rw-r--r--model/command_test.go81
-rw-r--r--model/config.go18
-rw-r--r--model/gitlab.go8
-rw-r--r--model/gitlab/gitlab.go10
-rw-r--r--model/initial_load.go40
-rw-r--r--model/initial_load_test.go20
-rw-r--r--model/password_recovery.go37
-rw-r--r--model/session.go32
-rw-r--r--model/team.go25
-rw-r--r--model/team_member.go78
-rw-r--r--model/team_member_test.go43
-rw-r--r--model/user.go20
-rw-r--r--model/user_test.go9
-rw-r--r--model/utils.go11
-rw-r--r--model/version.go1
-rw-r--r--store/sql_audit_store.go11
-rw-r--r--store/sql_channel_store.go42
-rw-r--r--store/sql_channel_store_test.go69
-rw-r--r--store/sql_command_store_test.go7
-rw-r--r--store/sql_compliance_store_test.go4
-rw-r--r--store/sql_post_store.go14
-rw-r--r--store/sql_recovery_store.go124
-rw-r--r--store/sql_recovery_store_test.go54
-rw-r--r--store/sql_session_store.go45
-rw-r--r--store/sql_session_store_test.go24
-rw-r--r--store/sql_store.go128
-rw-r--r--store/sql_team_store.go189
-rw-r--r--store/sql_team_store_test.go172
-rw-r--r--store/sql_user_store.go217
-rw-r--r--store/sql_user_store_test.go272
-rw-r--r--store/store.go34
-rw-r--r--utils/config.go13
-rw-r--r--utils/license.go4
-rw-r--r--utils/textgeneration.go20
-rw-r--r--web/web.go6
-rw-r--r--web/web_test.go10
-rw-r--r--webapp/.eslintrc.json7
-rw-r--r--webapp/Makefile2
-rw-r--r--webapp/action_creators/global_actions.jsx113
-rw-r--r--webapp/action_creators/websocket_actions.jsx19
-rw-r--r--webapp/client/client.jsx1463
-rw-r--r--webapp/components/activity_log_modal.jsx2
-rw-r--r--webapp/components/admin_console/admin_navbar_dropdown.jsx20
-rw-r--r--webapp/components/admin_console/admin_sidebar_header.jsx3
-rw-r--r--webapp/components/admin_console/compliance_reports.jsx4
-rw-r--r--webapp/components/admin_console/compliance_settings.jsx2
-rw-r--r--webapp/components/admin_console/email_settings.jsx2
-rw-r--r--webapp/components/admin_console/gitlab_settings.jsx2
-rw-r--r--webapp/components/admin_console/image_settings.jsx2
-rw-r--r--webapp/components/admin_console/ldap_settings.jsx32
-rw-r--r--webapp/components/admin_console/legal_and_support_settings.jsx2
-rw-r--r--webapp/components/admin_console/license_settings.jsx7
-rw-r--r--webapp/components/admin_console/log_settings.jsx2
-rw-r--r--webapp/components/admin_console/privacy_settings.jsx2
-rw-r--r--webapp/components/admin_console/rate_settings.jsx2
-rw-r--r--webapp/components/admin_console/reset_password_modal.jsx13
-rw-r--r--webapp/components/admin_console/service_settings.jsx2
-rw-r--r--webapp/components/admin_console/sql_settings.jsx2
-rw-r--r--webapp/components/admin_console/team_settings.jsx105
-rw-r--r--webapp/components/admin_console/team_users.jsx2
-rw-r--r--webapp/components/admin_console/user_item.jsx133
-rw-r--r--webapp/components/authorize.jsx2
-rw-r--r--webapp/components/backstage/add_command.jsx10
-rw-r--r--webapp/components/backstage/add_incoming_webhook.jsx5
-rw-r--r--webapp/components/backstage/add_outgoing_webhook.jsx7
-rw-r--r--webapp/components/backstage/backstage_navbar.jsx2
-rw-r--r--webapp/components/backstage/backstage_sidebar.jsx3
-rw-r--r--webapp/components/backstage/installed_commands.jsx3
-rw-r--r--webapp/components/backstage/installed_incoming_webhook.jsx11
-rw-r--r--webapp/components/backstage/installed_incoming_webhooks.jsx3
-rw-r--r--webapp/components/backstage/installed_outgoing_webhooks.jsx3
-rw-r--r--webapp/components/backstage/integrations.jsx7
-rw-r--r--webapp/components/channel_header.jsx2
-rw-r--r--webapp/components/channel_info_modal.jsx30
-rw-r--r--webapp/components/channel_invite_button.jsx8
-rw-r--r--webapp/components/channel_members_modal.jsx7
-rw-r--r--webapp/components/channel_notifications_modal.jsx6
-rw-r--r--webapp/components/channel_select.jsx2
-rw-r--r--webapp/components/claim/claim.jsx32
-rw-r--r--webapp/components/claim/components/email_to_ldap.jsx47
-rw-r--r--webapp/components/claim/components/email_to_oauth.jsx23
-rw-r--r--webapp/components/claim/components/ldap_to_email.jsx33
-rw-r--r--webapp/components/claim/components/oauth_to_email.jsx18
-rw-r--r--webapp/components/create_comment.jsx3
-rw-r--r--webapp/components/create_post.jsx4
-rw-r--r--webapp/components/create_team/components/display_name.jsx (renamed from webapp/components/signup_team_complete/components/team_signup_display_name_page.jsx)33
-rw-r--r--webapp/components/create_team/components/team_url.jsx (renamed from webapp/components/signup_team_complete/components/team_signup_url_page.jsx)88
-rw-r--r--webapp/components/create_team/create_team.jsx72
-rw-r--r--webapp/components/delete_channel_modal.jsx2
-rw-r--r--webapp/components/delete_post_modal.jsx2
-rw-r--r--webapp/components/do_verify_email.jsx8
-rw-r--r--webapp/components/edit_channel_header_modal.jsx2
-rw-r--r--webapp/components/edit_channel_purpose_modal.jsx11
-rw-r--r--webapp/components/edit_post_modal.jsx49
-rw-r--r--webapp/components/error_bar.jsx4
-rw-r--r--webapp/components/file_attachment.jsx4
-rw-r--r--webapp/components/file_upload.jsx46
-rw-r--r--webapp/components/filtered_user_list.jsx3
-rw-r--r--webapp/components/header_footer_template.jsx (renamed from webapp/components/not_logged_in.jsx)8
-rw-r--r--webapp/components/invite_member_modal.jsx2
-rw-r--r--webapp/components/logged_in.jsx147
-rw-r--r--webapp/components/login/login.jsx198
-rw-r--r--webapp/components/member_list_team.jsx16
-rw-r--r--webapp/components/more_channels.jsx2
-rw-r--r--webapp/components/navbar.jsx2
-rw-r--r--webapp/components/navbar_dropdown.jsx59
-rw-r--r--webapp/components/needs_team.jsx148
-rw-r--r--webapp/components/new_channel_flow.jsx8
-rw-r--r--webapp/components/notify_counts.jsx11
-rw-r--r--webapp/components/password_reset_form.jsx14
-rw-r--r--webapp/components/password_reset_send_link.jsx8
-rw-r--r--webapp/components/popover_list_members.jsx3
-rw-r--r--webapp/components/post.jsx4
-rw-r--r--webapp/components/register_app_modal.jsx2
-rw-r--r--webapp/components/rename_channel_modal.jsx2
-rw-r--r--webapp/components/rhs_comment.jsx6
-rw-r--r--webapp/components/rhs_header_post.jsx12
-rw-r--r--webapp/components/root.jsx50
-rw-r--r--webapp/components/search_bar.jsx2
-rw-r--r--webapp/components/select_team/select_team.jsx257
-rw-r--r--webapp/components/should_verify_email.jsx20
-rw-r--r--webapp/components/sidebar_header.jsx3
-rw-r--r--webapp/components/sidebar_right_menu.jsx7
-rw-r--r--webapp/components/signup_team.jsx235
-rw-r--r--webapp/components/signup_team_complete/components/signup_team_complete.jsx1
-rw-r--r--webapp/components/signup_team_complete/components/team_signup_email_item.jsx89
-rw-r--r--webapp/components/signup_team_complete/components/team_signup_finished.jsx17
-rw-r--r--webapp/components/signup_team_complete/components/team_signup_password_page.jsx225
-rw-r--r--webapp/components/signup_team_complete/components/team_signup_send_invites_page.jsx215
-rw-r--r--webapp/components/signup_team_complete/components/team_signup_username_page.jsx169
-rw-r--r--webapp/components/signup_team_complete/components/team_signup_welcome_page.jsx243
-rw-r--r--webapp/components/signup_team_confirm.jsx49
-rw-r--r--webapp/components/signup_user_complete.jsx199
-rw-r--r--webapp/components/suggestion/at_mention_provider.jsx3
-rw-r--r--webapp/components/suggestion/search_user_provider.jsx3
-rw-r--r--webapp/components/team_export_tab.jsx2
-rw-r--r--webapp/components/team_general_tab.jsx160
-rw-r--r--webapp/components/team_members_dropdown.jsx46
-rw-r--r--webapp/components/team_settings.jsx1
-rw-r--r--webapp/components/team_signup_choose_auth.jsx132
-rw-r--r--webapp/components/team_signup_with_email.jsx116
-rw-r--r--webapp/components/team_signup_with_ldap.jsx231
-rw-r--r--webapp/components/team_signup_with_sso.jsx174
-rw-r--r--webapp/components/toggle_modal_button.jsx7
-rw-r--r--webapp/components/user_list.jsx10
-rw-r--r--webapp/components/user_list_row.jsx11
-rw-r--r--webapp/components/user_profile.jsx3
-rw-r--r--webapp/components/user_settings/import_theme_modal.jsx2
-rw-r--r--webapp/components/user_settings/manage_languages.jsx2
-rw-r--r--webapp/components/user_settings/user_settings_general.jsx8
-rw-r--r--webapp/components/user_settings/user_settings_notifications.jsx2
-rw-r--r--webapp/components/user_settings/user_settings_security.jsx37
-rw-r--r--webapp/components/user_settings/user_settings_theme.jsx2
-rw-r--r--webapp/components/view_image.jsx2
-rw-r--r--webapp/i18n/en.json104
-rw-r--r--webapp/i18n/es.json64
-rw-r--r--webapp/i18n/fr.json65
-rw-r--r--webapp/i18n/ja.json55
-rw-r--r--webapp/i18n/pt.json64
-rw-r--r--webapp/package.json17
-rw-r--r--webapp/root.html13
-rw-r--r--webapp/root.jsx463
-rw-r--r--webapp/sass/routes/_backstage.scss1
-rw-r--r--webapp/sass/routes/_signup.scss11
-rw-r--r--webapp/stores/browser_store.jsx12
-rw-r--r--webapp/stores/error_store.jsx11
-rw-r--r--webapp/stores/preference_store.jsx2
-rw-r--r--webapp/stores/team_store.jsx47
-rw-r--r--webapp/stores/user_store.jsx33
-rw-r--r--webapp/tests/client_channel.test.jsx334
-rw-r--r--webapp/tests/client_command.test.jsx123
-rw-r--r--webapp/tests/client_general.test.jsx333
-rw-r--r--webapp/tests/client_hooks.test.jsx139
-rw-r--r--webapp/tests/client_oauth.test.jsx60
-rw-r--r--webapp/tests/client_post.test.jsx207
-rw-r--r--webapp/tests/client_preferences.test.jsx72
-rw-r--r--webapp/tests/client_team.test.jsx235
-rw-r--r--webapp/tests/client_user.test.jsx550
-rw-r--r--webapp/tests/spinner_button.test.jsx24
-rw-r--r--webapp/tests/test_helper.jsx183
-rw-r--r--webapp/utils/async_client.jsx324
-rw-r--r--webapp/utils/channel_intro_messages.jsx3
-rw-r--r--webapp/utils/client.jsx1759
-rw-r--r--webapp/utils/constants.jsx5
-rw-r--r--webapp/utils/utils.jsx20
-rw-r--r--webapp/utils/web_client.jsx67
-rw-r--r--webapp/webpack.config-test.js131
-rw-r--r--webapp/webpack.config.js3
268 files changed, 13128 insertions, 9600 deletions
diff --git a/.gitignore b/.gitignore
index 422c6d5f5..882c5a783 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,6 +47,8 @@ _testmain.go
*mattermost.log
*npm-debug.log*
+.tmp
+
# Vim temporary files
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index aff57a2af..06798d010 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -10,6 +10,11 @@
"Rev": "a88790d49798560db24af70fb6a10a66e2549a72"
},
{
+ "ImportPath": "github.com/gorilla/handlers",
+ "Comment": "v1.1-4-gd0f2612",
+ "Rev": "d0f261246491e3a8613039e90764460448dc05f5"
+ },
+ {
"ImportPath": "github.com/alecthomas/log4go",
"Rev": "8e9057c3b25c409a34c0b9737cdc82cbcafeabce"
},
diff --git a/Godeps/_workspace/src/github.com/gorilla/handlers/.travis.yml b/Godeps/_workspace/src/github.com/gorilla/handlers/.travis.yml
new file mode 100644
index 000000000..ad1d76acc
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/handlers/.travis.yml
@@ -0,0 +1,17 @@
+language: go
+sudo: false
+
+matrix:
+ include:
+ - go: 1.4
+ - go: 1.5
+ - go: 1.6
+
+install:
+ - go get golang.org/x/tools/cmd/vet
+
+script:
+ - go get -t -v ./...
+ - diff -u <(echo -n) <(gofmt -d .)
+ - go tool vet .
+ - go test -v -race ./...
diff --git a/Godeps/_workspace/src/github.com/gorilla/handlers/LICENSE b/Godeps/_workspace/src/github.com/gorilla/handlers/LICENSE
new file mode 100644
index 000000000..66ea3c8ae
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/handlers/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2013 The Gorilla Handlers Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Godeps/_workspace/src/github.com/gorilla/handlers/README.md b/Godeps/_workspace/src/github.com/gorilla/handlers/README.md
new file mode 100644
index 000000000..a782c4152
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/handlers/README.md
@@ -0,0 +1,53 @@
+gorilla/handlers
+================
+[![GoDoc](https://godoc.org/github.com/gorilla/handlers?status.svg)](https://godoc.org/github.com/gorilla/handlers) [![Build Status](https://travis-ci.org/gorilla/handlers.svg?branch=master)](https://travis-ci.org/gorilla/handlers)
+
+Package handlers is a collection of handlers (aka "HTTP middleware") for use
+with Go's `net/http` package (or any framework supporting `http.Handler`), including:
+
+* [**LoggingHandler**](https://godoc.org/github.com/gorilla/handlers#LoggingHandler) for logging HTTP requests in the Apache [Common Log
+ Format](http://httpd.apache.org/docs/2.2/logs.html#common).
+* [**CombinedLoggingHandler**](https://godoc.org/github.com/gorilla/handlers#CombinedLoggingHandler) for logging HTTP requests in the Apache [Combined Log
+ Format](http://httpd.apache.org/docs/2.2/logs.html#combined) commonly used by
+ both Apache and nginx.
+* [**CompressHandler**](https://godoc.org/github.com/gorilla/handlers#CompressHandler) for gzipping responses.
+* [**ContentTypeHandler**](https://godoc.org/github.com/gorilla/handlers#ContentTypeHandler) for validating requests against a list of accepted
+ content types.
+* [**MethodHandler**](https://godoc.org/github.com/gorilla/handlers#MethodHandler) for matching HTTP methods against handlers in a
+ `map[string]http.Handler`
+* [**ProxyHeaders**](https://godoc.org/github.com/gorilla/handlers#ProxyHeaders) for populating `r.RemoteAddr` and `r.URL.Scheme` based on the
+ `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto` and RFC7239 `Forwarded`
+ headers when running a Go server behind a HTTP reverse proxy.
+* [**CanonicalHost**](https://godoc.org/github.com/gorilla/handlers#CanonicalHost) for re-directing to the preferred host when handling multiple
+ domains (i.e. multiple CNAME aliases).
+* [**RecoveryHandler**](https://godoc.org/github.com/gorilla/handlers#RecoveryHandler) for recovering from unexpected panics.
+
+Other handlers are documented [on the Gorilla
+website](http://www.gorillatoolkit.org/pkg/handlers).
+
+## Example
+
+A simple example using `handlers.LoggingHandler` and `handlers.CompressHandler`:
+
+```go
+import (
+ "net/http"
+ "github.com/gorilla/handlers"
+)
+
+func main() {
+ r := http.NewServeMux()
+
+ // Only log requests to our admin dashboard to stdout
+ r.Handle("/admin", handlers.LoggingHandler(os.Stdout, http.HandlerFunc(ShowAdminDashboard)))
+ r.HandleFunc("/", ShowIndex)
+
+ // Wrap our server with our gzip handler to gzip compress all responses.
+ http.ListenAndServe(":8000", handlers.CompressHandler(r))
+}
+```
+
+## License
+
+BSD licensed. See the included LICENSE file for details.
+
diff --git a/Godeps/_workspace/src/github.com/gorilla/handlers/canonical.go b/Godeps/_workspace/src/github.com/gorilla/handlers/canonical.go
new file mode 100644
index 000000000..8437fefc1
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/handlers/canonical.go
@@ -0,0 +1,74 @@
+package handlers
+
+import (
+ "net/http"
+ "net/url"
+ "strings"
+)
+
+type canonical struct {
+ h http.Handler
+ domain string
+ code int
+}
+
+// CanonicalHost is HTTP middleware that re-directs requests to the canonical
+// domain. It accepts a domain and a status code (e.g. 301 or 302) and
+// re-directs clients to this domain. The existing request path is maintained.
+//
+// Note: If the provided domain is considered invalid by url.Parse or otherwise
+// returns an empty scheme or host, clients are not re-directed.
+//
+// Example:
+//
+// r := mux.NewRouter()
+// canonical := handlers.CanonicalHost("http://www.gorillatoolkit.org", 302)
+// r.HandleFunc("/route", YourHandler)
+//
+// log.Fatal(http.ListenAndServe(":7000", canonical(r)))
+//
+func CanonicalHost(domain string, code int) func(h http.Handler) http.Handler {
+ fn := func(h http.Handler) http.Handler {
+ return canonical{h, domain, code}
+ }
+
+ return fn
+}
+
+func (c canonical) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ dest, err := url.Parse(c.domain)
+ if err != nil {
+ // Call the next handler if the provided domain fails to parse.
+ c.h.ServeHTTP(w, r)
+ return
+ }
+
+ if dest.Scheme == "" || dest.Host == "" {
+ // Call the next handler if the scheme or host are empty.
+ // Note that url.Parse won't fail on in this case.
+ c.h.ServeHTTP(w, r)
+ return
+ }
+
+ if !strings.EqualFold(cleanHost(r.Host), dest.Host) {
+ // Re-build the destination URL
+ dest := dest.Scheme + "://" + dest.Host + r.URL.Path
+ if r.URL.RawQuery != "" {
+ dest += "?" + r.URL.RawQuery
+ }
+ http.Redirect(w, r, dest, c.code)
+ return
+ }
+
+ c.h.ServeHTTP(w, r)
+}
+
+// cleanHost cleans invalid Host headers by stripping anything after '/' or ' '.
+// This is backported from Go 1.5 (in response to issue #11206) and attempts to
+// mitigate malformed Host headers that do not match the format in RFC7230.
+func cleanHost(in string) string {
+ if i := strings.IndexAny(in, " /"); i != -1 {
+ return in[:i]
+ }
+ return in
+}
diff --git a/Godeps/_workspace/src/github.com/gorilla/handlers/compress.go b/Godeps/_workspace/src/github.com/gorilla/handlers/compress.go
new file mode 100644
index 000000000..5e140c503
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/handlers/compress.go
@@ -0,0 +1,145 @@
+// Copyright 2013 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package handlers
+
+import (
+ "compress/flate"
+ "compress/gzip"
+ "io"
+ "net/http"
+ "strings"
+)
+
+type compressResponseWriter struct {
+ io.Writer
+ http.ResponseWriter
+ http.Hijacker
+ http.Flusher
+ http.CloseNotifier
+}
+
+func (w *compressResponseWriter) WriteHeader(c int) {
+ w.ResponseWriter.Header().Del("Content-Length")
+ w.ResponseWriter.WriteHeader(c)
+}
+
+func (w *compressResponseWriter) Header() http.Header {
+ return w.ResponseWriter.Header()
+}
+
+func (w *compressResponseWriter) Write(b []byte) (int, error) {
+ h := w.ResponseWriter.Header()
+ if h.Get("Content-Type") == "" {
+ h.Set("Content-Type", http.DetectContentType(b))
+ }
+ h.Del("Content-Length")
+
+ return w.Writer.Write(b)
+}
+
+type flusher interface {
+ Flush() error
+}
+
+func (w *compressResponseWriter) Flush() {
+ // Flush compressed data if compressor supports it.
+ if f, ok := w.Writer.(flusher); ok {
+ f.Flush()
+ }
+ // Flush HTTP response.
+ if w.Flusher != nil {
+ w.Flusher.Flush()
+ }
+}
+
+// CompressHandler gzip compresses HTTP responses for clients that support it
+// via the 'Accept-Encoding' header.
+func CompressHandler(h http.Handler) http.Handler {
+ return CompressHandlerLevel(h, gzip.DefaultCompression)
+}
+
+// CompressHandlerLevel gzip compresses HTTP responses with specified compression level
+// for clients that support it via the 'Accept-Encoding' header.
+//
+// The compression level should be gzip.DefaultCompression, gzip.NoCompression,
+// or any integer value between gzip.BestSpeed and gzip.BestCompression inclusive.
+// gzip.DefaultCompression is used in case of invalid compression level.
+func CompressHandlerLevel(h http.Handler, level int) http.Handler {
+ if level < gzip.DefaultCompression || level > gzip.BestCompression {
+ level = gzip.DefaultCompression
+ }
+
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ L:
+ for _, enc := range strings.Split(r.Header.Get("Accept-Encoding"), ",") {
+ switch strings.TrimSpace(enc) {
+ case "gzip":
+ w.Header().Set("Content-Encoding", "gzip")
+ w.Header().Add("Vary", "Accept-Encoding")
+
+ gw, _ := gzip.NewWriterLevel(w, level)
+ defer gw.Close()
+
+ h, hok := w.(http.Hijacker)
+ if !hok { /* w is not Hijacker... oh well... */
+ h = nil
+ }
+
+ f, fok := w.(http.Flusher)
+ if !fok {
+ f = nil
+ }
+
+ cn, cnok := w.(http.CloseNotifier)
+ if !cnok {
+ cn = nil
+ }
+
+ w = &compressResponseWriter{
+ Writer: gw,
+ ResponseWriter: w,
+ Hijacker: h,
+ Flusher: f,
+ CloseNotifier: cn,
+ }
+
+ break L
+ case "deflate":
+ w.Header().Set("Content-Encoding", "deflate")
+ w.Header().Add("Vary", "Accept-Encoding")
+
+ fw, _ := flate.NewWriter(w, level)
+ defer fw.Close()
+
+ h, hok := w.(http.Hijacker)
+ if !hok { /* w is not Hijacker... oh well... */
+ h = nil
+ }
+
+ f, fok := w.(http.Flusher)
+ if !fok {
+ f = nil
+ }
+
+ cn, cnok := w.(http.CloseNotifier)
+ if !cnok {
+ cn = nil
+ }
+
+ w = &compressResponseWriter{
+ Writer: fw,
+ ResponseWriter: w,
+ Hijacker: h,
+ Flusher: f,
+ CloseNotifier: cn,
+ }
+
+ break L
+ }
+ }
+
+ h.ServeHTTP(w, r)
+ })
+}
diff --git a/Godeps/_workspace/src/github.com/gorilla/handlers/cors.go b/Godeps/_workspace/src/github.com/gorilla/handlers/cors.go
new file mode 100644
index 000000000..1f92d1ad4
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/handlers/cors.go
@@ -0,0 +1,317 @@
+package handlers
+
+import (
+ "net/http"
+ "strconv"
+ "strings"
+)
+
+// CORSOption represents a functional option for configuring the CORS middleware.
+type CORSOption func(*cors) error
+
+type cors struct {
+ h http.Handler
+ allowedHeaders []string
+ allowedMethods []string
+ allowedOrigins []string
+ allowedOriginValidator OriginValidator
+ exposedHeaders []string
+ maxAge int
+ ignoreOptions bool
+ allowCredentials bool
+}
+
+// OriginValidator takes an origin string and returns whether or not that origin is allowed.
+type OriginValidator func(string) bool
+
+var (
+ defaultCorsMethods = []string{"GET", "HEAD", "POST"}
+ defaultCorsHeaders = []string{"Accept", "Accept-Language", "Content-Language", "Origin"}
+ // (WebKit/Safari v9 sends the Origin header by default in AJAX requests)
+)
+
+const (
+ corsOptionMethod string = "OPTIONS"
+ corsAllowOriginHeader string = "Access-Control-Allow-Origin"
+ corsExposeHeadersHeader string = "Access-Control-Expose-Headers"
+ corsMaxAgeHeader string = "Access-Control-Max-Age"
+ corsAllowMethodsHeader string = "Access-Control-Allow-Methods"
+ corsAllowHeadersHeader string = "Access-Control-Allow-Headers"
+ corsAllowCredentialsHeader string = "Access-Control-Allow-Credentials"
+ corsRequestMethodHeader string = "Access-Control-Request-Method"
+ corsRequestHeadersHeader string = "Access-Control-Request-Headers"
+ corsOriginHeader string = "Origin"
+ corsVaryHeader string = "Vary"
+ corsOriginMatchAll string = "*"
+)
+
+func (ch *cors) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ origin := r.Header.Get(corsOriginHeader)
+ if !ch.isOriginAllowed(origin) {
+ ch.h.ServeHTTP(w, r)
+ return
+ }
+
+ if r.Method == corsOptionMethod {
+ if ch.ignoreOptions {
+ ch.h.ServeHTTP(w, r)
+ return
+ }
+
+ if _, ok := r.Header[corsRequestMethodHeader]; !ok {
+ w.WriteHeader(http.StatusBadRequest)
+ return
+ }
+
+ method := r.Header.Get(corsRequestMethodHeader)
+ if !ch.isMatch(method, ch.allowedMethods) {
+ w.WriteHeader(http.StatusMethodNotAllowed)
+ return
+ }
+
+ requestHeaders := strings.Split(r.Header.Get(corsRequestHeadersHeader), ",")
+ allowedHeaders := []string{}
+ for _, v := range requestHeaders {
+ canonicalHeader := http.CanonicalHeaderKey(strings.TrimSpace(v))
+ if canonicalHeader == "" || ch.isMatch(canonicalHeader, defaultCorsHeaders) {
+ continue
+ }
+
+ if !ch.isMatch(canonicalHeader, ch.allowedHeaders) {
+ w.WriteHeader(http.StatusForbidden)
+ return
+ }
+
+ allowedHeaders = append(allowedHeaders, canonicalHeader)
+ }
+
+ if len(allowedHeaders) > 0 {
+ w.Header().Set(corsAllowHeadersHeader, strings.Join(allowedHeaders, ","))
+ }
+
+ if ch.maxAge > 0 {
+ w.Header().Set(corsMaxAgeHeader, strconv.Itoa(ch.maxAge))
+ }
+
+ if !ch.isMatch(method, defaultCorsMethods) {
+ w.Header().Set(corsAllowMethodsHeader, method)
+ }
+ } else {
+ if len(ch.exposedHeaders) > 0 {
+ w.Header().Set(corsExposeHeadersHeader, strings.Join(ch.exposedHeaders, ","))
+ }
+ }
+
+ if ch.allowCredentials {
+ w.Header().Set(corsAllowCredentialsHeader, "true")
+ }
+
+ if len(ch.allowedOrigins) > 1 {
+ w.Header().Set(corsVaryHeader, corsOriginHeader)
+ }
+
+ w.Header().Set(corsAllowOriginHeader, origin)
+
+ if r.Method == corsOptionMethod {
+ return
+ }
+ ch.h.ServeHTTP(w, r)
+}
+
+// CORS provides Cross-Origin Resource Sharing middleware.
+// Example:
+//
+// import (
+// "net/http"
+//
+// "github.com/gorilla/handlers"
+// "github.com/gorilla/mux"
+// )
+//
+// func main() {
+// r := mux.NewRouter()
+// r.HandleFunc("/users", UserEndpoint)
+// r.HandleFunc("/projects", ProjectEndpoint)
+//
+// // Apply the CORS middleware to our top-level router, with the defaults.
+// http.ListenAndServe(":8000", handlers.CORS()(r))
+// }
+//
+func CORS(opts ...CORSOption) func(http.Handler) http.Handler {
+ return func(h http.Handler) http.Handler {
+ ch := parseCORSOptions(opts...)
+ ch.h = h
+ return ch
+ }
+}
+
+func parseCORSOptions(opts ...CORSOption) *cors {
+ ch := &cors{
+ allowedMethods: defaultCorsMethods,
+ allowedHeaders: defaultCorsHeaders,
+ allowedOrigins: []string{corsOriginMatchAll},
+ }
+
+ for _, option := range opts {
+ option(ch)
+ }
+
+ return ch
+}
+
+//
+// Functional options for configuring CORS.
+//
+
+// AllowedHeaders adds the provided headers to the list of allowed headers in a
+// CORS request.
+// This is an append operation so the headers Accept, Accept-Language,
+// and Content-Language are always allowed.
+// Content-Type must be explicitly declared if accepting Content-Types other than
+// application/x-www-form-urlencoded, multipart/form-data, or text/plain.
+func AllowedHeaders(headers []string) CORSOption {
+ return func(ch *cors) error {
+ for _, v := range headers {
+ normalizedHeader := http.CanonicalHeaderKey(strings.TrimSpace(v))
+ if normalizedHeader == "" {
+ continue
+ }
+
+ if !ch.isMatch(normalizedHeader, ch.allowedHeaders) {
+ ch.allowedHeaders = append(ch.allowedHeaders, normalizedHeader)
+ }
+ }
+
+ return nil
+ }
+}
+
+// AllowedMethods can be used to explicitly allow methods in the
+// Access-Control-Allow-Methods header.
+// This is a replacement operation so you must also
+// pass GET, HEAD, and POST if you wish to support those methods.
+func AllowedMethods(methods []string) CORSOption {
+ return func(ch *cors) error {
+ ch.allowedMethods = []string{}
+ for _, v := range methods {
+ normalizedMethod := strings.ToUpper(strings.TrimSpace(v))
+ if normalizedMethod == "" {
+ continue
+ }
+
+ if !ch.isMatch(normalizedMethod, ch.allowedMethods) {
+ ch.allowedMethods = append(ch.allowedMethods, normalizedMethod)
+ }
+ }
+
+ return nil
+ }
+}
+
+// AllowedOrigins sets the allowed origins for CORS requests, as used in the
+// 'Allow-Access-Control-Origin' HTTP header.
+// Note: Passing in a []string{"*"} will allow any domain.
+func AllowedOrigins(origins []string) CORSOption {
+ return func(ch *cors) error {
+ for _, v := range origins {
+ if v == corsOriginMatchAll {
+ ch.allowedOrigins = []string{corsOriginMatchAll}
+ return nil
+ }
+ }
+
+ ch.allowedOrigins = origins
+ return nil
+ }
+}
+
+// AllowedOriginValidator sets a function for evaluating allowed origins in CORS requests, represented by the
+// 'Allow-Access-Control-Origin' HTTP header.
+func AllowedOriginValidator(fn OriginValidator) CORSOption {
+ return func(ch *cors) error {
+ ch.allowedOriginValidator = fn
+ return nil
+ }
+}
+
+// ExposeHeaders can be used to specify headers that are available
+// and will not be stripped out by the user-agent.
+func ExposedHeaders(headers []string) CORSOption {
+ return func(ch *cors) error {
+ ch.exposedHeaders = []string{}
+ for _, v := range headers {
+ normalizedHeader := http.CanonicalHeaderKey(strings.TrimSpace(v))
+ if normalizedHeader == "" {
+ continue
+ }
+
+ if !ch.isMatch(normalizedHeader, ch.exposedHeaders) {
+ ch.exposedHeaders = append(ch.exposedHeaders, normalizedHeader)
+ }
+ }
+
+ return nil
+ }
+}
+
+// MaxAge determines the maximum age (in seconds) between preflight requests. A
+// maximum of 10 minutes is allowed. An age above this value will default to 10
+// minutes.
+func MaxAge(age int) CORSOption {
+ return func(ch *cors) error {
+ // Maximum of 10 minutes.
+ if age > 600 {
+ age = 600
+ }
+
+ ch.maxAge = age
+ return nil
+ }
+}
+
+// IgnoreOptions causes the CORS middleware to ignore OPTIONS requests, instead
+// passing them through to the next handler. This is useful when your application
+// or framework has a pre-existing mechanism for responding to OPTIONS requests.
+func IgnoreOptions() CORSOption {
+ return func(ch *cors) error {
+ ch.ignoreOptions = true
+ return nil
+ }
+}
+
+// AllowCredentials can be used to specify that the user agent may pass
+// authentication details along with the request.
+func AllowCredentials() CORSOption {
+ return func(ch *cors) error {
+ ch.allowCredentials = true
+ return nil
+ }
+}
+
+func (ch *cors) isOriginAllowed(origin string) bool {
+ if origin == "" {
+ return false
+ }
+
+ if ch.allowedOriginValidator != nil {
+ return ch.allowedOriginValidator(origin)
+ }
+
+ for _, allowedOrigin := range ch.allowedOrigins {
+ if allowedOrigin == origin || allowedOrigin == corsOriginMatchAll {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (ch *cors) isMatch(needle string, haystack []string) bool {
+ for _, v := range haystack {
+ if v == needle {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/Godeps/_workspace/src/github.com/gorilla/handlers/doc.go b/Godeps/_workspace/src/github.com/gorilla/handlers/doc.go
new file mode 100644
index 000000000..944e5a8ae
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/handlers/doc.go
@@ -0,0 +1,9 @@
+/*
+Package handlers is a collection of handlers (aka "HTTP middleware") for use
+with Go's net/http package (or any framework supporting http.Handler).
+
+The package includes handlers for logging in standardised formats, compressing
+HTTP responses, validating content types and other useful tools for manipulating
+requests and responses.
+*/
+package handlers
diff --git a/Godeps/_workspace/src/github.com/gorilla/handlers/handlers.go b/Godeps/_workspace/src/github.com/gorilla/handlers/handlers.go
new file mode 100644
index 000000000..9544d2f0a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/handlers/handlers.go
@@ -0,0 +1,403 @@
+// Copyright 2013 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package handlers
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "net/url"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+ "unicode/utf8"
+)
+
+// MethodHandler is an http.Handler that dispatches to a handler whose key in the
+// MethodHandler's map matches the name of the HTTP request's method, eg: GET
+//
+// If the request's method is OPTIONS and OPTIONS is not a key in the map then
+// the handler responds with a status of 200 and sets the Allow header to a
+// comma-separated list of available methods.
+//
+// If the request's method doesn't match any of its keys the handler responds
+// with a status of HTTP 405 "Method Not Allowed" and sets the Allow header to a
+// comma-separated list of available methods.
+type MethodHandler map[string]http.Handler
+
+func (h MethodHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ if handler, ok := h[req.Method]; ok {
+ handler.ServeHTTP(w, req)
+ } else {
+ allow := []string{}
+ for k := range h {
+ allow = append(allow, k)
+ }
+ sort.Strings(allow)
+ w.Header().Set("Allow", strings.Join(allow, ", "))
+ if req.Method == "OPTIONS" {
+ w.WriteHeader(http.StatusOK)
+ } else {
+ http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+ }
+ }
+}
+
+// loggingHandler is the http.Handler implementation for LoggingHandlerTo and its
+// friends
+type loggingHandler struct {
+ writer io.Writer
+ handler http.Handler
+}
+
+// combinedLoggingHandler is the http.Handler implementation for LoggingHandlerTo
+// and its friends
+type combinedLoggingHandler struct {
+ writer io.Writer
+ handler http.Handler
+}
+
+func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ t := time.Now()
+ logger := makeLogger(w)
+ url := *req.URL
+ h.handler.ServeHTTP(logger, req)
+ writeLog(h.writer, req, url, t, logger.Status(), logger.Size())
+}
+
+func (h combinedLoggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ t := time.Now()
+ logger := makeLogger(w)
+ url := *req.URL
+ h.handler.ServeHTTP(logger, req)
+ writeCombinedLog(h.writer, req, url, t, logger.Status(), logger.Size())
+}
+
+func makeLogger(w http.ResponseWriter) loggingResponseWriter {
+ var logger loggingResponseWriter = &responseLogger{w: w}
+ if _, ok := w.(http.Hijacker); ok {
+ logger = &hijackLogger{responseLogger{w: w}}
+ }
+ h, ok1 := logger.(http.Hijacker)
+ c, ok2 := w.(http.CloseNotifier)
+ if ok1 && ok2 {
+ return hijackCloseNotifier{logger, h, c}
+ }
+ if ok2 {
+ return &closeNotifyWriter{logger, c}
+ }
+ return logger
+}
+
+type loggingResponseWriter interface {
+ http.ResponseWriter
+ http.Flusher
+ Status() int
+ Size() int
+}
+
+// responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP
+// status code and body size
+type responseLogger struct {
+ w http.ResponseWriter
+ status int
+ size int
+}
+
+func (l *responseLogger) Header() http.Header {
+ return l.w.Header()
+}
+
+func (l *responseLogger) Write(b []byte) (int, error) {
+ if l.status == 0 {
+ // The status will be StatusOK if WriteHeader has not been called yet
+ l.status = http.StatusOK
+ }
+ size, err := l.w.Write(b)
+ l.size += size
+ return size, err
+}
+
+func (l *responseLogger) WriteHeader(s int) {
+ l.w.WriteHeader(s)
+ l.status = s
+}
+
+func (l *responseLogger) Status() int {
+ return l.status
+}
+
+func (l *responseLogger) Size() int {
+ return l.size
+}
+
+func (l *responseLogger) Flush() {
+ f, ok := l.w.(http.Flusher)
+ if ok {
+ f.Flush()
+ }
+}
+
+type hijackLogger struct {
+ responseLogger
+}
+
+func (l *hijackLogger) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+ h := l.responseLogger.w.(http.Hijacker)
+ conn, rw, err := h.Hijack()
+ if err == nil && l.responseLogger.status == 0 {
+ // The status will be StatusSwitchingProtocols if there was no error and
+ // WriteHeader has not been called yet
+ l.responseLogger.status = http.StatusSwitchingProtocols
+ }
+ return conn, rw, err
+}
+
+type closeNotifyWriter struct {
+ loggingResponseWriter
+ http.CloseNotifier
+}
+
+type hijackCloseNotifier struct {
+ loggingResponseWriter
+ http.Hijacker
+ http.CloseNotifier
+}
+
+const lowerhex = "0123456789abcdef"
+
+func appendQuoted(buf []byte, s string) []byte {
+ var runeTmp [utf8.UTFMax]byte
+ for width := 0; len(s) > 0; s = s[width:] {
+ r := rune(s[0])
+ width = 1
+ if r >= utf8.RuneSelf {
+ r, width = utf8.DecodeRuneInString(s)
+ }
+ if width == 1 && r == utf8.RuneError {
+ buf = append(buf, `\x`...)
+ buf = append(buf, lowerhex[s[0]>>4])
+ buf = append(buf, lowerhex[s[0]&0xF])
+ continue
+ }
+ if r == rune('"') || r == '\\' { // always backslashed
+ buf = append(buf, '\\')
+ buf = append(buf, byte(r))
+ continue
+ }
+ if strconv.IsPrint(r) {
+ n := utf8.EncodeRune(runeTmp[:], r)
+ buf = append(buf, runeTmp[:n]...)
+ continue
+ }
+ switch r {
+ case '\a':
+ buf = append(buf, `\a`...)
+ case '\b':
+ buf = append(buf, `\b`...)
+ case '\f':
+ buf = append(buf, `\f`...)
+ case '\n':
+ buf = append(buf, `\n`...)
+ case '\r':
+ buf = append(buf, `\r`...)
+ case '\t':
+ buf = append(buf, `\t`...)
+ case '\v':
+ buf = append(buf, `\v`...)
+ default:
+ switch {
+ case r < ' ':
+ buf = append(buf, `\x`...)
+ buf = append(buf, lowerhex[s[0]>>4])
+ buf = append(buf, lowerhex[s[0]&0xF])
+ case r > utf8.MaxRune:
+ r = 0xFFFD
+ fallthrough
+ case r < 0x10000:
+ buf = append(buf, `\u`...)
+ for s := 12; s >= 0; s -= 4 {
+ buf = append(buf, lowerhex[r>>uint(s)&0xF])
+ }
+ default:
+ buf = append(buf, `\U`...)
+ for s := 28; s >= 0; s -= 4 {
+ buf = append(buf, lowerhex[r>>uint(s)&0xF])
+ }
+ }
+ }
+ }
+ return buf
+
+}
+
+// buildCommonLogLine builds a log entry for req in Apache Common Log Format.
+// ts is the timestamp with which the entry should be logged.
+// status and size are used to provide the response HTTP status and size.
+func buildCommonLogLine(req *http.Request, url url.URL, ts time.Time, status int, size int) []byte {
+ username := "-"
+ if url.User != nil {
+ if name := url.User.Username(); name != "" {
+ username = name
+ }
+ }
+
+ host, _, err := net.SplitHostPort(req.RemoteAddr)
+
+ if err != nil {
+ host = req.RemoteAddr
+ }
+
+ uri := req.RequestURI
+
+ // Requests using the CONNECT method over HTTP/2.0 must use
+ // the authority field (aka r.Host) to identify the target.
+ // Refer: https://httpwg.github.io/specs/rfc7540.html#CONNECT
+ if req.ProtoMajor == 2 && req.Method == "CONNECT" {
+ uri = req.Host
+ }
+ if uri == "" {
+ uri = url.RequestURI()
+ }
+
+ buf := make([]byte, 0, 3*(len(host)+len(username)+len(req.Method)+len(uri)+len(req.Proto)+50)/2)
+ buf = append(buf, host...)
+ buf = append(buf, " - "...)
+ buf = append(buf, username...)
+ buf = append(buf, " ["...)
+ buf = append(buf, ts.Format("02/Jan/2006:15:04:05 -0700")...)
+ buf = append(buf, `] "`...)
+ buf = append(buf, req.Method...)
+ buf = append(buf, " "...)
+ buf = appendQuoted(buf, uri)
+ buf = append(buf, " "...)
+ buf = append(buf, req.Proto...)
+ buf = append(buf, `" `...)
+ buf = append(buf, strconv.Itoa(status)...)
+ buf = append(buf, " "...)
+ buf = append(buf, strconv.Itoa(size)...)
+ return buf
+}
+
+// writeLog writes a log entry for req to w in Apache Common Log Format.
+// ts is the timestamp with which the entry should be logged.
+// status and size are used to provide the response HTTP status and size.
+func writeLog(w io.Writer, req *http.Request, url url.URL, ts time.Time, status, size int) {
+ buf := buildCommonLogLine(req, url, ts, status, size)
+ buf = append(buf, '\n')
+ w.Write(buf)
+}
+
+// writeCombinedLog writes a log entry for req to w in Apache Combined Log Format.
+// ts is the timestamp with which the entry should be logged.
+// status and size are used to provide the response HTTP status and size.
+func writeCombinedLog(w io.Writer, req *http.Request, url url.URL, ts time.Time, status, size int) {
+ buf := buildCommonLogLine(req, url, ts, status, size)
+ buf = append(buf, ` "`...)
+ buf = appendQuoted(buf, req.Referer())
+ buf = append(buf, `" "`...)
+ buf = appendQuoted(buf, req.UserAgent())
+ buf = append(buf, '"', '\n')
+ w.Write(buf)
+}
+
+// CombinedLoggingHandler return a http.Handler that wraps h and logs requests to out in
+// Apache Combined Log Format.
+//
+// See http://httpd.apache.org/docs/2.2/logs.html#combined for a description of this format.
+//
+// LoggingHandler always sets the ident field of the log to -
+func CombinedLoggingHandler(out io.Writer, h http.Handler) http.Handler {
+ return combinedLoggingHandler{out, h}
+}
+
+// LoggingHandler return a http.Handler that wraps h and logs requests to out in
+// Apache Common Log Format (CLF).
+//
+// See http://httpd.apache.org/docs/2.2/logs.html#common for a description of this format.
+//
+// LoggingHandler always sets the ident field of the log to -
+//
+// Example:
+//
+// r := mux.NewRouter()
+// r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+// w.Write([]byte("This is a catch-all route"))
+// })
+// loggedRouter := handlers.LoggingHandler(os.Stdout, r)
+// http.ListenAndServe(":1123", loggedRouter)
+//
+func LoggingHandler(out io.Writer, h http.Handler) http.Handler {
+ return loggingHandler{out, h}
+}
+
+// isContentType validates the Content-Type header matches the supplied
+// contentType. That is, its type and subtype match.
+func isContentType(h http.Header, contentType string) bool {
+ ct := h.Get("Content-Type")
+ if i := strings.IndexRune(ct, ';'); i != -1 {
+ ct = ct[0:i]
+ }
+ return ct == contentType
+}
+
+// ContentTypeHandler wraps and returns a http.Handler, validating the request
+// content type is compatible with the contentTypes list. It writes a HTTP 415
+// error if that fails.
+//
+// Only PUT, POST, and PATCH requests are considered.
+func ContentTypeHandler(h http.Handler, contentTypes ...string) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if !(r.Method == "PUT" || r.Method == "POST" || r.Method == "PATCH") {
+ h.ServeHTTP(w, r)
+ return
+ }
+
+ for _, ct := range contentTypes {
+ if isContentType(r.Header, ct) {
+ h.ServeHTTP(w, r)
+ return
+ }
+ }
+ http.Error(w, fmt.Sprintf("Unsupported content type %q; expected one of %q", r.Header.Get("Content-Type"), contentTypes), http.StatusUnsupportedMediaType)
+ })
+}
+
+const (
+ // HTTPMethodOverrideHeader is a commonly used
+ // http header to override a request method.
+ HTTPMethodOverrideHeader = "X-HTTP-Method-Override"
+ // HTTPMethodOverrideFormKey is a commonly used
+ // HTML form key to override a request method.
+ HTTPMethodOverrideFormKey = "_method"
+)
+
+// HTTPMethodOverrideHandler wraps and returns a http.Handler which checks for
+// the X-HTTP-Method-Override header or the _method form key, and overrides (if
+// valid) request.Method with its value.
+//
+// This is especially useful for HTTP clients that don't support many http verbs.
+// It isn't secure to override e.g a GET to a POST, so only POST requests are
+// considered. Likewise, the override method can only be a "write" method: PUT,
+// PATCH or DELETE.
+//
+// Form method takes precedence over header method.
+func HTTPMethodOverrideHandler(h http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "POST" {
+ om := r.FormValue(HTTPMethodOverrideFormKey)
+ if om == "" {
+ om = r.Header.Get(HTTPMethodOverrideHeader)
+ }
+ if om == "PUT" || om == "PATCH" || om == "DELETE" {
+ r.Method = om
+ }
+ }
+ h.ServeHTTP(w, r)
+ })
+}
diff --git a/Godeps/_workspace/src/github.com/gorilla/handlers/proxy_headers.go b/Godeps/_workspace/src/github.com/gorilla/handlers/proxy_headers.go
new file mode 100644
index 000000000..268de9c6a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/handlers/proxy_headers.go
@@ -0,0 +1,113 @@
+package handlers
+
+import (
+ "net/http"
+ "regexp"
+ "strings"
+)
+
+var (
+ // De-facto standard header keys.
+ xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For")
+ xRealIP = http.CanonicalHeaderKey("X-Real-IP")
+ xForwardedProto = http.CanonicalHeaderKey("X-Forwarded-Scheme")
+)
+
+var (
+ // RFC7239 defines a new "Forwarded: " header designed to replace the
+ // existing use of X-Forwarded-* headers.
+ // e.g. Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43
+ forwarded = http.CanonicalHeaderKey("Forwarded")
+ // Allows for a sub-match of the first value after 'for=' to the next
+ // comma, semi-colon or space. The match is case-insensitive.
+ forRegex = regexp.MustCompile(`(?i)(?:for=)([^(;|,| )]+)`)
+ // Allows for a sub-match for the first instance of scheme (http|https)
+ // prefixed by 'proto='. The match is case-insensitive.
+ protoRegex = regexp.MustCompile(`(?i)(?:proto=)(https|http)`)
+)
+
+// ProxyHeaders inspects common reverse proxy headers and sets the corresponding
+// fields in the HTTP request struct. These are X-Forwarded-For and X-Real-IP
+// for the remote (client) IP address, X-Forwarded-Proto for the scheme
+// (http|https) and the RFC7239 Forwarded header, which may include both client
+// IPs and schemes.
+//
+// NOTE: This middleware should only be used when behind a reverse
+// proxy like nginx, HAProxy or Apache. Reverse proxies that don't (or are
+// configured not to) strip these headers from client requests, or where these
+// headers are accepted "as is" from a remote client (e.g. when Go is not behind
+// a proxy), can manifest as a vulnerability if your application uses these
+// headers for validating the 'trustworthiness' of a request.
+func ProxyHeaders(h http.Handler) http.Handler {
+ fn := func(w http.ResponseWriter, r *http.Request) {
+ // Set the remote IP with the value passed from the proxy.
+ if fwd := getIP(r); fwd != "" {
+ r.RemoteAddr = fwd
+ }
+
+ // Set the scheme (proto) with the value passed from the proxy.
+ if scheme := getScheme(r); scheme != "" {
+ r.URL.Scheme = scheme
+ }
+
+ // Call the next handler in the chain.
+ h.ServeHTTP(w, r)
+ }
+
+ return http.HandlerFunc(fn)
+}
+
+// getIP retrieves the IP from the X-Forwarded-For, X-Real-IP and RFC7239
+// Forwarded headers (in that order).
+func getIP(r *http.Request) string {
+ var addr string
+
+ if fwd := r.Header.Get(xForwardedFor); fwd != "" {
+ // Only grab the first (client) address. Note that '192.168.0.1,
+ // 10.1.1.1' is a valid key for X-Forwarded-For where addresses after
+ // the first may represent forwarding proxies earlier in the chain.
+ s := strings.Index(fwd, ", ")
+ if s == -1 {
+ s = len(fwd)
+ }
+ addr = fwd[:s]
+ } else if fwd := r.Header.Get(xRealIP); fwd != "" {
+ // X-Real-IP should only contain one IP address (the client making the
+ // request).
+ addr = fwd
+ } else if fwd := r.Header.Get(forwarded); fwd != "" {
+ // match should contain at least two elements if the protocol was
+ // specified in the Forwarded header. The first element will always be
+ // the 'for=' capture, which we ignore. In the case of multiple IP
+ // addresses (for=8.8.8.8, 8.8.4.4,172.16.1.20 is valid) we only
+ // extract the first, which should be the client IP.
+ if match := forRegex.FindStringSubmatch(fwd); len(match) > 1 {
+ // IPv6 addresses in Forwarded headers are quoted-strings. We strip
+ // these quotes.
+ addr = strings.Trim(match[1], `"`)
+ }
+ }
+
+ return addr
+}
+
+// getScheme retrieves the scheme from the X-Forwarded-Proto and RFC7239
+// Forwarded headers (in that order).
+func getScheme(r *http.Request) string {
+ var scheme string
+
+ // Retrieve the scheme from X-Forwarded-Proto.
+ if proto := r.Header.Get(xForwardedProto); proto != "" {
+ scheme = strings.ToLower(proto)
+ } else if proto := r.Header.Get(forwarded); proto != "" {
+ // match should contain at least two elements if the protocol was
+ // specified in the Forwarded header. The first element will always be
+ // the 'proto=' capture, which we ignore. In the case of multiple proto
+ // parameters (invalid) we only extract the first.
+ if match := protoRegex.FindStringSubmatch(proto); len(match) > 1 {
+ scheme = strings.ToLower(match[1])
+ }
+ }
+
+ return scheme
+}
diff --git a/Godeps/_workspace/src/github.com/gorilla/handlers/recovery.go b/Godeps/_workspace/src/github.com/gorilla/handlers/recovery.go
new file mode 100644
index 000000000..65b7de58a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/handlers/recovery.go
@@ -0,0 +1,86 @@
+package handlers
+
+import (
+ "log"
+ "net/http"
+ "runtime/debug"
+)
+
+type recoveryHandler struct {
+ handler http.Handler
+ logger *log.Logger
+ printStack bool
+}
+
+// RecoveryOption provides a functional approach to define
+// configuration for a handler; such as setting the logging
+// whether or not to print strack traces on panic.
+type RecoveryOption func(http.Handler)
+
+func parseRecoveryOptions(h http.Handler, opts ...RecoveryOption) http.Handler {
+ for _, option := range opts {
+ option(h)
+ }
+
+ return h
+}
+
+// RecoveryHandler is HTTP middleware that recovers from a panic,
+// logs the panic, writes http.StatusInternalServerError, and
+// continues to the next handler.
+//
+// Example:
+//
+// r := mux.NewRouter()
+// r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+// panic("Unexpected error!")
+// })
+//
+// http.ListenAndServe(":1123", handlers.RecoveryHandler()(r))
+func RecoveryHandler(opts ...RecoveryOption) func(h http.Handler) http.Handler {
+ return func(h http.Handler) http.Handler {
+ r := &recoveryHandler{handler: h}
+ return parseRecoveryOptions(r, opts...)
+ }
+}
+
+// RecoveryLogger is a functional option to override
+// the default logger
+func RecoveryLogger(logger *log.Logger) RecoveryOption {
+ return func(h http.Handler) {
+ r := h.(*recoveryHandler)
+ r.logger = logger
+ }
+}
+
+// PrintRecoveryStack is a functional option to enable
+// or disable printing stack traces on panic.
+func PrintRecoveryStack(print bool) RecoveryOption {
+ return func(h http.Handler) {
+ r := h.(*recoveryHandler)
+ r.printStack = print
+ }
+}
+
+func (h recoveryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ defer func() {
+ if err := recover(); err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ h.log(err)
+ }
+ }()
+
+ h.handler.ServeHTTP(w, req)
+}
+
+func (h recoveryHandler) log(message interface{}) {
+ if h.logger != nil {
+ h.logger.Println(message)
+ } else {
+ log.Println(message)
+ }
+
+ if h.printStack {
+ debug.PrintStack()
+ }
+}
diff --git a/Makefile b/Makefile
index a20086652..0d22d392d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: build package run stop run-client run-server stop-client stop-server restart-server restart-client start-docker clean-dist clean nuke check-style check-unit-tests test dist setup-mac prepare-enteprise build-linux build-osx build-windows
+.PHONY: build package run stop run-client run-server stop-client stop-server restart-server restart-client start-docker clean-dist clean nuke check-style check-unit-tests test dist setup-mac prepare-enteprise run-client-tests setup-run-client-tests cleanup-run-client-tests test-client build-linux build-osx build-windows
# Build Flags
BUILD_NUMBER ?= $(BUILD_NUMBER:)
@@ -63,10 +63,30 @@ start-docker:
echo starting mattermost-postgres; \
docker run --name mattermost-postgres -p 5432:5432 -e POSTGRES_USER=mmuser -e POSTGRES_PASSWORD=mostest \
-d postgres:9.4 > /dev/null; \
- sleep 10; \
elif [ $(shell docker ps | grep -ci mattermost-postgres) -eq 0 ]; then \
echo restarting mattermost-postgres; \
docker start mattermost-postgres > /dev/null; \
+ fi
+
+ @echo Ldap test user test.one
+ @if [ $(shell docker ps -a | grep -ci mattermost-openldap) -eq 0 ]; then \
+ echo starting mattermost-openldap; \
+ docker run --name mattermost-openldap -p 389:389 -p 636:636 \
+ -e LDAP_TLS_VERIFY_CLIENT="never" \
+ -e LDAP_ORGANISATION="Mattermost Test" \
+ -e LDAP_DOMAIN="mm.test.com" \
+ -e LDAP_ADMIN_PASSWORD="mostest" \
+ -d osixia/openldap:1.1.2 > /dev/null;\
+ sleep 10; \
+ docker exec -ti mattermost-openldap bash -c 'echo -e "dn: ou=testusers,dc=mm,dc=test,dc=com\nobjectclass: organizationalunit" | ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest';\
+ docker exec -ti mattermost-openldap bash -c 'echo -e "dn: uid=test.one,ou=testusers,dc=mm,dc=test,dc=com\nobjectclass: iNetOrgPerson\nsn: User\ncn: Test1\nmail: success+testone@simulator.amazonses.com" | ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest';\
+ docker exec -ti mattermost-openldap bash -c 'ldappasswd -s Password1 -D "cn=admin,dc=mm,dc=test,dc=com" -x "uid=test.one,ou=testusers,dc=mm,dc=test,dc=com" -w mostest';\
+ docker exec -ti mattermost-openldap bash -c 'echo -e "dn: uid=test.two,ou=testusers,dc=mm,dc=test,dc=com\nobjectclass: iNetOrgPerson\nsn: User\ncn: Test2\nmail: success+testtwo@simulator.amazonses.com" | ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest';\
+ docker exec -ti mattermost-openldap bash -c 'ldappasswd -s Password1 -D "cn=admin,dc=mm,dc=test,dc=com" -x "uid=test.two,ou=testusers,dc=mm,dc=test,dc=com" -w mostest';\
+ docker exec -ti mattermost-openldap bash -c 'echo -e "dn: cn=tgroup,ou=testusers,dc=mm,dc=test,dc=com\nobjectclass: groupOfUniqueNames\nuniqueMember: uid=test.one,ou=testusers,dc=mm,dc=test,dc=com" | ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest';\
+ elif [ $(shell docker ps | grep -ci mattermost-openldap) -eq 0 ]; then \
+ echo restarting mattermost-openldap; \
+ docker start mattermost-openldap > /dev/null; \
sleep 10; \
fi
@@ -82,22 +102,33 @@ stop-docker:
echo stopping mattermost-postgres; \
docker stop mattermost-postgres > /dev/null; \
fi
+
+ @if [ $(shell docker ps -a | grep -ci mattermost-openldap) -eq 1 ]; then \
+ echo stopping mattermost-openldap; \
+ docker stop mattermost-openldap > /dev/null; \
+ fi
clean-docker:
@echo Removing docker containers
@if [ $(shell docker ps -a | grep -ci mattermost-mysql) -eq 1 ]; then \
- echo stopping mattermost-mysql; \
+ echo removing mattermost-mysql; \
docker stop mattermost-mysql > /dev/null; \
docker rm -v mattermost-mysql > /dev/null; \
fi
@if [ $(shell docker ps -a | grep -ci mattermost-postgres) -eq 1 ]; then \
- echo stopping mattermost-postgres; \
+ echo removing mattermost-postgres; \
docker stop mattermost-postgres > /dev/null; \
docker rm -v mattermost-postgres > /dev/null; \
fi
+ @if [ $(shell docker ps -a | grep -ci mattermost-openldap) -eq 1 ]; then \
+ echo removing mattermost-openldap; \
+ docker stop mattermost-openldap > /dev/null; \
+ docker rm -v mattermost-openldap > /dev/null; \
+ fi
+
check-style:
@echo Running GOFMT
$(eval GOFMT_OUTPUT := $(shell gofmt -d -s api/ model/ store/ utils/ manualtesting/ einterfaces/ mattermost.go 2>&1))
@@ -110,11 +141,32 @@ check-style:
fi
test: start-docker
- $(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=180s ./api || exit 1
+ @echo Running tests
+
+ $(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=240s ./api || exit 1
$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=12s ./model || exit 1
$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./store || exit 1
$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./utils || exit 1
$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./web || exit 1
+ifeq ($(BUILD_ENTERPRISE_READY),true)
+ @echo Running Enterprise tests
+ $(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s $(BUILD_ENTERPRISE_DIR)/ldap || exit 1
+ $(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s $(BUILD_ENTERPRISE_DIR)/compliance || exit 1
+endif
+
+setup-run-client-tests:
+ sed -i'.bak' 's|"EnableOpenServer": false,|"EnableOpenServer": true,|g' config/config.json
+
+cleanup-run-client-tests:
+ sed -i'.bak' 's|"EnableOpenServer": true,|"EnableOpenServer": false,|g' config/config.json
+
+run-client-tests:
+ cd $(BUILD_WEBAPP_DIR) && $(MAKE) test
+ sleep 10
+ @echo Running client side unit tests
+ cd $(BUILD_WEBAPP_DIR) && npm test
+
+test-client: setup-run-client-tests run-server run-client-tests stop-server cleanup-run-client-tests
.prebuild:
@echo Preparation for running go code
@@ -124,7 +176,7 @@ test: start-docker
prepare-enterprise:
ifeq ($(BUILD_ENTERPRISE_READY),true)
- @echo Enterprise build selected, perparing
+ @echo Enterprise build selected, preparing
cp $(BUILD_ENTERPRISE_DIR)/imports.go .
endif
diff --git a/api/admin.go b/api/admin.go
index 3ed2bee7a..930170619 100644
--- a/api/admin.go
+++ b/api/admin.go
@@ -19,24 +19,25 @@ import (
"github.com/mssola/user_agent"
)
-func InitAdmin(r *mux.Router) {
+func InitAdmin() {
l4g.Debug(utils.T("api.admin.init.debug"))
- sr := r.PathPrefix("/admin").Subrouter()
- sr.Handle("/logs", ApiUserRequired(getLogs)).Methods("GET")
- sr.Handle("/audits", ApiUserRequired(getAllAudits)).Methods("GET")
- sr.Handle("/config", ApiUserRequired(getConfig)).Methods("GET")
- sr.Handle("/save_config", ApiUserRequired(saveConfig)).Methods("POST")
- sr.Handle("/test_email", ApiUserRequired(testEmail)).Methods("POST")
- sr.Handle("/client_props", ApiAppHandler(getClientConfig)).Methods("GET")
- sr.Handle("/log_client", ApiAppHandler(logClient)).Methods("POST")
- sr.Handle("/analytics/{id:[A-Za-z0-9]+}/{name:[A-Za-z0-9_]+}", ApiUserRequired(getAnalytics)).Methods("GET")
- sr.Handle("/analytics/{name:[A-Za-z0-9_]+}", ApiUserRequired(getAnalytics)).Methods("GET")
- sr.Handle("/save_compliance_report", ApiUserRequired(saveComplianceReport)).Methods("POST")
- sr.Handle("/compliance_reports", ApiUserRequired(getComplianceReports)).Methods("GET")
- sr.Handle("/download_compliance_report/{id:[A-Za-z0-9]+}", ApiUserRequired(downloadComplianceReport)).Methods("GET")
- sr.Handle("/upload_brand_image", ApiAdminSystemRequired(uploadBrandImage)).Methods("POST")
- sr.Handle("/get_brand_image", ApiAppHandlerTrustRequester(getBrandImage)).Methods("GET")
+ BaseRoutes.Admin.Handle("/logs", ApiUserRequired(getLogs)).Methods("GET")
+ BaseRoutes.Admin.Handle("/audits", ApiUserRequired(getAllAudits)).Methods("GET")
+ BaseRoutes.Admin.Handle("/config", ApiUserRequired(getConfig)).Methods("GET")
+ BaseRoutes.Admin.Handle("/save_config", ApiUserRequired(saveConfig)).Methods("POST")
+ BaseRoutes.Admin.Handle("/test_email", ApiUserRequired(testEmail)).Methods("POST")
+ BaseRoutes.Admin.Handle("/client_props", ApiAppHandler(getClientConfig)).Methods("GET")
+ BaseRoutes.Admin.Handle("/log_client", ApiAppHandler(logClient)).Methods("POST")
+ BaseRoutes.Admin.Handle("/analytics/{id:[A-Za-z0-9]+}/{name:[A-Za-z0-9_]+}", ApiUserRequired(getAnalytics)).Methods("GET")
+ BaseRoutes.Admin.Handle("/analytics/{name:[A-Za-z0-9_]+}", ApiUserRequired(getAnalytics)).Methods("GET")
+ BaseRoutes.Admin.Handle("/save_compliance_report", ApiUserRequired(saveComplianceReport)).Methods("POST")
+ BaseRoutes.Admin.Handle("/compliance_reports", ApiUserRequired(getComplianceReports)).Methods("GET")
+ BaseRoutes.Admin.Handle("/download_compliance_report/{id:[A-Za-z0-9]+}", ApiUserRequired(downloadComplianceReport)).Methods("GET")
+ BaseRoutes.Admin.Handle("/upload_brand_image", ApiAdminSystemRequired(uploadBrandImage)).Methods("POST")
+ BaseRoutes.Admin.Handle("/get_brand_image", ApiAppHandlerTrustRequester(getBrandImage)).Methods("GET")
+ BaseRoutes.Admin.Handle("/reset_mfa", ApiAdminSystemRequired(adminResetMfa)).Methods("POST")
+ BaseRoutes.Admin.Handle("/reset_password", ApiAdminSystemRequired(adminResetPassword)).Methods("POST")
}
func getLogs(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -374,7 +375,7 @@ func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) {
iHookChan := Srv.Store.Webhook().AnalyticsIncomingCount(teamId)
oHookChan := Srv.Store.Webhook().AnalyticsOutgoingCount(teamId)
commandChan := Srv.Store.Command().AnalyticsCommandCount(teamId)
- sessionChan := Srv.Store.Session().AnalyticsSessionCount(teamId)
+ sessionChan := Srv.Store.Session().AnalyticsSessionCount()
if r := <-fileChan; r.Err != nil {
c.Err = r.Err
@@ -498,3 +499,51 @@ func getBrandImage(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write(img)
}
}
+
+func adminResetMfa(c *Context, w http.ResponseWriter, r *http.Request) {
+ props := model.MapFromJson(r.Body)
+
+ userId := props["user_id"]
+ if len(userId) != 26 {
+ c.SetInvalidParam("adminResetMfa", "user_id")
+ return
+ }
+
+ if err := DeactivateMfa(userId); err != nil {
+ c.Err = err
+ return
+ }
+
+ c.LogAudit("")
+
+ rdata := map[string]string{}
+ rdata["status"] = "ok"
+ w.Write([]byte(model.MapToJson(rdata)))
+}
+
+func adminResetPassword(c *Context, w http.ResponseWriter, r *http.Request) {
+ props := model.MapFromJson(r.Body)
+
+ userId := props["user_id"]
+ if len(userId) != 26 {
+ c.SetInvalidParam("adminResetPassword", "user_id")
+ return
+ }
+
+ newPassword := props["new_password"]
+ if len(newPassword) < model.MIN_PASSWORD_LENGTH {
+ c.SetInvalidParam("adminResetPassword", "new_password")
+ return
+ }
+
+ if err := ResetPassword(c, userId, newPassword); err != nil {
+ c.Err = err
+ return
+ }
+
+ c.LogAudit("")
+
+ rdata := map[string]string{}
+ rdata["status"] = "ok"
+ w.Write([]byte(model.MapToJson(rdata)))
+}
diff --git a/api/admin_test.go b/api/admin_test.go
index 67bc1d38b..2edc151bd 100644
--- a/api/admin_test.go
+++ b/api/admin_test.go
@@ -7,33 +7,18 @@ import (
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
+ "strings"
"testing"
)
func TestGetLogs(t *testing.T) {
- Setup()
+ th := Setup().InitSystemAdmin().InitBasic()
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- if _, err := Client.GetLogs(); err == nil {
+ if _, err := th.BasicClient.GetLogs(); err == nil {
t.Fatal("Shouldn't have permissions")
}
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- if logs, err := Client.GetLogs(); err != nil {
+ if logs, err := th.SystemAdminClient.GetLogs(); err != nil {
t.Fatal(err)
} else if len(logs.Data.([]string)) <= 0 {
t.Fatal()
@@ -41,29 +26,13 @@ func TestGetLogs(t *testing.T) {
}
func TestGetAllAudits(t *testing.T) {
- Setup()
+ th := Setup().InitBasic().InitSystemAdmin()
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- if _, err := Client.GetAllAudits(); err == nil {
+ if _, err := th.BasicClient.GetAllAudits(); err == nil {
t.Fatal("Shouldn't have permissions")
}
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- if audits, err := Client.GetAllAudits(); err != nil {
+ if audits, err := th.SystemAdminClient.GetAllAudits(); err != nil {
t.Fatal(err)
} else if len(audits.Data.(model.Audits)) <= 0 {
t.Fatal()
@@ -71,9 +40,9 @@ func TestGetAllAudits(t *testing.T) {
}
func TestGetClientProperties(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
- if result, err := Client.GetClientProperties(); err != nil {
+ if result, err := th.BasicClient.GetClientProperties(); err != nil {
t.Fatal(err)
} else {
props := result.Data.(map[string]string)
@@ -85,29 +54,13 @@ func TestGetClientProperties(t *testing.T) {
}
func TestGetConfig(t *testing.T) {
- Setup()
+ th := Setup().InitBasic().InitSystemAdmin()
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- if _, err := Client.GetConfig(); err == nil {
+ if _, err := th.BasicClient.GetConfig(); err == nil {
t.Fatal("Shouldn't have permissions")
}
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- if result, err := Client.GetConfig(); err != nil {
+ if result, err := th.SystemAdminClient.GetConfig(); err != nil {
t.Fatal(err)
} else {
cfg := result.Data.(*model.Config)
@@ -119,29 +72,15 @@ func TestGetConfig(t *testing.T) {
}
func TestSaveConfig(t *testing.T) {
- Setup()
+ th := Setup().InitBasic().InitSystemAdmin()
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- if _, err := Client.SaveConfig(utils.Cfg); err == nil {
+ if _, err := th.BasicClient.SaveConfig(utils.Cfg); err == nil {
t.Fatal("Shouldn't have permissions")
}
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ *utils.Cfg.TeamSettings.EnableOpenServer = false
- if result, err := Client.SaveConfig(utils.Cfg); err != nil {
+ if result, err := th.SystemAdminClient.SaveConfig(utils.Cfg); err != nil {
t.Fatal(err)
} else {
cfg := result.Data.(*model.Config)
@@ -150,66 +89,31 @@ func TestSaveConfig(t *testing.T) {
t.Fatal()
}
}
+
+ *utils.Cfg.TeamSettings.EnableOpenServer = true
}
func TestEmailTest(t *testing.T) {
- Setup()
+ th := Setup().InitBasic().InitSystemAdmin()
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- if _, err := Client.TestEmail(utils.Cfg); err == nil {
+ if _, err := th.BasicClient.TestEmail(utils.Cfg); err == nil {
t.Fatal("Shouldn't have permissions")
}
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- if _, err := Client.TestEmail(utils.Cfg); err != nil {
+ if _, err := th.SystemAdminClient.TestEmail(utils.Cfg); err != nil {
t.Fatal(err)
}
}
func TestGetTeamAnalyticsStandard(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
-
- post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
- post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
+ th := Setup().InitBasic().InitSystemAdmin()
+ th.CreatePrivateChannel(th.BasicClient, th.BasicTeam)
- if _, err := Client.GetTeamAnalytics(team.Id, "standard"); err == nil {
+ if _, err := th.BasicClient.GetTeamAnalytics(th.BasicTeam.Id, "standard"); err == nil {
t.Fatal("Shouldn't have permissions")
}
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- if result, err := Client.GetTeamAnalytics(team.Id, "standard"); err != nil {
+ if result, err := th.SystemAdminClient.GetTeamAnalytics(th.BasicTeam.Id, "standard"); err != nil {
t.Fatal(err)
} else {
rows := result.Data.(model.AnalyticsRows)
@@ -219,7 +123,7 @@ func TestGetTeamAnalyticsStandard(t *testing.T) {
t.Fatal()
}
- if rows[0].Value != 2 {
+ if rows[0].Value != 3 {
t.Log(rows.ToJson())
t.Fatal()
}
@@ -249,7 +153,7 @@ func TestGetTeamAnalyticsStandard(t *testing.T) {
t.Fatal()
}
- if rows[3].Value != 1 {
+ if rows[3].Value != 2 {
t.Log(rows.ToJson())
t.Fatal()
}
@@ -265,7 +169,7 @@ func TestGetTeamAnalyticsStandard(t *testing.T) {
}
}
- if result, err := Client.GetSystemAnalytics("standard"); err != nil {
+ if result, err := th.SystemAdminClient.GetSystemAnalytics("standard"); err != nil {
t.Fatal(err)
} else {
rows := result.Data.(model.AnalyticsRows)
@@ -275,7 +179,7 @@ func TestGetTeamAnalyticsStandard(t *testing.T) {
t.Fatal()
}
- if rows[0].Value < 2 {
+ if rows[0].Value < 3 {
t.Log(rows.ToJson())
t.Fatal()
}
@@ -323,39 +227,17 @@ func TestGetTeamAnalyticsStandard(t *testing.T) {
}
func TestGetPostCount(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
-
- post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
- post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
+ th := Setup().InitBasic().InitSystemAdmin()
// manually update creation time, since it's always set to 0 upon saving and we only retrieve posts < today
Srv.Store.(*store.SqlStore).GetMaster().Exec("UPDATE Posts SET CreateAt = :CreateAt WHERE ChannelId = :ChannelId",
- map[string]interface{}{"ChannelId": channel1.Id, "CreateAt": utils.MillisFromTime(utils.Yesterday())})
+ map[string]interface{}{"ChannelId": th.BasicChannel.Id, "CreateAt": utils.MillisFromTime(utils.Yesterday())})
- if _, err := Client.GetTeamAnalytics(team.Id, "post_counts_day"); err == nil {
+ if _, err := th.BasicClient.GetTeamAnalytics(th.BasicTeam.Id, "post_counts_day"); err == nil {
t.Fatal("Shouldn't have permissions")
}
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- if result, err := Client.GetTeamAnalytics(team.Id, "post_counts_day"); err != nil {
+ if result, err := th.SystemAdminClient.GetTeamAnalytics(th.BasicTeam.Id, "post_counts_day"); err != nil {
t.Fatal(err)
} else {
rows := result.Data.(model.AnalyticsRows)
@@ -368,39 +250,17 @@ func TestGetPostCount(t *testing.T) {
}
func TestUserCountsWithPostsByDay(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
-
- post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
- post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
+ th := Setup().InitBasic().InitSystemAdmin()
// manually update creation time, since it's always set to 0 upon saving and we only retrieve posts < today
Srv.Store.(*store.SqlStore).GetMaster().Exec("UPDATE Posts SET CreateAt = :CreateAt WHERE ChannelId = :ChannelId",
- map[string]interface{}{"ChannelId": channel1.Id, "CreateAt": utils.MillisFromTime(utils.Yesterday())})
+ map[string]interface{}{"ChannelId": th.BasicChannel.Id, "CreateAt": utils.MillisFromTime(utils.Yesterday())})
- if _, err := Client.GetTeamAnalytics(team.Id, "user_counts_with_posts_day"); err == nil {
+ if _, err := th.BasicClient.GetTeamAnalytics(th.BasicTeam.Id, "user_counts_with_posts_day"); err == nil {
t.Fatal("Shouldn't have permissions")
}
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- if result, err := Client.GetTeamAnalytics(team.Id, "user_counts_with_posts_day"); err != nil {
+ if result, err := th.SystemAdminClient.GetTeamAnalytics(th.BasicTeam.Id, "user_counts_with_posts_day"); err != nil {
t.Fatal(err)
} else {
rows := result.Data.(model.AnalyticsRows)
@@ -413,38 +273,15 @@ func TestUserCountsWithPostsByDay(t *testing.T) {
}
func TestGetTeamAnalyticsExtra(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
-
- post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
- post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
+ th := Setup().InitBasic().InitSystemAdmin()
- post2 := &model.Post{ChannelId: channel1.Id, Message: "#test a" + model.NewId() + "a"}
- post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post)
+ th.CreatePost(th.BasicClient, th.BasicChannel)
- if _, err := Client.GetTeamAnalytics("", "extra_counts"); err == nil {
+ if _, err := th.BasicClient.GetTeamAnalytics("", "extra_counts"); err == nil {
t.Fatal("Shouldn't have permissions")
}
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- if result, err := Client.GetTeamAnalytics(team.Id, "extra_counts"); err != nil {
+ if result, err := th.SystemAdminClient.GetTeamAnalytics(th.BasicTeam.Id, "extra_counts"); err != nil {
t.Fatal(err)
} else {
rows := result.Data.(model.AnalyticsRows)
@@ -464,7 +301,7 @@ func TestGetTeamAnalyticsExtra(t *testing.T) {
t.Fatal()
}
- if rows[1].Value != 1 {
+ if rows[1].Value != 0 {
t.Log(rows.ToJson())
t.Fatal()
}
@@ -510,7 +347,7 @@ func TestGetTeamAnalyticsExtra(t *testing.T) {
}
}
- if result, err := Client.GetSystemAnalytics("extra_counts"); err != nil {
+ if result, err := th.SystemAdminClient.GetSystemAnalytics("extra_counts"); err != nil {
t.Fatal(err)
} else {
rows := result.Data.(model.AnalyticsRows)
@@ -525,11 +362,6 @@ func TestGetTeamAnalyticsExtra(t *testing.T) {
t.Fatal()
}
- if rows[1].Value < 1 {
- t.Log(rows.ToJson())
- t.Fatal()
- }
-
if rows[2].Name != "incoming_webhook_count" {
t.Log(rows.ToJson())
t.Fatal()
@@ -551,3 +383,73 @@ func TestGetTeamAnalyticsExtra(t *testing.T) {
}
}
}
+
+func TestAdminResetMfa(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+
+ if _, err := th.BasicClient.AdminResetMfa("12345678901234567890123456"); err == nil {
+ t.Fatal("should have failed - not an admin")
+ }
+
+ if _, err := th.SystemAdminClient.AdminResetMfa(""); err == nil {
+ t.Fatal("should have failed - empty user id")
+ }
+
+ if _, err := th.SystemAdminClient.AdminResetMfa("12345678901234567890123456"); err == nil {
+ t.Fatal("should have failed - bad user id")
+ }
+
+ if _, err := th.SystemAdminClient.AdminResetMfa(th.BasicUser.Id); err == nil {
+ t.Fatal("should have failed - not licensed or configured")
+ }
+
+ // need to add more test cases when enterprise bits can be loaded into tests
+}
+
+func TestAdminResetPassword(t *testing.T) {
+ th := Setup().InitSystemAdmin()
+ Client := th.SystemAdminClient
+ team := th.SystemAdminTeam
+
+ user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ LinkUserToTeam(user, team)
+ store.Must(Srv.Store.User().VerifyEmail(user.Id))
+
+ if _, err := Client.AdminResetPassword("", "newpwd"); err == nil {
+ t.Fatal("Should have errored - empty user id")
+ }
+
+ if _, err := Client.AdminResetPassword("123", "newpwd"); err == nil {
+ t.Fatal("Should have errored - bad user id")
+ }
+
+ if _, err := Client.AdminResetPassword("12345678901234567890123456", "newpwd"); err == nil {
+ t.Fatal("Should have errored - bad user id")
+ }
+
+ if _, err := Client.AdminResetPassword("12345678901234567890123456", "newp"); err == nil {
+ t.Fatal("Should have errored - password too short")
+ }
+
+ user2 := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", AuthData: "1", AuthService: "random"}
+ user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
+ LinkUserToTeam(user2, team)
+ store.Must(Srv.Store.User().VerifyEmail(user2.Id))
+
+ if _, err := Client.AdminResetPassword(user2.Id, "newpwd"); err == nil {
+ t.Fatal("should have errored - SSO user can't reset password")
+ }
+
+ if _, err := Client.AdminResetPassword(user.Id, "newpwd"); err != nil {
+ t.Fatal(err)
+ }
+
+ Client.Logout()
+ Client.Must(Client.LoginById(user.Id, "newpwd"))
+ Client.SetTeamId(team.Id)
+
+ if _, err := Client.AdminResetPassword(user.Id, "newpwd"); err == nil {
+ t.Fatal("Should have errored - not sytem admin")
+ }
+}
diff --git a/api/api.go b/api/api.go
index 476047877..e9a95b125 100644
--- a/api/api.go
+++ b/api/api.go
@@ -6,6 +6,7 @@ package api
import (
"net/http"
+ "github.com/gorilla/mux"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
@@ -13,20 +14,71 @@ import (
_ "github.com/nicksnyder/go-i18n/i18n"
)
+type Routes struct {
+ Root *mux.Router // ''
+ ApiRoot *mux.Router // 'api/v3'
+
+ Users *mux.Router // 'api/v3/users'
+ NeedUser *mux.Router // 'api/v3/users/{user_id:[A-Za-z0-9]+}'
+
+ Teams *mux.Router // 'api/v3/teams'
+ NeedTeam *mux.Router // 'api/v3/teams/{team_id:[A-Za-z0-9]+}'
+
+ Channels *mux.Router // 'api/v3/teams/{team_id:[A-Za-z0-9]+}/channels'
+ NeedChannel *mux.Router // 'api/v3/teams/{team_id:[A-Za-z0-9]+}/channels/{channel_id:[A-Za-z0-9]+}'
+
+ Posts *mux.Router // 'api/v3/teams/{team_id:[A-Za-z0-9]+}/channels/{channel_id:[A-Za-z0-9]+}/posts'
+ NeedPost *mux.Router // 'api/v3/teams/{team_id:[A-Za-z0-9]+}/channels/{channel_id:[A-Za-z0-9]+}/posts/{post_id:[A-Za-z0-9]+}'
+
+ Commands *mux.Router // 'api/v3/teams/{team_id:[A-Za-z0-9]+}/commands'
+ Hooks *mux.Router // 'api/v3/teams/{team_id:[A-Za-z0-9]+}/hooks'
+
+ Files *mux.Router // 'api/v3/teams/{team_id:[A-Za-z0-9]+}/files'
+
+ OAuth *mux.Router // 'api/v3/oauth'
+
+ Admin *mux.Router // 'api/v3/admin'
+
+ Preferences *mux.Router // 'api/v3/preferences'
+
+ License *mux.Router // 'api/v3/license'
+}
+
+var BaseRoutes *Routes
+
func InitApi() {
- r := Srv.Router.PathPrefix("/api/v1").Subrouter()
- InitUser(r)
- InitTeam(r)
- InitChannel(r)
- InitPost(r)
- InitWebSocket(r)
- InitFile(r)
- InitCommand(r)
- InitAdmin(r)
- InitOAuth(r)
- InitWebhook(r)
- InitPreference(r)
- InitLicense(r)
+ BaseRoutes = &Routes{}
+ BaseRoutes.Root = Srv.Router
+ BaseRoutes.ApiRoot = Srv.Router.PathPrefix(model.API_URL_SUFFIX).Subrouter()
+ BaseRoutes.Users = BaseRoutes.ApiRoot.PathPrefix("/users").Subrouter()
+ BaseRoutes.NeedUser = BaseRoutes.Users.PathPrefix("/{user_id:[A-Za-z0-9]+}").Subrouter()
+ BaseRoutes.Teams = BaseRoutes.ApiRoot.PathPrefix("/teams").Subrouter()
+ BaseRoutes.NeedTeam = BaseRoutes.Teams.PathPrefix("/{team_id:[A-Za-z0-9]+}").Subrouter()
+ BaseRoutes.Channels = BaseRoutes.NeedTeam.PathPrefix("/channels").Subrouter()
+ BaseRoutes.NeedChannel = BaseRoutes.Channels.PathPrefix("/{channel_id:[A-Za-z0-9]+}").Subrouter()
+ BaseRoutes.Posts = BaseRoutes.NeedChannel.PathPrefix("/posts").Subrouter()
+ BaseRoutes.NeedPost = BaseRoutes.Posts.PathPrefix("/{post_id:[A-Za-z0-9]+}").Subrouter()
+ BaseRoutes.Commands = BaseRoutes.NeedTeam.PathPrefix("/commands").Subrouter()
+ BaseRoutes.Files = BaseRoutes.NeedTeam.PathPrefix("/files").Subrouter()
+ BaseRoutes.Hooks = BaseRoutes.NeedTeam.PathPrefix("/hooks").Subrouter()
+ BaseRoutes.OAuth = BaseRoutes.ApiRoot.PathPrefix("/oauth").Subrouter()
+ BaseRoutes.Admin = BaseRoutes.ApiRoot.PathPrefix("/admin").Subrouter()
+ BaseRoutes.Preferences = BaseRoutes.ApiRoot.PathPrefix("/preferences").Subrouter()
+ BaseRoutes.License = BaseRoutes.ApiRoot.PathPrefix("/license").Subrouter()
+
+ InitUser()
+ InitTeam()
+ InitChannel()
+ InitPost()
+ InitWebSocket()
+ InitFile()
+ InitCommand()
+ InitAdmin()
+ InitOAuth()
+ InitWebhook()
+ InitPreference()
+ InitLicense()
+
// 404 on any api route before web.go has a chance to serve it
Srv.Router.Handle("/api/{anything:.*}", http.HandlerFunc(Handle404))
diff --git a/api/api_test.go b/api/api_test.go
deleted file mode 100644
index 94691ab4b..000000000
--- a/api/api_test.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package api
-
-import (
- "github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
- "github.com/mattermost/platform/utils"
-)
-
-var Client *model.Client
-
-func Setup() {
- if Srv == nil {
- utils.LoadConfig("config.json")
- utils.InitTranslations()
- utils.Cfg.TeamSettings.MaxUsersPerTeam = 50
- NewServer()
- StartServer()
- InitApi()
- Client = model.NewClient("http://localhost" + utils.Cfg.ServiceSettings.ListenAddress)
-
- Srv.Store.MarkSystemRanUnitTests()
- }
-}
-
-func SetupBenchmark() (*model.Team, *model.User, *model.Channel) {
- Setup()
-
- team := &model.Team{DisplayName: "Benchmark Team", Name: "z-z-" + model.NewId() + "a", Email: "benchmark@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Mr. Benchmarker", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
- Client.LoginByEmail(team.Name, user.Email, "pwd")
- channel := &model.Channel{DisplayName: "Benchmark Channel", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel)
-
- return team, user, channel
-}
-
-func TearDown() {
- if Srv != nil {
- StopServer()
- }
-}
diff --git a/api/apitestlib.go b/api/apitestlib.go
new file mode 100644
index 000000000..d82dc30be
--- /dev/null
+++ b/api/apitestlib.go
@@ -0,0 +1,223 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api
+
+import (
+ "time"
+
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/store"
+ "github.com/mattermost/platform/utils"
+
+ l4g "github.com/alecthomas/log4go"
+)
+
+type TestHelper struct {
+ BasicClient *model.Client
+ BasicTeam *model.Team
+ BasicUser *model.User
+ BasicUser2 *model.User
+ BasicChannel *model.Channel
+ BasicPost *model.Post
+
+ SystemAdminClient *model.Client
+ SystemAdminTeam *model.Team
+ SystemAdminUser *model.User
+ SystemAdminChannel *model.Channel
+}
+
+func SetupEnterprise(platformDir string) *TestHelper {
+ if Srv == nil {
+ utils.LoadConfig(platformDir + "/config/config.json")
+ utils.InitTranslationsWithDir(platformDir + "/i18n")
+ utils.Cfg.TeamSettings.MaxUsersPerTeam = 50
+ utils.DisableDebugLogForTest()
+ utils.License.Features.SetDefaults()
+ NewServer()
+ StartServer()
+ utils.InitHTMLWithDir(platformDir + "/templates")
+ InitApi()
+ utils.EnableDebugLogForTest()
+ Srv.Store.MarkSystemRanUnitTests()
+
+ *utils.Cfg.TeamSettings.EnableOpenServer = true
+ }
+
+ return &TestHelper{}
+}
+
+func Setup() *TestHelper {
+ if Srv == nil {
+ utils.LoadConfig("config.json")
+ utils.InitTranslations()
+ utils.Cfg.TeamSettings.MaxUsersPerTeam = 50
+ utils.DisableDebugLogForTest()
+ NewServer()
+ StartServer()
+ InitApi()
+ utils.EnableDebugLogForTest()
+ Srv.Store.MarkSystemRanUnitTests()
+
+ *utils.Cfg.TeamSettings.EnableOpenServer = true
+ }
+
+ return &TestHelper{}
+}
+
+func (me *TestHelper) InitBasic() *TestHelper {
+ me.BasicClient = me.CreateClient()
+ me.BasicTeam = me.CreateTeam(me.BasicClient)
+ me.BasicUser = me.CreateUser(me.BasicClient)
+ LinkUserToTeam(me.BasicUser, me.BasicTeam)
+ me.BasicUser2 = me.CreateUser(me.BasicClient)
+ LinkUserToTeam(me.BasicUser2, me.BasicTeam)
+ me.BasicClient.SetTeamId(me.BasicTeam.Id)
+ me.LoginBasic()
+ me.BasicChannel = me.CreateChannel(me.BasicClient, me.BasicTeam)
+ me.BasicPost = me.CreatePost(me.BasicClient, me.BasicChannel)
+
+ return me
+}
+
+func (me *TestHelper) InitSystemAdmin() *TestHelper {
+ me.SystemAdminClient = me.CreateClient()
+ me.SystemAdminTeam = me.CreateTeam(me.SystemAdminClient)
+ me.SystemAdminUser = me.CreateUser(me.SystemAdminClient)
+ LinkUserToTeam(me.SystemAdminUser, me.SystemAdminTeam)
+ me.SystemAdminClient.SetTeamId(me.SystemAdminTeam.Id)
+ c := &Context{}
+ c.RequestId = model.NewId()
+ c.IpAddress = "cmd_line"
+ UpdateRoles(c, me.SystemAdminUser, model.ROLE_SYSTEM_ADMIN)
+ me.SystemAdminUser.Password = "Password1"
+ me.LoginSystemAdmin()
+ me.SystemAdminChannel = me.CreateChannel(me.SystemAdminClient, me.SystemAdminTeam)
+
+ return me
+}
+
+func (me *TestHelper) CreateClient() *model.Client {
+ return model.NewClient("http://localhost" + utils.Cfg.ServiceSettings.ListenAddress)
+}
+
+func (me *TestHelper) CreateTeam(client *model.Client) *model.Team {
+ id := model.NewId()
+ team := &model.Team{
+ DisplayName: "dn_" + id,
+ Name: "name" + id,
+ Email: "success+" + id + "@simulator.amazonses.com",
+ Type: model.TEAM_OPEN,
+ }
+
+ utils.DisableDebugLogForTest()
+ r := client.Must(client.CreateTeam(team)).Data.(*model.Team)
+ utils.EnableDebugLogForTest()
+ return r
+}
+
+func (me *TestHelper) CreateUser(client *model.Client) *model.User {
+ id := model.NewId()
+
+ user := &model.User{
+ Email: "success+" + id + "@simulator.amazonses.com",
+ Username: "un_" + id,
+ Nickname: "nn_" + id,
+ Password: "Password1",
+ }
+
+ utils.DisableDebugLogForTest()
+ ruser := client.Must(client.CreateUser(user, "")).Data.(*model.User)
+ ruser.Password = "Password1"
+ store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
+ utils.EnableDebugLogForTest()
+ return ruser
+}
+
+func LinkUserToTeam(user *model.User, team *model.Team) {
+ utils.DisableDebugLogForTest()
+
+ err := JoinUserToTeam(team, user)
+ if err != nil {
+ l4g.Error(err.Error())
+ l4g.Close()
+ time.Sleep(time.Second)
+ panic(err)
+ }
+
+ utils.EnableDebugLogForTest()
+}
+
+func UpdateUserToTeamAdmin(user *model.User, team *model.Team) {
+ utils.DisableDebugLogForTest()
+
+ tm := &model.TeamMember{TeamId: team.Id, UserId: user.Id, Roles: model.ROLE_TEAM_ADMIN}
+ if tmr := <-Srv.Store.Team().UpdateMember(tm); tmr.Err != nil {
+ l4g.Error(tmr.Err.Error())
+ l4g.Close()
+ time.Sleep(time.Second)
+ panic(tmr.Err)
+ }
+}
+
+func (me *TestHelper) CreateChannel(client *model.Client, team *model.Team) *model.Channel {
+ return me.createChannel(client, team, model.CHANNEL_OPEN)
+}
+
+func (me *TestHelper) CreatePrivateChannel(client *model.Client, team *model.Team) *model.Channel {
+ return me.createChannel(client, team, model.CHANNEL_PRIVATE)
+}
+
+func (me *TestHelper) createChannel(client *model.Client, team *model.Team, channelType string) *model.Channel {
+ id := model.NewId()
+
+ channel := &model.Channel{
+ DisplayName: "dn_" + id,
+ Name: "name_" + id,
+ Type: channelType,
+ TeamId: team.Id,
+ }
+
+ utils.DisableDebugLogForTest()
+ r := client.Must(client.CreateChannel(channel)).Data.(*model.Channel)
+ utils.EnableDebugLogForTest()
+ return r
+}
+
+func (me *TestHelper) CreatePost(client *model.Client, channel *model.Channel) *model.Post {
+ id := model.NewId()
+
+ post := &model.Post{
+ ChannelId: channel.Id,
+ Message: "message_" + id,
+ }
+
+ utils.DisableDebugLogForTest()
+ r := client.Must(client.CreatePost(post)).Data.(*model.Post)
+ utils.EnableDebugLogForTest()
+ return r
+}
+
+func (me *TestHelper) LoginBasic() {
+ utils.DisableDebugLogForTest()
+ me.BasicClient.Must(me.BasicClient.LoginByEmail(me.BasicTeam.Name, me.BasicUser.Email, me.BasicUser.Password))
+ utils.EnableDebugLogForTest()
+}
+
+func (me *TestHelper) LoginBasic2() {
+ utils.DisableDebugLogForTest()
+ me.BasicClient.Must(me.BasicClient.LoginByEmail(me.BasicTeam.Name, me.BasicUser2.Email, me.BasicUser2.Password))
+ utils.EnableDebugLogForTest()
+}
+
+func (me *TestHelper) LoginSystemAdmin() {
+ utils.DisableDebugLogForTest()
+ me.SystemAdminClient.Must(me.SystemAdminClient.LoginByEmail(me.SystemAdminTeam.Name, me.SystemAdminUser.Email, me.SystemAdminUser.Password))
+ utils.EnableDebugLogForTest()
+}
+
+func TearDown() {
+ if Srv != nil {
+ StopServer()
+ }
+}
diff --git a/api/authentication.go b/api/authentication.go
new file mode 100644
index 000000000..bab83a720
--- /dev/null
+++ b/api/authentication.go
@@ -0,0 +1,99 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api
+
+import (
+ "github.com/mattermost/platform/einterfaces"
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+)
+
+func checkPasswordAndAllCriteria(user *model.User, password string, mfaToken string) *model.AppError {
+ if err := checkUserPassword(user, password); err != nil {
+ return err
+ }
+
+ if err := checkUserAdditionalAuthenticationCriteria(user, mfaToken); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func checkUserPassword(user *model.User, password string) *model.AppError {
+ if !model.ComparePassword(user.Password, password) {
+ if result := <-Srv.Store.User().UpdateFailedPasswordAttempts(user.Id, user.FailedAttempts+1); result.Err != nil {
+ return result.Err
+ }
+
+ return model.NewLocAppError("checkUserPassword", "api.user.check_user_password.invalid.app_error", nil, "user_id="+user.Id)
+ } else {
+ if result := <-Srv.Store.User().UpdateFailedPasswordAttempts(user.Id, 0); result.Err != nil {
+ return result.Err
+ }
+
+ return nil
+ }
+}
+
+func checkUserAdditionalAuthenticationCriteria(user *model.User, mfaToken string) *model.AppError {
+ if err := checkUserMfa(user, mfaToken); err != nil {
+ return err
+ }
+
+ if err := checkEmailVerified(user); err != nil {
+ return err
+ }
+
+ if err := checkUserNotDisabled(user); err != nil {
+ return err
+ }
+
+ if err := checkUserLoginAttempts(user); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func checkUserMfa(user *model.User, token string) *model.AppError {
+ if !user.MfaActive || !utils.IsLicensed || !*utils.License.Features.MFA || !*utils.Cfg.ServiceSettings.EnableMultifactorAuthentication {
+ return nil
+ }
+
+ mfaInterface := einterfaces.GetMfaInterface()
+ if mfaInterface == nil {
+ return model.NewLocAppError("checkUserMfa", "api.user.check_user_mfa.not_available.app_error", nil, "")
+ }
+
+ if ok, err := mfaInterface.ValidateToken(user.MfaSecret, token); err != nil {
+ return err
+ } else if !ok {
+ return model.NewLocAppError("checkUserMfa", "api.user.check_user_mfa.bad_code.app_error", nil, "")
+ }
+
+ return nil
+}
+
+func checkUserLoginAttempts(user *model.User) *model.AppError {
+ if user.FailedAttempts >= utils.Cfg.ServiceSettings.MaximumLoginAttempts {
+ return model.NewLocAppError("checkUserLoginAttempts", "api.user.check_user_login_attempts.too_many.app_error", nil, "user_id="+user.Id)
+ }
+
+ return nil
+}
+
+func checkEmailVerified(user *model.User) *model.AppError {
+ if !user.EmailVerified && utils.Cfg.EmailSettings.RequireEmailVerification {
+ return model.NewLocAppError("Login", "api.user.login.not_verified.app_error", nil, "user_id="+user.Id)
+ }
+ return nil
+}
+
+func checkUserNotDisabled(user *model.User) *model.AppError {
+ if user.DeleteAt > 0 {
+ return model.NewLocAppError("Login", "api.user.login.inactive.app_error", nil, "user_id="+user.Id)
+ }
+ return nil
+}
diff --git a/api/auto_channels.go b/api/auto_channels.go
index ab1fe6ed3..1d0f0e7d9 100644
--- a/api/auto_channels.go
+++ b/api/auto_channels.go
@@ -10,7 +10,7 @@ import (
type AutoChannelCreator struct {
client *model.Client
- teamID string
+ team *model.Team
Fuzzy bool
DisplayNameLen utils.Range
DisplayNameCharset string
@@ -19,10 +19,10 @@ type AutoChannelCreator struct {
ChannelType string
}
-func NewAutoChannelCreator(client *model.Client, teamID string) *AutoChannelCreator {
+func NewAutoChannelCreator(client *model.Client, team *model.Team) *AutoChannelCreator {
return &AutoChannelCreator{
client: client,
- teamID: teamID,
+ team: team,
Fuzzy: false,
DisplayNameLen: CHANNEL_DISPLAY_NAME_LEN,
DisplayNameCharset: utils.ALPHANUMERIC,
@@ -42,13 +42,17 @@ func (cfg *AutoChannelCreator) createRandomChannel() (*model.Channel, bool) {
name := utils.RandomName(cfg.NameLen, cfg.NameCharset)
channel := &model.Channel{
- TeamId: cfg.teamID,
+ TeamId: cfg.team.Id,
DisplayName: displayName,
Name: name,
Type: cfg.ChannelType}
+ println(cfg.client.GetTeamRoute())
result, err := cfg.client.CreateChannel(channel)
if err != nil {
+ err.Translate(utils.T)
+ println(err.Error())
+ println(err.DetailedError)
return nil, false
}
return result.Data.(*model.Channel), true
diff --git a/api/auto_environment.go b/api/auto_environment.go
index 68186ec6c..270b43936 100644
--- a/api/auto_environment.go
+++ b/api/auto_environment.go
@@ -28,14 +28,15 @@ func CreateTestEnvironmentWithTeams(client *model.Client, rangeTeams utils.Range
environment := TestEnvironment{teams, make([]TeamEnvironment, len(teams))}
for i, team := range teams {
- userCreator := NewAutoUserCreator(client, team.Id)
+ userCreator := NewAutoUserCreator(client, team)
userCreator.Fuzzy = fuzzy
randomUser, err := userCreator.createRandomUser()
if err != true {
return TestEnvironment{}, false
}
client.LoginById(randomUser.Id, USER_PASSWORD)
- teamEnvironment, err := CreateTestEnvironmentInTeam(client, team.Id, rangeChannels, rangeUsers, rangePosts, fuzzy)
+ client.SetTeamId(team.Id)
+ teamEnvironment, err := CreateTestEnvironmentInTeam(client, team, rangeChannels, rangeUsers, rangePosts, fuzzy)
if err != true {
return TestEnvironment{}, false
}
@@ -45,7 +46,7 @@ func CreateTestEnvironmentWithTeams(client *model.Client, rangeTeams utils.Range
return environment, true
}
-func CreateTestEnvironmentInTeam(client *model.Client, teamID string, rangeChannels utils.Range, rangeUsers utils.Range, rangePosts utils.Range, fuzzy bool) (TeamEnvironment, bool) {
+func CreateTestEnvironmentInTeam(client *model.Client, team *model.Team, rangeChannels utils.Range, rangeUsers utils.Range, rangePosts utils.Range, fuzzy bool) (TeamEnvironment, bool) {
rand.Seed(time.Now().UTC().UnixNano())
// We need to create at least one user
@@ -53,7 +54,7 @@ func CreateTestEnvironmentInTeam(client *model.Client, teamID string, rangeChann
rangeUsers.Begin = 1
}
- userCreator := NewAutoUserCreator(client, teamID)
+ userCreator := NewAutoUserCreator(client, team)
userCreator.Fuzzy = fuzzy
users, err := userCreator.CreateTestUsers(rangeUsers)
if err != true {
@@ -64,7 +65,7 @@ func CreateTestEnvironmentInTeam(client *model.Client, teamID string, rangeChann
usernames[i] = user.Username
}
- channelCreator := NewAutoChannelCreator(client, teamID)
+ channelCreator := NewAutoChannelCreator(client, team)
channelCreator.Fuzzy = fuzzy
channels, err := channelCreator.CreateTestChannels(rangeChannels)
@@ -79,6 +80,7 @@ func CreateTestEnvironmentInTeam(client *model.Client, teamID string, rangeChann
if err != true {
return TeamEnvironment{}, false
}
+
numPosts := utils.RandIntFromRange(rangePosts)
numImages := utils.RandIntFromRange(rangePosts) / 4
for j := 0; j < numPosts; j++ {
diff --git a/api/auto_posts.go b/api/auto_posts.go
index b64217c55..2e26e513b 100644
--- a/api/auto_posts.go
+++ b/api/auto_posts.go
@@ -74,7 +74,7 @@ func (cfg *AutoPostCreator) UploadTestFile() ([]string, bool) {
return nil, false
}
- resp, appErr := cfg.client.UploadFile("/files/upload", body.Bytes(), writer.FormDataContentType())
+ resp, appErr := cfg.client.UploadPostAttachment(body.Bytes(), writer.FormDataContentType())
if appErr != nil {
return nil, false
}
diff --git a/api/auto_teams.go b/api/auto_teams.go
index 082415d32..b2e1ace85 100644
--- a/api/auto_teams.go
+++ b/api/auto_teams.go
@@ -42,11 +42,11 @@ func (cfg *AutoTeamCreator) createRandomTeam() (*model.Team, bool) {
var teamDisplayName string
var teamName string
if cfg.Fuzzy {
- teamEmail = utils.FuzzEmail()
+ teamEmail = "success+" + model.NewId() + "simulator.amazonses.com"
teamDisplayName = utils.FuzzName()
teamName = utils.FuzzName()
} else {
- teamEmail = utils.RandomEmail(cfg.EmailLength, cfg.EmailCharset)
+ teamEmail = "success+" + model.NewId() + "simulator.amazonses.com"
teamDisplayName = utils.RandomName(cfg.NameLength, cfg.NameCharset)
teamName = utils.RandomName(cfg.NameLength, cfg.NameCharset) + model.NewId()
}
diff --git a/api/auto_users.go b/api/auto_users.go
index d1e3d494e..a23b76246 100644
--- a/api/auto_users.go
+++ b/api/auto_users.go
@@ -7,11 +7,13 @@ import (
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
+
+ l4g "github.com/alecthomas/log4go"
)
type AutoUserCreator struct {
client *model.Client
- teamID string
+ team *model.Team
EmailLength utils.Range
EmailCharset string
NameLength utils.Range
@@ -19,10 +21,10 @@ type AutoUserCreator struct {
Fuzzy bool
}
-func NewAutoUserCreator(client *model.Client, teamID string) *AutoUserCreator {
+func NewAutoUserCreator(client *model.Client, team *model.Team) *AutoUserCreator {
return &AutoUserCreator{
client: client,
- teamID: teamID,
+ team: team,
EmailLength: USER_EMAIL_LEN,
EmailCharset: utils.LOWERCASE,
NameLength: USER_NAME_LEN,
@@ -33,7 +35,7 @@ func NewAutoUserCreator(client *model.Client, teamID string) *AutoUserCreator {
// Basic test team and user so you always know one
func CreateBasicUser(client *model.Client) *model.AppError {
- result, _ := client.FindTeamByName(BTEST_TEAM_NAME, true)
+ result, _ := client.FindTeamByName(BTEST_TEAM_NAME)
if result.Data.(bool) == false {
newteam := &model.Team{DisplayName: BTEST_TEAM_DISPLAY_NAME, Name: BTEST_TEAM_NAME, Email: BTEST_TEAM_EMAIL, Type: BTEST_TEAM_TYPE}
result, err := client.CreateTeam(newteam)
@@ -41,12 +43,14 @@ func CreateBasicUser(client *model.Client) *model.AppError {
return err
}
basicteam := result.Data.(*model.Team)
- newuser := &model.User{TeamId: basicteam.Id, Email: BTEST_USER_EMAIL, Nickname: BTEST_USER_NAME, Password: BTEST_USER_PASSWORD}
+ newuser := &model.User{Email: BTEST_USER_EMAIL, Nickname: BTEST_USER_NAME, Password: BTEST_USER_PASSWORD}
result, err = client.CreateUser(newuser, "")
if err != nil {
return err
}
- store.Must(Srv.Store.User().VerifyEmail(result.Data.(*model.User).Id))
+ ruser := result.Data.(*model.User)
+ store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
+ store.Must(Srv.Store.Team().SaveMember(&model.TeamMember{TeamId: basicteam.Id, UserId: ruser.Id}))
}
return nil
}
@@ -55,25 +59,30 @@ func (cfg *AutoUserCreator) createRandomUser() (*model.User, bool) {
var userEmail string
var userName string
if cfg.Fuzzy {
- userEmail = utils.RandString(FUZZ_USER_EMAIL_PREFIX_LEN, utils.LOWERCASE) + "-" + utils.FuzzEmail()
+ userEmail = "success+" + model.NewId() + "simulator.amazonses.com"
userName = utils.FuzzName()
} else {
- userEmail = utils.RandomEmail(cfg.EmailLength, cfg.EmailCharset)
+ userEmail = "success+" + model.NewId() + "simulator.amazonses.com"
userName = utils.RandomName(cfg.NameLength, cfg.NameCharset)
}
user := &model.User{
- TeamId: cfg.teamID,
Email: userEmail,
Nickname: userName,
Password: USER_PASSWORD}
- result, err := cfg.client.CreateUser(user, "")
+ result, err := cfg.client.CreateUserWithInvite(user, "", "", cfg.team.InviteId)
if err != nil {
+ err.Translate(utils.T)
+ l4g.Error(err.Error())
return nil, false
}
+
+ ruser := result.Data.(*model.User)
+
// We need to cheat to verify the user's email
- store.Must(Srv.Store.User().VerifyEmail(result.Data.(*model.User).Id))
+ store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
+
return result.Data.(*model.User), true
}
diff --git a/api/channel.go b/api/channel.go
index e97e08fc0..871477824 100644
--- a/api/channel.go
+++ b/api/channel.go
@@ -18,29 +18,28 @@ const (
defaultExtraMemberLimit = 100
)
-func InitChannel(r *mux.Router) {
+func InitChannel() {
l4g.Debug(utils.T("api.channel.init.debug"))
- sr := r.PathPrefix("/channels").Subrouter()
- sr.Handle("/", ApiUserRequiredActivity(getChannels, false)).Methods("GET")
- sr.Handle("/more", ApiUserRequired(getMoreChannels)).Methods("GET")
- sr.Handle("/counts", ApiUserRequiredActivity(getChannelCounts, false)).Methods("GET")
- sr.Handle("/create", ApiUserRequired(createChannel)).Methods("POST")
- sr.Handle("/create_direct", ApiUserRequired(createDirectChannel)).Methods("POST")
- sr.Handle("/update", ApiUserRequired(updateChannel)).Methods("POST")
- sr.Handle("/update_header", ApiUserRequired(updateChannelHeader)).Methods("POST")
- sr.Handle("/update_purpose", ApiUserRequired(updateChannelPurpose)).Methods("POST")
- sr.Handle("/update_notify_props", ApiUserRequired(updateNotifyProps)).Methods("POST")
- sr.Handle("/{id:[A-Za-z0-9]+}/", ApiUserRequiredActivity(getChannel, false)).Methods("GET")
- sr.Handle("/{id:[A-Za-z0-9]+}/extra_info", ApiUserRequired(getChannelExtraInfo)).Methods("GET")
- sr.Handle("/{id:[A-Za-z0-9]+}/extra_info/{member_limit:-?[0-9]+}", ApiUserRequired(getChannelExtraInfo)).Methods("GET")
- sr.Handle("/{id:[A-Za-z0-9]+}/join", ApiUserRequired(join)).Methods("POST")
- sr.Handle("/{id:[A-Za-z0-9]+}/leave", ApiUserRequired(leave)).Methods("POST")
- sr.Handle("/{id:[A-Za-z0-9]+}/delete", ApiUserRequired(deleteChannel)).Methods("POST")
- sr.Handle("/{id:[A-Za-z0-9]+}/add", ApiUserRequired(addMember)).Methods("POST")
- sr.Handle("/{id:[A-Za-z0-9]+}/remove", ApiUserRequired(removeMember)).Methods("POST")
- sr.Handle("/{id:[A-Za-z0-9]+}/update_last_viewed_at", ApiUserRequired(updateLastViewedAt)).Methods("POST")
-
+ BaseRoutes.Channels.Handle("/", ApiUserRequiredActivity(getChannels, false)).Methods("GET")
+ BaseRoutes.Channels.Handle("/more", ApiUserRequired(getMoreChannels)).Methods("GET")
+ BaseRoutes.Channels.Handle("/counts", ApiUserRequiredActivity(getChannelCounts, false)).Methods("GET")
+ BaseRoutes.Channels.Handle("/create", ApiUserRequired(createChannel)).Methods("POST")
+ BaseRoutes.Channels.Handle("/create_direct", ApiUserRequired(createDirectChannel)).Methods("POST")
+ BaseRoutes.Channels.Handle("/update", ApiUserRequired(updateChannel)).Methods("POST")
+ BaseRoutes.Channels.Handle("/update_header", ApiUserRequired(updateChannelHeader)).Methods("POST")
+ BaseRoutes.Channels.Handle("/update_purpose", ApiUserRequired(updateChannelPurpose)).Methods("POST")
+ BaseRoutes.Channels.Handle("/update_notify_props", ApiUserRequired(updateNotifyProps)).Methods("POST")
+
+ BaseRoutes.NeedChannel.Handle("/", ApiUserRequiredActivity(getChannel, false)).Methods("GET")
+ BaseRoutes.NeedChannel.Handle("/extra_info", ApiUserRequired(getChannelExtraInfo)).Methods("GET")
+ BaseRoutes.NeedChannel.Handle("/extra_info/{member_limit:-?[0-9]+}", ApiUserRequired(getChannelExtraInfo)).Methods("GET")
+ BaseRoutes.NeedChannel.Handle("/join", ApiUserRequired(join)).Methods("POST")
+ BaseRoutes.NeedChannel.Handle("/leave", ApiUserRequired(leave)).Methods("POST")
+ BaseRoutes.NeedChannel.Handle("/delete", ApiUserRequired(deleteChannel)).Methods("POST")
+ BaseRoutes.NeedChannel.Handle("/add", ApiUserRequired(addMember)).Methods("POST")
+ BaseRoutes.NeedChannel.Handle("/remove", ApiUserRequired(removeMember)).Methods("POST")
+ BaseRoutes.NeedChannel.Handle("/update_last_viewed_at", ApiUserRequired(updateLastViewedAt)).Methods("POST")
}
func createChannel(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -52,6 +51,10 @@ func createChannel(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ if len(channel.TeamId) == 0 {
+ channel.TeamId = c.TeamId
+ }
+
if !c.HasPermissionsToTeam(channel.TeamId, "createChannel") {
return
}
@@ -107,10 +110,6 @@ func createDirectChannel(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !c.HasPermissionsToTeam(c.Session.TeamId, "createDirectChannel") {
- return
- }
-
if sc, err := CreateDirectChannel(c, userId); err != nil {
c.Err = err
return
@@ -131,7 +130,7 @@ func CreateDirectChannel(c *Context, otherUserId string) (*model.Channel, *model
channel.DisplayName = ""
channel.Name = model.GetDMNameFromIds(otherUserId, c.Session.UserId)
- channel.TeamId = c.Session.TeamId
+ channel.TeamId = c.TeamId
channel.Header = ""
channel.Type = model.CHANNEL_DIRECT
@@ -214,7 +213,7 @@ func updateChannel(c *Context, w http.ResponseWriter, r *http.Request) {
if oldChannel.Name == model.DEFAULT_CHANNEL {
if (len(channel.Name) > 0 && channel.Name != oldChannel.Name) || (len(channel.Type) > 0 && channel.Type != oldChannel.Type) {
c.Err = model.NewLocAppError("updateChannel", "api.channel.update_channel.tried.app_error", map[string]interface{}{"Channel": model.DEFAULT_CHANNEL}, "")
- c.Err.StatusCode = http.StatusForbidden
+ c.Err.StatusCode = http.StatusBadRequest
return
}
}
@@ -367,7 +366,7 @@ func getChannels(c *Context, w http.ResponseWriter, r *http.Request) {
// user is already in the team
- if result := <-Srv.Store.Channel().GetChannels(c.Session.TeamId, c.Session.UserId); result.Err != nil {
+ if result := <-Srv.Store.Channel().GetChannels(c.TeamId, c.Session.UserId); result.Err != nil {
if result.Err.Id == "store.sql_channel.get_channels.not_found.app_error" {
// lets make sure the user is valid
if result := <-Srv.Store.User().Get(c.Session.UserId); result.Err != nil {
@@ -392,7 +391,7 @@ func getMoreChannels(c *Context, w http.ResponseWriter, r *http.Request) {
// user is already in the team
- if result := <-Srv.Store.Channel().GetMoreChannels(c.Session.TeamId, c.Session.UserId); result.Err != nil {
+ if result := <-Srv.Store.Channel().GetMoreChannels(c.TeamId, c.Session.UserId); result.Err != nil {
c.Err = result.Err
return
} else if HandleEtag(result.Data.(*model.ChannelList).Etag(), w, r) {
@@ -408,7 +407,7 @@ func getChannelCounts(c *Context, w http.ResponseWriter, r *http.Request) {
// user is already in the team
- if result := <-Srv.Store.Channel().GetChannelCounts(c.Session.TeamId, c.Session.UserId); result.Err != nil {
+ if result := <-Srv.Store.Channel().GetChannelCounts(c.TeamId, c.Session.UserId); result.Err != nil {
c.Err = model.NewLocAppError("getChannelCounts", "api.channel.get_channel_counts.app_error", nil, result.Err.Message)
return
} else if HandleEtag(result.Data.(*model.ChannelCounts).Etag(), w, r) {
@@ -423,7 +422,7 @@ func getChannelCounts(c *Context, w http.ResponseWriter, r *http.Request) {
func join(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
- channelId := params["id"]
+ channelId := params["channel_id"]
JoinChannel(c, channelId, "")
@@ -498,7 +497,7 @@ func AddUserToChannel(user *model.User, channel *model.Channel) (*model.ChannelM
}
go func() {
- UpdateChannelAccessCache(channel.TeamId, user.Id, channel.Id)
+ InvalidateCacheForUser(user.Id)
message := model.NewMessage(channel.TeamId, channel.Id, user.Id, model.ACTION_USER_ADDED)
PublishAndForget(message)
@@ -507,12 +506,12 @@ func AddUserToChannel(user *model.User, channel *model.Channel) (*model.ChannelM
return newMember, nil
}
-func JoinDefaultChannels(user *model.User, channelRole string) *model.AppError {
+func JoinDefaultChannels(teamId string, user *model.User, channelRole string) *model.AppError {
// We don't call JoinChannel here since c.Session is not populated on user creation
var err *model.AppError = nil
- if result := <-Srv.Store.Channel().GetByName(user.TeamId, "town-square"); result.Err != nil {
+ if result := <-Srv.Store.Channel().GetByName(teamId, "town-square"); result.Err != nil {
err = result.Err
} else {
cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id,
@@ -523,7 +522,7 @@ func JoinDefaultChannels(user *model.User, channelRole string) *model.AppError {
}
}
- if result := <-Srv.Store.Channel().GetByName(user.TeamId, "off-topic"); result.Err != nil {
+ if result := <-Srv.Store.Channel().GetByName(teamId, "off-topic"); result.Err != nil {
err = result.Err
} else {
cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id,
@@ -540,7 +539,7 @@ func JoinDefaultChannels(user *model.User, channelRole string) *model.AppError {
func leave(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
- id := params["id"]
+ id := params["channel_id"]
sc := Srv.Store.Channel().Get(id)
uc := Srv.Store.User().Get(c.Session.UserId)
@@ -561,13 +560,13 @@ func leave(c *Context, w http.ResponseWriter, r *http.Request) {
if channel.Type == model.CHANNEL_DIRECT {
c.Err = model.NewLocAppError("leave", "api.channel.leave.direct.app_error", nil, "")
- c.Err.StatusCode = http.StatusForbidden
+ c.Err.StatusCode = http.StatusBadRequest
return
}
if channel.Name == model.DEFAULT_CHANNEL {
c.Err = model.NewLocAppError("leave", "api.channel.leave.default.app_error", map[string]interface{}{"Channel": model.DEFAULT_CHANNEL}, "")
- c.Err.StatusCode = http.StatusForbidden
+ c.Err.StatusCode = http.StatusBadRequest
return
}
@@ -589,7 +588,7 @@ func leave(c *Context, w http.ResponseWriter, r *http.Request) {
func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
- id := params["id"]
+ id := params["channel_id"]
sc := Srv.Store.Channel().Get(id)
scm := Srv.Store.Channel().GetMember(id, c.Session.UserId)
@@ -637,7 +636,7 @@ func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) {
if channel.Name == model.DEFAULT_CHANNEL {
c.Err = model.NewLocAppError("deleteChannel", "api.channel.delete_channel.cannot.app_error", map[string]interface{}{"Channel": model.DEFAULT_CHANNEL}, "")
- c.Err.StatusCode = http.StatusForbidden
+ c.Err.StatusCode = http.StatusBadRequest
return
}
@@ -682,7 +681,7 @@ func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) {
func updateLastViewedAt(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
- id := params["id"]
+ id := params["channel_id"]
Srv.Store.Channel().UpdateLastViewedAt(id, c.Session.UserId)
@@ -695,7 +694,7 @@ func updateLastViewedAt(c *Context, w http.ResponseWriter, r *http.Request) {
Srv.Store.Preference().Save(&model.Preferences{preference})
- message := model.NewMessage(c.Session.TeamId, id, c.Session.UserId, model.ACTION_CHANNEL_VIEWED)
+ message := model.NewMessage(c.TeamId, id, c.Session.UserId, model.ACTION_CHANNEL_VIEWED)
message.Add("channel_id", id)
PublishAndForget(message)
@@ -707,9 +706,9 @@ func updateLastViewedAt(c *Context, w http.ResponseWriter, r *http.Request) {
func getChannel(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
- id := params["id"]
+ id := params["channel_id"]
- //pchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, id, c.Session.UserId)
+ //pchan := Srv.Store.Channel().CheckPermissionsTo(c.TeamId, id, c.Session.UserId)
cchan := Srv.Store.Channel().Get(id)
cmchan := Srv.Store.Channel().GetMember(id, c.Session.UserId)
@@ -737,7 +736,7 @@ func getChannel(c *Context, w http.ResponseWriter, r *http.Request) {
func getChannelExtraInfo(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
- id := params["id"]
+ id := params["channel_id"]
var memberLimit int
if memberLimitString, ok := params["member_limit"]; !ok {
@@ -781,7 +780,7 @@ func getChannelExtraInfo(c *Context, w http.ResponseWriter, r *http.Request) {
extraMembers := ecmresult.Data.([]model.ExtraMember)
memberCount := ccmresult.Data.(int64)
- if !c.HasPermissionsToTeam(channel.TeamId, "getChannelExtraInfo") {
+ if len(channel.TeamId) > 0 && !c.HasPermissionsToTeam(channel.TeamId, "getChannelExtraInfo") {
return
}
@@ -803,7 +802,7 @@ func getChannelExtraInfo(c *Context, w http.ResponseWriter, r *http.Request) {
func addMember(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
- id := params["id"]
+ id := params["channel_id"]
data := model.MapFromJson(r.Body)
userId := data["user_id"]
@@ -813,7 +812,7 @@ func addMember(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, id, c.Session.UserId)
+ cchan := Srv.Store.Channel().CheckPermissionsTo(c.TeamId, id, c.Session.UserId)
sc := Srv.Store.Channel().Get(id)
ouc := Srv.Store.User().Get(c.Session.UserId)
nuc := Srv.Store.User().Get(userId)
@@ -857,7 +856,7 @@ func addMember(c *Context, w http.ResponseWriter, r *http.Request) {
func removeMember(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
- channelId := params["id"]
+ channelId := params["channel_id"]
data := model.MapFromJson(r.Body)
userIdToRemove := data["user_id"]
@@ -914,7 +913,7 @@ func RemoveUserFromChannel(userIdToRemove string, removerUserId string, channel
return cmresult.Err
}
- UpdateChannelAccessCacheAndForget(channel.TeamId, userIdToRemove, channel.Id)
+ InvalidateCacheForUser(userIdToRemove)
message := model.NewMessage(channel.TeamId, channel.Id, userIdToRemove, model.ACTION_USER_REMOVED)
message.Add("remover_id", removerUserId)
@@ -938,7 +937,7 @@ func updateNotifyProps(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId)
+ cchan := Srv.Store.Channel().CheckPermissionsTo(c.TeamId, channelId, c.Session.UserId)
if !c.HasPermissionsToUser(userId, "updateNotifyLevel") {
return
diff --git a/api/channel_benchmark_test.go b/api/channel_benchmark_test.go
index 09c734cc2..3e7c2882c 100644
--- a/api/channel_benchmark_test.go
+++ b/api/channel_benchmark_test.go
@@ -12,61 +12,47 @@ import (
const (
NUM_CHANNELS = 140
+ NUM_USERS = 40
)
func BenchmarkCreateChannel(b *testing.B) {
- var (
- NUM_CHANNELS_RANGE = utils.Range{NUM_CHANNELS, NUM_CHANNELS}
- )
- team, _, _ := SetupBenchmark()
+ th := Setup().InitBasic()
- channelCreator := NewAutoChannelCreator(Client, team.Id)
+ channelCreator := NewAutoChannelCreator(th.BasicClient, th.BasicTeam)
- // Benchmark Start
b.ResetTimer()
for i := 0; i < b.N; i++ {
- channelCreator.CreateTestChannels(NUM_CHANNELS_RANGE)
+ channelCreator.CreateTestChannels(utils.Range{NUM_CHANNELS, NUM_CHANNELS})
}
}
func BenchmarkCreateDirectChannel(b *testing.B) {
- var (
- NUM_CHANNELS_RANGE = utils.Range{NUM_CHANNELS, NUM_CHANNELS}
- )
- team, _, _ := SetupBenchmark()
+ th := Setup().InitBasic()
- userCreator := NewAutoUserCreator(Client, team.Id)
- users, err := userCreator.CreateTestUsers(NUM_CHANNELS_RANGE)
+ userCreator := NewAutoUserCreator(th.BasicClient, th.BasicTeam)
+ users, err := userCreator.CreateTestUsers(utils.Range{NUM_USERS, NUM_USERS})
if err == false {
b.Fatal("Could not create users")
}
- data := make([]map[string]string, len(users))
-
- for i := range data {
- newmap := map[string]string{
- "user_id": users[i].Id,
- }
- data[i] = newmap
- }
-
// Benchmark Start
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < NUM_CHANNELS; j++ {
- Client.CreateDirectChannel(data[j])
+ for j := 0; j < NUM_USERS; j++ {
+ th.BasicClient.CreateDirectChannel(users[j].Id)
}
}
}
func BenchmarkUpdateChannel(b *testing.B) {
+ th := Setup().InitBasic()
+
var (
NUM_CHANNELS_RANGE = utils.Range{NUM_CHANNELS, NUM_CHANNELS}
CHANNEL_HEADER_LEN = 50
)
- team, _, _ := SetupBenchmark()
- channelCreator := NewAutoChannelCreator(Client, team.Id)
+ channelCreator := NewAutoChannelCreator(th.BasicClient, th.BasicTeam)
channels, valid := channelCreator.CreateTestChannels(NUM_CHANNELS_RANGE)
if valid == false {
b.Fatal("Unable to create test channels")
@@ -80,7 +66,7 @@ func BenchmarkUpdateChannel(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := range channels {
- if _, err := Client.UpdateChannel(channels[j]); err != nil {
+ if _, err := th.BasicClient.UpdateChannel(channels[j]); err != nil {
b.Fatal(err)
}
}
@@ -88,12 +74,13 @@ func BenchmarkUpdateChannel(b *testing.B) {
}
func BenchmarkGetChannels(b *testing.B) {
+ th := Setup().InitBasic()
+
var (
NUM_CHANNELS_RANGE = utils.Range{NUM_CHANNELS, NUM_CHANNELS}
)
- team, _, _ := SetupBenchmark()
- channelCreator := NewAutoChannelCreator(Client, team.Id)
+ channelCreator := NewAutoChannelCreator(th.BasicClient, th.BasicTeam)
_, valid := channelCreator.CreateTestChannels(NUM_CHANNELS_RANGE)
if valid == false {
b.Fatal("Unable to create test channels")
@@ -102,17 +89,18 @@ func BenchmarkGetChannels(b *testing.B) {
// Benchmark Start
b.ResetTimer()
for i := 0; i < b.N; i++ {
- Client.Must(Client.GetChannels(""))
+ th.BasicClient.Must(th.BasicClient.GetChannels(""))
}
}
func BenchmarkGetMoreChannels(b *testing.B) {
+ th := Setup().InitBasic()
+
var (
NUM_CHANNELS_RANGE = utils.Range{NUM_CHANNELS, NUM_CHANNELS}
)
- team, _, _ := SetupBenchmark()
- channelCreator := NewAutoChannelCreator(Client, team.Id)
+ channelCreator := NewAutoChannelCreator(th.BasicClient, th.BasicTeam)
_, valid := channelCreator.CreateTestChannels(NUM_CHANNELS_RANGE)
if valid == false {
b.Fatal("Unable to create test channels")
@@ -121,44 +109,47 @@ func BenchmarkGetMoreChannels(b *testing.B) {
// Benchmark Start
b.ResetTimer()
for i := 0; i < b.N; i++ {
- Client.Must(Client.GetMoreChannels(""))
+ th.BasicClient.Must(th.BasicClient.GetMoreChannels(""))
}
}
func BenchmarkJoinChannel(b *testing.B) {
+ th := Setup().InitBasic()
+
var (
NUM_CHANNELS_RANGE = utils.Range{NUM_CHANNELS, NUM_CHANNELS}
)
- team, _, _ := SetupBenchmark()
- channelCreator := NewAutoChannelCreator(Client, team.Id)
+ channelCreator := NewAutoChannelCreator(th.BasicClient, th.BasicTeam)
channels, valid := channelCreator.CreateTestChannels(NUM_CHANNELS_RANGE)
if valid == false {
b.Fatal("Unable to create test channels")
}
// Secondary test user to join channels created by primary test user
- user := &model.User{TeamId: team.Id, Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "That Guy", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ user := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "That Guy", Password: "pwd"}
+ user = th.BasicClient.Must(th.BasicClient.CreateUser(user, "")).Data.(*model.User)
+ LinkUserToTeam(user, th.BasicTeam)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ th.BasicClient.LoginByEmail(th.BasicTeam.Name, user.Email, "pwd")
// Benchmark Start
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := range channels {
- Client.Must(Client.JoinChannel(channels[j].Id))
+ th.BasicClient.Must(th.BasicClient.JoinChannel(channels[j].Id))
}
}
}
func BenchmarkDeleteChannel(b *testing.B) {
+ th := Setup().InitBasic()
+
var (
NUM_CHANNELS_RANGE = utils.Range{NUM_CHANNELS, NUM_CHANNELS}
)
- team, _, _ := SetupBenchmark()
- channelCreator := NewAutoChannelCreator(Client, team.Id)
+ channelCreator := NewAutoChannelCreator(th.BasicClient, th.BasicTeam)
channels, valid := channelCreator.CreateTestChannels(NUM_CHANNELS_RANGE)
if valid == false {
b.Fatal("Unable to create test channels")
@@ -168,18 +159,19 @@ func BenchmarkDeleteChannel(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := range channels {
- Client.Must(Client.DeleteChannel(channels[j].Id))
+ th.BasicClient.Must(th.BasicClient.DeleteChannel(channels[j].Id))
}
}
}
func BenchmarkGetChannelExtraInfo(b *testing.B) {
+ th := Setup().InitBasic()
+
var (
NUM_CHANNELS_RANGE = utils.Range{NUM_CHANNELS, NUM_CHANNELS}
)
- team, _, _ := SetupBenchmark()
- channelCreator := NewAutoChannelCreator(Client, team.Id)
+ channelCreator := NewAutoChannelCreator(th.BasicClient, th.BasicTeam)
channels, valid := channelCreator.CreateTestChannels(NUM_CHANNELS_RANGE)
if valid == false {
b.Fatal("Unable to create test channels")
@@ -189,22 +181,23 @@ func BenchmarkGetChannelExtraInfo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := range channels {
- Client.Must(Client.GetChannelExtraInfo(channels[j].Id, -1, ""))
+ th.BasicClient.Must(th.BasicClient.GetChannelExtraInfo(channels[j].Id, -1, ""))
}
}
}
func BenchmarkAddChannelMember(b *testing.B) {
+ th := Setup().InitBasic()
+
var (
NUM_USERS = 100
NUM_USERS_RANGE = utils.Range{NUM_USERS, NUM_USERS}
)
- team, _, _ := SetupBenchmark()
- channel := &model.Channel{DisplayName: "Test Channel", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel)
+ channel := &model.Channel{DisplayName: "Test Channel", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: th.BasicTeam.Id}
+ channel = th.BasicClient.Must(th.BasicClient.CreateChannel(channel)).Data.(*model.Channel)
- userCreator := NewAutoUserCreator(Client, team.Id)
+ userCreator := NewAutoUserCreator(th.BasicClient, th.BasicTeam)
users, valid := userCreator.CreateTestUsers(NUM_USERS_RANGE)
if valid == false {
b.Fatal("Unable to create test users")
@@ -214,7 +207,7 @@ func BenchmarkAddChannelMember(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := range users {
- if _, err := Client.AddChannelMember(channel.Id, users[j].Id); err != nil {
+ if _, err := th.BasicClient.AddChannelMember(channel.Id, users[j].Id); err != nil {
b.Fatal(err)
}
}
@@ -223,23 +216,24 @@ func BenchmarkAddChannelMember(b *testing.B) {
// Is this benchmark failing? Raise your file ulimit! 2048 worked for me.
func BenchmarkRemoveChannelMember(b *testing.B) {
+ th := Setup().InitBasic()
+
var (
NUM_USERS = 140
NUM_USERS_RANGE = utils.Range{NUM_USERS, NUM_USERS}
)
- team, _, _ := SetupBenchmark()
- channel := &model.Channel{DisplayName: "Test Channel", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel)
+ channel := &model.Channel{DisplayName: "Test Channel", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: th.BasicTeam.Id}
+ channel = th.BasicClient.Must(th.BasicClient.CreateChannel(channel)).Data.(*model.Channel)
- userCreator := NewAutoUserCreator(Client, team.Id)
+ userCreator := NewAutoUserCreator(th.BasicClient, th.BasicTeam)
users, valid := userCreator.CreateTestUsers(NUM_USERS_RANGE)
if valid == false {
b.Fatal("Unable to create test users")
}
for i := range users {
- if _, err := Client.AddChannelMember(channel.Id, users[i].Id); err != nil {
+ if _, err := th.BasicClient.AddChannelMember(channel.Id, users[i].Id); err != nil {
b.Fatal(err)
}
}
@@ -248,7 +242,7 @@ func BenchmarkRemoveChannelMember(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := range users {
- if _, err := Client.RemoveChannelMember(channel.Id, users[j].Id); err != nil {
+ if _, err := th.BasicClient.RemoveChannelMember(channel.Id, users[j].Id); err != nil {
b.Fatal(err)
}
}
@@ -256,12 +250,13 @@ func BenchmarkRemoveChannelMember(b *testing.B) {
}
func BenchmarkUpdateNotifyProps(b *testing.B) {
+ th := Setup().InitBasic()
+
var (
NUM_CHANNELS_RANGE = utils.Range{NUM_CHANNELS, NUM_CHANNELS}
)
- team, user, _ := SetupBenchmark()
- channelCreator := NewAutoChannelCreator(Client, team.Id)
+ channelCreator := NewAutoChannelCreator(th.BasicClient, th.BasicTeam)
channels, valid := channelCreator.CreateTestChannels(NUM_CHANNELS_RANGE)
if valid == false {
b.Fatal("Unable to create test channels")
@@ -272,7 +267,7 @@ func BenchmarkUpdateNotifyProps(b *testing.B) {
for i := range data {
newmap := map[string]string{
"channel_id": channels[i].Id,
- "user_id": user.Id,
+ "user_id": th.BasicUser.Id,
"desktop": model.CHANNEL_NOTIFY_MENTION,
"mark_unread": model.CHANNEL_MARK_UNREAD_MENTION,
}
@@ -283,7 +278,7 @@ func BenchmarkUpdateNotifyProps(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := range channels {
- Client.Must(Client.UpdateNotifyProps(data[j]))
+ th.BasicClient.Must(th.BasicClient.UpdateNotifyProps(data[j]))
}
}
}
diff --git a/api/channel_test.go b/api/channel_test.go
index c3015f924..23dd77698 100644
--- a/api/channel_test.go
+++ b/api/channel_test.go
@@ -14,19 +14,13 @@ import (
)
func TestCreateChannel(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- team2 := &model.Team{DisplayName: "Name Team 2", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+ Client.Must(Client.Logout())
+ team2 := th.CreateTeam(th.BasicClient)
+ th.LoginBasic()
+ th.BasicClient.SetTeamId(team.Id)
channel := model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
rchannel, err := Client.CreateChannel(&channel)
@@ -63,7 +57,7 @@ func TestCreateChannel(t *testing.T) {
}
}
- if _, err := Client.DoApiPost("/channels/create", "garbage"); err == nil {
+ if _, err := Client.DoApiPost(Client.GetTeamRoute()+"/channels/create", "garbage"); err == nil {
t.Fatal("should have been an error")
}
@@ -94,25 +88,12 @@ func TestCreateChannel(t *testing.T) {
}
func TestCreateDirectChannel(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- data := make(map[string]string)
- data["user_id"] = user2.Id
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ user := th.BasicUser
+ user2 := th.BasicUser2
- rchannel, err := Client.CreateDirectChannel(data)
+ rchannel, err := Client.CreateDirectChannel(th.BasicUser2.Id)
if err != nil {
t.Fatal(err)
}
@@ -132,47 +113,31 @@ func TestCreateDirectChannel(t *testing.T) {
t.Fatal("channel type was not direct")
}
- if _, err := Client.CreateDirectChannel(data); err == nil {
+ if _, err := Client.CreateDirectChannel(th.BasicUser2.Id); err == nil {
t.Fatal("channel already exists and should have failed")
}
- data["user_id"] = "junk"
- if _, err := Client.CreateDirectChannel(data); err == nil {
+ if _, err := Client.CreateDirectChannel("junk"); err == nil {
t.Fatal("should have failed with bad user id")
}
- data["user_id"] = "12345678901234567890123456"
- if _, err := Client.CreateDirectChannel(data); err == nil {
+ if _, err := Client.CreateDirectChannel("12345678901234567890123456"); err == nil {
t.Fatal("should have failed with non-existent user")
}
}
func TestUpdateChannel(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- userTeamAdmin := &model.User{TeamId: team.Id, Email: team.Email, Nickname: "Corey Hulen", Password: "pwd"}
- userTeamAdmin = Client.Must(Client.CreateUser(userTeamAdmin, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(userTeamAdmin.Id))
-
- userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- userChannelAdmin = Client.Must(Client.CreateUser(userChannelAdmin, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(userChannelAdmin.Id))
-
- userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- userStd = Client.Must(Client.CreateUser(userStd, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(userStd.Id))
- userStd.Roles = ""
-
- Client.LoginByEmail(team.Name, userChannelAdmin.Email, "pwd")
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+ user := th.BasicUser
+ user2 := th.CreateUser(th.BasicClient)
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
- Client.AddChannelMember(channel1.Id, userTeamAdmin.Id)
+ Client.AddChannelMember(channel1.Id, user.Id)
header := "a" + model.NewId() + "a"
purpose := "a" + model.NewId() + "a"
@@ -191,25 +156,6 @@ func TestUpdateChannel(t *testing.T) {
t.Fatal("Channel admin failed to skip displayName")
}
- Client.LoginByEmail(team.Name, userTeamAdmin.Email, "pwd")
-
- header = "b" + model.NewId() + "b"
- purpose = "b" + model.NewId() + "b"
- upChannel1 = &model.Channel{Id: channel1.Id, Header: header, Purpose: purpose}
- upChannel1 = Client.Must(Client.UpdateChannel(upChannel1)).Data.(*model.Channel)
-
- if upChannel1.Header != header {
- t.Fatal("Team admin failed to update header")
- }
-
- if upChannel1.Purpose != purpose {
- t.Fatal("Team admin failed to update purpose")
- }
-
- if upChannel1.DisplayName != channel1.DisplayName {
- t.Fatal("Team admin failed to skip displayName")
- }
-
rget := Client.Must(Client.GetChannels(""))
data := rget.Data.(*model.ChannelList)
for _, c := range data.Channels {
@@ -223,7 +169,7 @@ func TestUpdateChannel(t *testing.T) {
}
}
- Client.LoginByEmail(team.Name, userStd.Email, "pwd")
+ Client.LoginByEmail(team.Name, user2.Email, user2.Password)
if _, err := Client.UpdateChannel(upChannel1); err == nil {
t.Fatal("Standard User should have failed to update")
@@ -231,16 +177,9 @@ func TestUpdateChannel(t *testing.T) {
}
func TestUpdateChannelHeader(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
@@ -276,11 +215,7 @@ func TestUpdateChannelHeader(t *testing.T) {
t.Fatal("should have errored on bad channel header")
}
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
-
- Client.LoginByEmail(team.Name, user2.Email, "pwd")
+ th.LoginBasic2()
data["channel_id"] = channel1.Id
data["channel_header"] = "new header"
@@ -290,16 +225,9 @@ func TestUpdateChannelHeader(t *testing.T) {
}
func TestUpdateChannelPurpose(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
@@ -335,11 +263,7 @@ func TestUpdateChannelPurpose(t *testing.T) {
t.Fatal("should have errored on bad channel purpose")
}
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
-
- Client.LoginByEmail(team.Name, user2.Email, "pwd")
+ th.LoginBasic2()
data["channel_id"] = channel1.Id
data["channel_purpose"] = "new purpose"
@@ -349,16 +273,9 @@ func TestUpdateChannelPurpose(t *testing.T) {
}
func TestGetChannel(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
@@ -412,16 +329,9 @@ func TestGetChannel(t *testing.T) {
}
func TestGetMoreChannel(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
@@ -429,11 +339,7 @@ func TestGetMoreChannel(t *testing.T) {
channel2 := &model.Channel{DisplayName: "B Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
-
- Client.LoginByEmail(team.Name, user2.Email, "pwd")
+ th.LoginBasic2()
rget := Client.Must(Client.GetMoreChannels(""))
data := rget.Data.(*model.ChannelList)
@@ -456,16 +362,9 @@ func TestGetMoreChannel(t *testing.T) {
}
func TestGetChannelCounts(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
@@ -478,11 +377,11 @@ func TestGetChannelCounts(t *testing.T) {
} else {
counts := result.Data.(*model.ChannelCounts)
- if len(counts.Counts) != 4 {
+ if len(counts.Counts) != 5 {
t.Fatal("wrong number of channel counts")
}
- if len(counts.UpdateTimes) != 4 {
+ if len(counts.UpdateTimes) != 5 {
t.Fatal("wrong number of channel update times")
}
@@ -497,16 +396,9 @@ func TestGetChannelCounts(t *testing.T) {
}
func TestJoinChannel(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
@@ -514,11 +406,7 @@ func TestJoinChannel(t *testing.T) {
channel3 := &model.Channel{DisplayName: "B Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id}
channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel)
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
-
- Client.LoginByEmail(team.Name, user2.Email, "pwd")
+ th.LoginBasic2()
Client.Must(Client.JoinChannel(channel1.Id))
@@ -526,13 +414,10 @@ func TestJoinChannel(t *testing.T) {
t.Fatal("shouldn't be able to join secret group")
}
- data := make(map[string]string)
- data["user_id"] = user1.Id
- rchannel := Client.Must(Client.CreateDirectChannel(data)).Data.(*model.Channel)
-
- user3 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User)
+ rchannel := Client.Must(Client.CreateDirectChannel(th.BasicUser.Id)).Data.(*model.Channel)
+ user3 := th.CreateUser(th.BasicClient)
+ LinkUserToTeam(user3, team)
Client.LoginByEmail(team.Name, user3.Email, "pwd")
if _, err := Client.JoinChannel(rchannel.Id); err == nil {
@@ -541,16 +426,9 @@ func TestJoinChannel(t *testing.T) {
}
func TestLeaveChannel(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
@@ -558,20 +436,14 @@ func TestLeaveChannel(t *testing.T) {
channel3 := &model.Channel{DisplayName: "B Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id}
channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel)
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
-
- Client.LoginByEmail(team.Name, user2.Email, "pwd")
+ th.LoginBasic2()
Client.Must(Client.JoinChannel(channel1.Id))
// No error if you leave a channel you cannot see
Client.Must(Client.LeaveChannel(channel3.Id))
- data := make(map[string]string)
- data["user_id"] = user1.Id
- rchannel := Client.Must(Client.CreateDirectChannel(data)).Data.(*model.Channel)
+ rchannel := Client.Must(Client.CreateDirectChannel(th.BasicUser.Id)).Data.(*model.Channel)
if _, err := Client.LeaveChannel(rchannel.Id); err == nil {
t.Fatal("should have errored, cannot leave direct channel")
@@ -590,20 +462,12 @@ func TestLeaveChannel(t *testing.T) {
}
func TestDeleteChannel(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- userTeamAdmin := &model.User{TeamId: team.Id, Email: team.Email, Nickname: "Corey Hulen", Password: "pwd"}
- userTeamAdmin = Client.Must(Client.CreateUser(userTeamAdmin, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(userTeamAdmin.Id))
-
- userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- userChannelAdmin = Client.Must(Client.CreateUser(userChannelAdmin, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(userChannelAdmin.Id))
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+ userTeamAdmin := th.BasicUser
- Client.LoginByEmail(team.Name, userChannelAdmin.Email, "pwd")
+ th.LoginBasic2()
channelMadeByCA := &model.Channel{DisplayName: "C Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channelMadeByCA = Client.Must(Client.CreateChannel(channelMadeByCA)).Data.(*model.Channel)
@@ -631,11 +495,9 @@ func TestDeleteChannel(t *testing.T) {
t.Fatal("should have failed to post to deleted channel")
}
- userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- userStd = Client.Must(Client.CreateUser(userStd, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(userStd.Id))
-
- Client.LoginByEmail(team.Name, userStd.Email, "pwd")
+ userStd := th.CreateUser(th.BasicClient)
+ LinkUserToTeam(userStd, team)
+ Client.LoginByEmail(team.Name, userStd.Email, userStd.Password)
if _, err := Client.JoinChannel(channel1.Id); err == nil {
t.Fatal("should have failed to join deleted channel")
@@ -660,16 +522,9 @@ func TestDeleteChannel(t *testing.T) {
}
func TestGetChannelExtraInfo(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
@@ -701,8 +556,10 @@ func TestGetChannelExtraInfo(t *testing.T) {
Client2 := model.NewClient("http://localhost" + utils.Cfg.ServiceSettings.ListenAddress)
- user2 := &model.User{TeamId: team.Id, Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Tester 2", Password: "pwd"}
+ user2 := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Tester 2", Password: "pwd"}
user2 = Client2.Must(Client2.CreateUser(user2, "")).Data.(*model.User)
+ LinkUserToTeam(user2, team)
+ Client2.SetTeamId(team.Id)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
Client2.LoginByEmail(team.Name, user2.Email, "pwd")
@@ -784,24 +641,14 @@ func TestGetChannelExtraInfo(t *testing.T) {
}
func TestAddChannelMember(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+ user2 := th.BasicUser2
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
-
if _, err := Client.AddChannelMember(channel1.Id, user2.Id); err != nil {
t.Fatal(err)
}
@@ -825,13 +672,13 @@ func TestAddChannelMember(t *testing.T) {
channel2 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
- Client.LoginByEmail(team.Name, user2.Email, "pwd")
+ th.LoginBasic2()
if _, err := Client.AddChannelMember(channel2.Id, user2.Id); err == nil {
t.Fatal("Should have errored, user not in channel")
}
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
+ th.LoginBasic()
Client.Must(Client.DeleteChannel(channel2.Id))
@@ -842,34 +689,24 @@ func TestAddChannelMember(t *testing.T) {
}
func TestRemoveChannelMember(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- userTeamAdmin := &model.User{TeamId: team.Id, Email: team.Email, Nickname: "Corey Hulen", Password: "pwd"}
- userTeamAdmin = Client.Must(Client.CreateUser(userTeamAdmin, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(userTeamAdmin.Id))
-
- userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- userChannelAdmin = Client.Must(Client.CreateUser(userChannelAdmin, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(userChannelAdmin.Id))
-
- Client.LoginByEmail(team.Name, userChannelAdmin.Email, "pwd")
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+ user2 := th.BasicUser2
+ UpdateUserToTeamAdmin(user2, team)
channelMadeByCA := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channelMadeByCA = Client.Must(Client.CreateChannel(channelMadeByCA)).Data.(*model.Channel)
- Client.Must(Client.AddChannelMember(channelMadeByCA.Id, userTeamAdmin.Id))
+ Client.Must(Client.AddChannelMember(channelMadeByCA.Id, user2.Id))
- Client.LoginByEmail(team.Name, userTeamAdmin.Email, "pwd")
+ th.LoginBasic2()
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
- userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- userStd = Client.Must(Client.CreateUser(userStd, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(userStd.Id))
+ userStd := th.CreateUser(th.BasicClient)
+ LinkUserToTeam(userStd, team)
Client.Must(Client.AddChannelMember(channel1.Id, userStd.Id))
@@ -894,13 +731,13 @@ func TestRemoveChannelMember(t *testing.T) {
channel2 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
- Client.LoginByEmail(team.Name, userStd.Email, "pwd")
+ Client.LoginByEmail(team.Name, userStd.Email, userStd.Password)
if _, err := Client.RemoveChannelMember(channel2.Id, userStd.Id); err == nil {
t.Fatal("Should have errored, user not channel admin")
}
- Client.LoginByEmail(team.Name, userTeamAdmin.Email, "pwd")
+ th.LoginBasic2()
Client.Must(Client.AddChannelMember(channel2.Id, userStd.Id))
Client.Must(Client.DeleteChannel(channel2.Id))
@@ -912,16 +749,11 @@ func TestRemoveChannelMember(t *testing.T) {
}
func TestUpdateNotifyProps(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+ user := th.BasicUser
+ user2 := th.BasicUser2
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
@@ -1019,10 +851,7 @@ func TestUpdateNotifyProps(t *testing.T) {
t.Fatal("Should have errored - bad mark unread level")
}
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
-
- Client.LoginByEmail(team.Name, user2.Email, "pwd")
+ th.LoginBasic2()
data["channel_id"] = channel1.Id
data["user_id"] = user2.Id
@@ -1034,16 +863,9 @@ func TestUpdateNotifyProps(t *testing.T) {
}
func TestFuzzyChannel(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
// Strings that should pass as acceptable channel names
var fuzzyStringsPass = []string{
diff --git a/api/command.go b/api/command.go
index 99fd05d7a..72249a48c 100644
--- a/api/command.go
+++ b/api/command.go
@@ -12,7 +12,7 @@ import (
"strings"
l4g "github.com/alecthomas/log4go"
- "github.com/gorilla/mux"
+
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
)
@@ -38,23 +38,21 @@ func GetCommandProvider(name string) CommandProvider {
return nil
}
-func InitCommand(r *mux.Router) {
+func InitCommand() {
l4g.Debug(utils.T("api.command.init.debug"))
- sr := r.PathPrefix("/commands").Subrouter()
-
- sr.Handle("/execute", ApiUserRequired(executeCommand)).Methods("POST")
- sr.Handle("/list", ApiUserRequired(listCommands)).Methods("GET")
+ BaseRoutes.Commands.Handle("/execute", ApiUserRequired(executeCommand)).Methods("POST")
+ BaseRoutes.Commands.Handle("/list", ApiUserRequired(listCommands)).Methods("GET")
- sr.Handle("/create", ApiUserRequired(createCommand)).Methods("POST")
- sr.Handle("/list_team_commands", ApiUserRequired(listTeamCommands)).Methods("GET")
- sr.Handle("/regen_token", ApiUserRequired(regenCommandToken)).Methods("POST")
- sr.Handle("/delete", ApiUserRequired(deleteCommand)).Methods("POST")
+ BaseRoutes.Commands.Handle("/create", ApiUserRequired(createCommand)).Methods("POST")
+ BaseRoutes.Commands.Handle("/list_team_commands", ApiUserRequired(listTeamCommands)).Methods("GET")
+ BaseRoutes.Commands.Handle("/regen_token", ApiUserRequired(regenCommandToken)).Methods("POST")
+ BaseRoutes.Commands.Handle("/delete", ApiUserRequired(deleteCommand)).Methods("POST")
- sr.Handle("/test", ApiAppHandler(testCommand)).Methods("POST")
- sr.Handle("/test", ApiAppHandler(testCommand)).Methods("GET")
- sr.Handle("/test_e", ApiAppHandler(testEphemeralCommand)).Methods("POST")
- sr.Handle("/test_e", ApiAppHandler(testEphemeralCommand)).Methods("GET")
+ BaseRoutes.Teams.Handle("/command_test", ApiAppHandler(testCommand)).Methods("POST")
+ BaseRoutes.Teams.Handle("/command_test", ApiAppHandler(testCommand)).Methods("GET")
+ BaseRoutes.Teams.Handle("/command_test_e", ApiAppHandler(testEphemeralCommand)).Methods("POST")
+ BaseRoutes.Teams.Handle("/command_test_e", ApiAppHandler(testEphemeralCommand)).Methods("GET")
}
func listCommands(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -70,7 +68,7 @@ func listCommands(c *Context, w http.ResponseWriter, r *http.Request) {
}
if *utils.Cfg.ServiceSettings.EnableCommands {
- if result := <-Srv.Store.Command().GetByTeam(c.Session.TeamId); result.Err != nil {
+ if result := <-Srv.Store.Command().GetByTeam(c.TeamId); result.Err != nil {
c.Err = result.Err
return
} else {
@@ -99,7 +97,7 @@ func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) {
}
if len(channelId) > 0 {
- cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId)
+ cchan := Srv.Store.Channel().CheckPermissionsTo(c.TeamId, channelId, c.Session.UserId)
if !c.HasPermissionsToChannel(cchan, "checkCommand") {
return
@@ -124,10 +122,10 @@ func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) {
}
chanChan := Srv.Store.Channel().Get(channelId)
- teamChan := Srv.Store.Team().Get(c.Session.TeamId)
+ teamChan := Srv.Store.Team().Get(c.TeamId)
userChan := Srv.Store.User().Get(c.Session.UserId)
- if result := <-Srv.Store.Command().GetByTeam(c.Session.TeamId); result.Err != nil {
+ if result := <-Srv.Store.Command().GetByTeam(c.TeamId); result.Err != nil {
c.Err = result.Err
return
} else {
@@ -254,7 +252,7 @@ func handleResponse(c *Context, w http.ResponseWriter, response *model.CommandRe
post.Message = response.Text
post.CreateAt = model.GetMillis()
SendEphemeralPost(
- c.Session.TeamId,
+ c.TeamId,
c.Session.UserId,
post,
)
@@ -288,7 +286,7 @@ func createCommand(c *Context, w http.ResponseWriter, r *http.Request) {
}
cmd.CreatorId = c.Session.UserId
- cmd.TeamId = c.Session.TeamId
+ cmd.TeamId = c.TeamId
if result := <-Srv.Store.Command().Save(cmd); result.Err != nil {
c.Err = result.Err
@@ -315,7 +313,7 @@ func listTeamCommands(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
- if result := <-Srv.Store.Command().GetByTeam(c.Session.TeamId); result.Err != nil {
+ if result := <-Srv.Store.Command().GetByTeam(c.TeamId); result.Err != nil {
c.Err = result.Err
return
} else {
@@ -356,7 +354,7 @@ func regenCommandToken(c *Context, w http.ResponseWriter, r *http.Request) {
} else {
cmd = result.Data.(*model.Command)
- if c.Session.TeamId != cmd.TeamId || (c.Session.UserId != cmd.CreatorId && !c.IsTeamAdmin()) {
+ if c.TeamId != cmd.TeamId || (c.Session.UserId != cmd.CreatorId && !c.IsTeamAdmin()) {
c.LogAudit("fail - inappropriate permissions")
c.Err = model.NewLocAppError("regenToken", "api.command.regen.app_error", nil, "user_id="+c.Session.UserId)
return
@@ -402,7 +400,7 @@ func deleteCommand(c *Context, w http.ResponseWriter, r *http.Request) {
c.Err = result.Err
return
} else {
- if c.Session.TeamId != result.Data.(*model.Command).TeamId || (c.Session.UserId != result.Data.(*model.Command).CreatorId && !c.IsTeamAdmin()) {
+ if c.TeamId != result.Data.(*model.Command).TeamId || (c.Session.UserId != result.Data.(*model.Command).CreatorId && !c.IsTeamAdmin()) {
c.LogAudit("fail - inappropriate permissions")
c.Err = model.NewLocAppError("deleteCommand", "api.command.delete.app_error", nil, "user_id="+c.Session.UserId)
return
diff --git a/api/command_echo_test.go b/api/command_echo_test.go
index 3bfaa0279..26fba007c 100644
--- a/api/command_echo_test.go
+++ b/api/command_echo_test.go
@@ -8,23 +8,12 @@ import (
"time"
"github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
)
func TestEchoCommand(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel1 := th.BasicChannel
echoTestString := "/echo test"
@@ -36,7 +25,7 @@ func TestEchoCommand(t *testing.T) {
time.Sleep(100 * time.Millisecond)
p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList)
- if len(p1.Order) != 1 {
+ if len(p1.Order) != 2 {
t.Fatal("Echo command failed to send")
}
}
diff --git a/api/command_join.go b/api/command_join.go
index ba3b0041e..f59925c06 100644
--- a/api/command_join.go
+++ b/api/command_join.go
@@ -33,7 +33,7 @@ func (me *JoinProvider) GetCommand(c *Context) *model.Command {
}
func (me *JoinProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse {
- if result := <-Srv.Store.Channel().GetMoreChannels(c.Session.TeamId, c.Session.UserId); result.Err != nil {
+ if result := <-Srv.Store.Channel().GetMoreChannels(c.TeamId, c.Session.UserId); result.Err != nil {
return &model.CommandResponse{Text: c.T("api.command_join.list.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
} else {
channels := result.Data.(*model.ChannelList)
diff --git a/api/command_join_test.go b/api/command_join_test.go
index 7260915a6..2b4a5bfe3 100644
--- a/api/command_join_test.go
+++ b/api/command_join_test.go
@@ -8,20 +8,13 @@ import (
"testing"
"github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
)
func TestJoinCommands(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+ user2 := th.BasicUser2
channel0 := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel0 = Client.Must(Client.CreateChannel(channel0)).Data.(*model.Channel)
@@ -34,13 +27,7 @@ func TestJoinCommands(t *testing.T) {
channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
Client.Must(Client.LeaveChannel(channel2.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- data := make(map[string]string)
- data["user_id"] = user2.Id
- channel3 := Client.Must(Client.CreateDirectChannel(data)).Data.(*model.Channel)
+ channel3 := Client.Must(Client.CreateDirectChannel(user2.Id)).Data.(*model.Channel)
rs5 := Client.Must(Client.Command(channel0.Id, "/join "+channel2.Name, false)).Data.(*model.CommandResponse)
if !strings.HasSuffix(rs5.GotoLocation, "/"+team.Name+"/channels/"+channel2.Name) {
@@ -54,7 +41,7 @@ func TestJoinCommands(t *testing.T) {
c1 := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList)
- if len(c1.Channels) != 5 { // 4 because of town-square, off-topic and direct
+ if len(c1.Channels) != 6 { // 4 because of town-square, off-topic and direct
t.Fatal("didn't join channel")
}
diff --git a/api/command_loadtest.go b/api/command_loadtest.go
index 63598c06e..2738f4b51 100644
--- a/api/command_loadtest.go
+++ b/api/command_loadtest.go
@@ -182,10 +182,19 @@ func (me *LoadTestProvider) SetupCommand(c *Context, channelId string, message s
}
}
} else {
+
+ var team *model.Team
+ if tr := <-Srv.Store.Team().Get(c.TeamId); tr.Err != nil {
+ return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ } else {
+ team = tr.Data.(*model.Team)
+ }
+
client.MockSession(c.Session.Token)
+ client.SetTeamId(c.TeamId)
CreateTestEnvironmentInTeam(
client,
- c.Session.TeamId,
+ team,
utils.Range{numChannels, numChannels},
utils.Range{numUsers, numUsers},
utils.Range{numPosts, numPosts},
@@ -209,8 +218,16 @@ func (me *LoadTestProvider) UsersCommand(c *Context, channelId string, message s
usersr = utils.Range{2, 5}
}
+ var team *model.Team
+ if tr := <-Srv.Store.Team().Get(c.TeamId); tr.Err != nil {
+ return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ } else {
+ team = tr.Data.(*model.Team)
+ }
+
client := model.NewClient(c.GetSiteURL())
- userCreator := NewAutoUserCreator(client, c.Session.TeamId)
+ client.SetTeamId(team.Id)
+ userCreator := NewAutoUserCreator(client, team)
userCreator.Fuzzy = doFuzz
userCreator.CreateTestUsers(usersr)
@@ -230,9 +247,18 @@ func (me *LoadTestProvider) ChannelsCommand(c *Context, channelId string, messag
if err == false {
channelsr = utils.Range{2, 5}
}
+
+ var team *model.Team
+ if tr := <-Srv.Store.Team().Get(c.TeamId); tr.Err != nil {
+ return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ } else {
+ team = tr.Data.(*model.Team)
+ }
+
client := model.NewClient(c.GetSiteURL())
+ client.SetTeamId(team.Id)
client.MockSession(c.Session.Token)
- channelCreator := NewAutoChannelCreator(client, c.Session.TeamId)
+ channelCreator := NewAutoChannelCreator(client, team)
channelCreator.Fuzzy = doFuzz
channelCreator.CreateTestChannels(channelsr)
@@ -262,7 +288,7 @@ func (me *LoadTestProvider) PostsCommand(c *Context, channelId string, message s
}
var usernames []string
- if result := <-Srv.Store.User().GetProfiles(c.Session.TeamId); result.Err == nil {
+ if result := <-Srv.Store.User().GetProfiles(c.TeamId); result.Err == nil {
profileUsers := result.Data.(map[string]*model.User)
usernames = make([]string, len(profileUsers))
i := 0
@@ -273,6 +299,7 @@ func (me *LoadTestProvider) PostsCommand(c *Context, channelId string, message s
}
client := model.NewClient(c.GetSiteURL())
+ client.SetTeamId(c.TeamId)
client.MockSession(c.Session.Token)
testPoster := NewAutoPostCreator(client, channelId)
testPoster.Fuzzy = doFuzz
diff --git a/api/command_loadtest_test.go b/api/command_loadtest_test.go
index 4988050a1..8c138842e 100644
--- a/api/command_loadtest_test.go
+++ b/api/command_loadtest_test.go
@@ -9,12 +9,14 @@ import (
"time"
"github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
)
func TestLoadTestHelpCommands(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel := th.BasicChannel
+
// enable testing to use /loadtest but don't save it since we don't want to overwrite config.json
enableTesting := utils.Cfg.ServiceSettings.EnableTesting
defer func() {
@@ -23,18 +25,6 @@ func TestLoadTestHelpCommands(t *testing.T) {
utils.Cfg.ServiceSettings.EnableTesting = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel)
-
rs := Client.Must(Client.Command(channel.Id, "/loadtest help", false)).Data.(*model.CommandResponse)
if !strings.Contains(rs.Text, "Mattermost load testing commands to help") {
t.Fatal(rs.Text)
@@ -44,7 +34,10 @@ func TestLoadTestHelpCommands(t *testing.T) {
}
func TestLoadTestSetupCommands(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel := th.BasicChannel
+
// enable testing to use /loadtest but don't save it since we don't want to overwrite config.json
enableTesting := utils.Cfg.ServiceSettings.EnableTesting
defer func() {
@@ -53,18 +46,6 @@ func TestLoadTestSetupCommands(t *testing.T) {
utils.Cfg.ServiceSettings.EnableTesting = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel)
-
rs := Client.Must(Client.Command(channel.Id, "/loadtest setup fuzz 1 1 1", false)).Data.(*model.CommandResponse)
if rs.Text != "Creating enviroment..." {
t.Fatal(rs.Text)
@@ -74,7 +55,10 @@ func TestLoadTestSetupCommands(t *testing.T) {
}
func TestLoadTestUsersCommands(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel := th.BasicChannel
+
// enable testing to use /loadtest but don't save it since we don't want to overwrite config.json
enableTesting := utils.Cfg.ServiceSettings.EnableTesting
defer func() {
@@ -83,18 +67,6 @@ func TestLoadTestUsersCommands(t *testing.T) {
utils.Cfg.ServiceSettings.EnableTesting = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel)
-
rs := Client.Must(Client.Command(channel.Id, "/loadtest users fuzz 1 2", false)).Data.(*model.CommandResponse)
if rs.Text != "Adding users..." {
t.Fatal(rs.Text)
@@ -104,7 +76,10 @@ func TestLoadTestUsersCommands(t *testing.T) {
}
func TestLoadTestChannelsCommands(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel := th.BasicChannel
+
// enable testing to use /loadtest but don't save it since we don't want to overwrite config.json
enableTesting := utils.Cfg.ServiceSettings.EnableTesting
defer func() {
@@ -113,18 +88,6 @@ func TestLoadTestChannelsCommands(t *testing.T) {
utils.Cfg.ServiceSettings.EnableTesting = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel)
-
rs := Client.Must(Client.Command(channel.Id, "/loadtest channels fuzz 1 2", false)).Data.(*model.CommandResponse)
if rs.Text != "Adding channels..." {
t.Fatal(rs.Text)
@@ -134,7 +97,10 @@ func TestLoadTestChannelsCommands(t *testing.T) {
}
func TestLoadTestPostsCommands(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel := th.BasicChannel
+
// enable testing to use /loadtest but don't save it since we don't want to overwrite config.json
enableTesting := utils.Cfg.ServiceSettings.EnableTesting
defer func() {
@@ -143,18 +109,6 @@ func TestLoadTestPostsCommands(t *testing.T) {
utils.Cfg.ServiceSettings.EnableTesting = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel)
-
rs := Client.Must(Client.Command(channel.Id, "/loadtest posts fuzz 2 3 2", false)).Data.(*model.CommandResponse)
if rs.Text != "Adding posts..." {
t.Fatal(rs.Text)
@@ -164,7 +118,10 @@ func TestLoadTestPostsCommands(t *testing.T) {
}
func TestLoadTestUrlCommands(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel := th.BasicChannel
+
// enable testing to use /loadtest but don't save it since we don't want to overwrite config.json
enableTesting := utils.Cfg.ServiceSettings.EnableTesting
defer func() {
@@ -173,18 +130,6 @@ func TestLoadTestUrlCommands(t *testing.T) {
utils.Cfg.ServiceSettings.EnableTesting = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel)
-
command := "/loadtest url "
if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Command must contain a url" {
t.Fatal("/loadtest url with no url should've failed")
@@ -223,7 +168,10 @@ func TestLoadTestUrlCommands(t *testing.T) {
}
func TestLoadTestJsonCommands(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel := th.BasicChannel
+
// enable testing to use /loadtest but don't save it since we don't want to overwrite config.json
enableTesting := utils.Cfg.ServiceSettings.EnableTesting
defer func() {
@@ -232,18 +180,6 @@ func TestLoadTestJsonCommands(t *testing.T) {
utils.Cfg.ServiceSettings.EnableTesting = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel)
-
command := "/loadtest json "
if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Command must contain a url" {
t.Fatal("/loadtest url with no url should've failed")
@@ -255,9 +191,9 @@ func TestLoadTestJsonCommands(t *testing.T) {
t.Fatal("/loadtest url with invalid url should've failed")
}
- command = "/loadtest url https://secure.beldienst.nl/test.json" // Chicken-egg so will replace with mattermost/platform URL soon
+ command = "/loadtest json test-slack-attachments"
if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Loading data..." {
- t.Fatal("/loadtest url for README.md should've executed")
+ t.Fatal("/loadtest json should've executed")
}
time.Sleep(2 * time.Second)
diff --git a/api/command_logout_test.go b/api/command_logout_test.go
index eee7520a8..eec959115 100644
--- a/api/command_logout_test.go
+++ b/api/command_logout_test.go
@@ -8,25 +8,12 @@ import (
"testing"
"github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
)
func TestLogoutTestCommand(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
-
- rs1 := Client.Must(Client.Command(channel1.Id, "/logout", false)).Data.(*model.CommandResponse)
+ rs1 := th.BasicClient.Must(th.BasicClient.Command(th.BasicChannel.Id, "/logout", false)).Data.(*model.CommandResponse)
if !strings.HasSuffix(rs1.GotoLocation, "logout") {
t.Fatal("failed to logout")
}
diff --git a/api/command_me_test.go b/api/command_me_test.go
index d55a15b2c..f466f5764 100644
--- a/api/command_me_test.go
+++ b/api/command_me_test.go
@@ -8,35 +8,24 @@ import (
"time"
"github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
)
func TestMeCommand(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel := th.BasicChannel
testString := "/me hello"
- r1 := Client.Must(Client.Command(channel1.Id, testString, false)).Data.(*model.CommandResponse)
+ r1 := Client.Must(Client.Command(channel.Id, testString, false)).Data.(*model.CommandResponse)
if r1 == nil {
t.Fatal("Command failed to execute")
}
time.Sleep(100 * time.Millisecond)
- p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList)
- if len(p1.Order) != 1 {
+ p1 := Client.Must(Client.GetPosts(channel.Id, 0, 2, "")).Data.(*model.PostList)
+ if len(p1.Order) != 2 {
t.Fatal("Command failed to send")
} else {
if p1.Posts[p1.Order[0]].Message != `*hello*` {
diff --git a/api/command_msg.go b/api/command_msg.go
index 273a45be9..517999695 100644
--- a/api/command_msg.go
+++ b/api/command_msg.go
@@ -46,7 +46,7 @@ func (me *msgProvider) DoCommand(c *Context, channelId string, message string) *
targetUser = strings.SplitN(message, " ", 2)[0]
targetUser = strings.TrimPrefix(targetUser, "@")
- if profileList := <-Srv.Store.User().GetProfiles(c.Session.TeamId); profileList.Err != nil {
+ if profileList := <-Srv.Store.User().GetProfiles(c.TeamId); profileList.Err != nil {
c.Err = profileList.Err
return &model.CommandResponse{Text: c.T("api.command_msg.list.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
} else {
@@ -62,7 +62,7 @@ func (me *msgProvider) DoCommand(c *Context, channelId string, message string) *
//Find the channel based on this user
channelName := model.GetDMNameFromIds(c.Session.UserId, userProfile.Id)
- if channel := <-Srv.Store.Channel().GetByName(c.Session.TeamId, channelName); channel.Err != nil {
+ if channel := <-Srv.Store.Channel().GetByName(c.TeamId, channelName); channel.Err != nil {
if channel.Err.Id == "store.sql_channel.get_by_name.missing.app_error" {
if directChannel, err := CreateDirectChannel(c, userProfile.Id); err != nil {
c.Err = err
@@ -78,7 +78,7 @@ func (me *msgProvider) DoCommand(c *Context, channelId string, message string) *
targetChannelId = channel.Data.(*model.Channel).Id
}
- makeDirectChannelVisible(c.Session.TeamId, targetChannelId)
+ makeDirectChannelVisible(c.TeamId, targetChannelId)
if len(parsedMessage) > 0 {
post := &model.Post{}
post.Message = parsedMessage
@@ -87,9 +87,11 @@ func (me *msgProvider) DoCommand(c *Context, channelId string, message string) *
return &model.CommandResponse{Text: c.T("api.command_msg.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
}
+
return &model.CommandResponse{GotoLocation: c.GetTeamURL() + "/channels/" + channelName, Text: c.T("api.command_msg.success"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
}
}
+
return &model.CommandResponse{Text: c.T("api.command_msg.missing.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
diff --git a/api/command_msg_test.go b/api/command_msg_test.go
index 222a401fd..db8c3216c 100644
--- a/api/command_msg_test.go
+++ b/api/command_msg_test.go
@@ -4,39 +4,29 @@
package api
import (
+ "github.com/mattermost/platform/model"
"strings"
"testing"
-
- "github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
)
func TestMsgCommands(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "success+test@simulator.amazonses.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Username: "user1", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test2@simulator.amazonses.com", Nickname: "Corey Hulen 2", Username: "user2", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
-
- user3 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test3@simulator.amazonses.com", Nickname: "Corey Hulen 3", Username: "user3", Password: "pwd"}
- user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user3.Id))
-
- rs1 := Client.Must(Client.Command("", "/msg user2", false)).Data.(*model.CommandResponse)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+ user1 := th.BasicUser
+ user2 := th.BasicUser2
+ user3 := th.CreateUser(th.BasicClient)
+ LinkUserToTeam(user3, team)
+
+ Client.Must(Client.CreateDirectChannel(user2.Id))
+ Client.Must(Client.CreateDirectChannel(user3.Id))
+
+ rs1 := Client.Must(Client.Command("", "/msg "+user2.Username, false)).Data.(*model.CommandResponse)
if !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user2.Id) && !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+user2.Id+"__"+user1.Id) {
t.Fatal("failed to create direct channel")
}
- rs2 := Client.Must(Client.Command("", "/msg user3 foobar", false)).Data.(*model.CommandResponse)
+ rs2 := Client.Must(Client.Command("", "/msg "+user3.Username+" foobar", false)).Data.(*model.CommandResponse)
if !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user3.Id) && !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+user3.Id+"__"+user1.Id) {
t.Fatal("failed to create second direct channel")
}
@@ -44,7 +34,7 @@ func TestMsgCommands(t *testing.T) {
t.Fatalf("post did not get sent to direct message")
}
- rs3 := Client.Must(Client.Command("", "/msg user2", false)).Data.(*model.CommandResponse)
+ rs3 := Client.Must(Client.Command("", "/msg "+user2.Username, false)).Data.(*model.CommandResponse)
if !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user2.Id) && !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+user2.Id+"__"+user1.Id) {
t.Fatal("failed to go back to existing direct channel")
}
diff --git a/api/command_shrug_test.go b/api/command_shrug_test.go
index 92cecf664..99c10d191 100644
--- a/api/command_shrug_test.go
+++ b/api/command_shrug_test.go
@@ -8,35 +8,24 @@ import (
"time"
"github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
)
func TestShrugCommand(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel := th.BasicChannel
testString := "/shrug"
- r1 := Client.Must(Client.Command(channel1.Id, testString, false)).Data.(*model.CommandResponse)
+ r1 := Client.Must(Client.Command(channel.Id, testString, false)).Data.(*model.CommandResponse)
if r1 == nil {
t.Fatal("Command failed to execute")
}
time.Sleep(100 * time.Millisecond)
- p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList)
- if len(p1.Order) != 1 {
+ p1 := Client.Must(Client.GetPosts(channel.Id, 0, 2, "")).Data.(*model.PostList)
+ if len(p1.Order) != 2 {
t.Fatal("Command failed to send")
} else {
if p1.Posts[p1.Order[0]].Message != `¯\\\_(ツ)\_/¯` {
diff --git a/api/command_test.go b/api/command_test.go
index 22e2bd666..c6500c6cf 100644
--- a/api/command_test.go
+++ b/api/command_test.go
@@ -8,21 +8,12 @@ import (
"time"
"github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
)
func TestListCommands(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
+ th := Setup().InitBasic()
+ Client := th.BasicClient
if results, err := Client.ListCommands(); err != nil {
t.Fatal(err)
@@ -43,7 +34,10 @@ func TestListCommands(t *testing.T) {
}
func TestCreateCommand(t *testing.T) {
- Setup()
+ th := Setup().InitBasic().InitSystemAdmin()
+ Client := th.BasicClient
+ user := th.SystemAdminUser
+ team := th.SystemAdminTeam
enableCommands := *utils.Cfg.ServiceSettings.EnableCommands
defer func() {
@@ -51,26 +45,13 @@ func TestCreateCommand(t *testing.T) {
}()
*utils.Cfg.ServiceSettings.EnableCommands = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST}
+ cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST, Trigger: "trigger"}
if _, err := Client.CreateCommand(cmd); err == nil {
t.Fatal("should have failed because not admin")
}
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ Client = th.SystemAdminClient
var rcmd *model.Command
if result, err := Client.CreateCommand(cmd); err != nil {
@@ -87,7 +68,7 @@ func TestCreateCommand(t *testing.T) {
t.Fatal("team ids didn't match")
}
- cmd = &model.Command{CreatorId: "123", TeamId: "456", URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST}
+ cmd = &model.Command{CreatorId: "123", TeamId: "456", URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST, Trigger: "trigger"}
if result, err := Client.CreateCommand(cmd); err != nil {
t.Fatal(err)
} else {
@@ -101,27 +82,16 @@ func TestCreateCommand(t *testing.T) {
}
func TestListTeamCommands(t *testing.T) {
- Setup()
+ th := Setup().InitSystemAdmin()
+ Client := th.SystemAdminClient
+
enableCommands := *utils.Cfg.ServiceSettings.EnableCommands
defer func() {
utils.Cfg.ServiceSettings.EnableCommands = &enableCommands
}()
*utils.Cfg.ServiceSettings.EnableCommands = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- cmd1 := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST}
+ cmd1 := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST, Trigger: "trigger"}
cmd1 = Client.Must(Client.CreateCommand(cmd1)).Data.(*model.Command)
if result, err := Client.ListTeamCommands(); err != nil {
@@ -136,27 +106,16 @@ func TestListTeamCommands(t *testing.T) {
}
func TestRegenToken(t *testing.T) {
- Setup()
+ th := Setup().InitSystemAdmin()
+ Client := th.SystemAdminClient
+
enableCommands := *utils.Cfg.ServiceSettings.EnableCommands
defer func() {
utils.Cfg.ServiceSettings.EnableCommands = &enableCommands
}()
*utils.Cfg.ServiceSettings.EnableCommands = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST}
+ cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST, Trigger: "trigger"}
cmd = Client.Must(Client.CreateCommand(cmd)).Data.(*model.Command)
data := make(map[string]string)
@@ -172,27 +131,16 @@ func TestRegenToken(t *testing.T) {
}
func TestDeleteCommand(t *testing.T) {
- Setup()
+ th := Setup().InitSystemAdmin()
+ Client := th.SystemAdminClient
+
enableCommands := *utils.Cfg.ServiceSettings.EnableCommands
defer func() {
utils.Cfg.ServiceSettings.EnableCommands = &enableCommands
}()
*utils.Cfg.ServiceSettings.EnableCommands = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST}
+ cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST, Trigger: "trigger"}
cmd = Client.Must(Client.CreateCommand(cmd)).Data.(*model.Command)
data := make(map[string]string)
@@ -209,31 +157,18 @@ func TestDeleteCommand(t *testing.T) {
}
func TestTestCommand(t *testing.T) {
- Setup()
+ th := Setup().InitSystemAdmin()
+ Client := th.SystemAdminClient
+ channel1 := th.SystemAdminChannel
+
enableCommands := *utils.Cfg.ServiceSettings.EnableCommands
defer func() {
utils.Cfg.ServiceSettings.EnableCommands = &enableCommands
}()
*utils.Cfg.ServiceSettings.EnableCommands = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
-
cmd1 := &model.Command{
- URL: "http://localhost" + utils.Cfg.ServiceSettings.ListenAddress + "/api/v1/commands/test",
+ URL: "http://localhost" + utils.Cfg.ServiceSettings.ListenAddress + model.API_URL_SUFFIX + "/teams/command_test",
Method: model.COMMAND_METHOD_POST,
Trigger: "test",
}
@@ -253,7 +188,7 @@ func TestTestCommand(t *testing.T) {
}
cmd2 := &model.Command{
- URL: "http://localhost" + utils.Cfg.ServiceSettings.ListenAddress + "/api/v1/commands/test",
+ URL: "http://localhost" + utils.Cfg.ServiceSettings.ListenAddress + model.API_URL_SUFFIX + "/teams/command_test",
Method: model.COMMAND_METHOD_GET,
Trigger: "test2",
}
diff --git a/api/context.go b/api/context.go
index 56c8c86d2..8bbd5a1d2 100644
--- a/api/context.go
+++ b/api/context.go
@@ -11,10 +11,12 @@ import (
"strings"
l4g "github.com/alecthomas/log4go"
+ "github.com/gorilla/mux"
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
- goi18n "github.com/nicksnyder/go-i18n/i18n"
)
var sessionCache *utils.Cache = utils.NewLru(model.SESSION_CACHE_SIZE)
@@ -39,6 +41,7 @@ type Context struct {
siteURL string
T goi18n.TranslateFunc
Locale string
+ TeamId string
}
func ApiAppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
@@ -94,6 +97,8 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c.T, c.Locale = utils.GetTranslationsAndLocale(w, r)
c.RequestId = model.NewId()
c.IpAddress = GetIpAddress(r)
+ c.TeamId = mux.Vars(r)["team_id"]
+ h.isApi = IsApiCall(r)
token := ""
isTokenFromQueryString := false
@@ -116,7 +121,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if (h.requireSystemAdmin || h.requireUser) && !h.trustRequester {
if r.Header.Get(model.HEADER_REQUESTED_WITH) != model.HEADER_REQUESTED_WITH_XML {
- c.Err = model.NewLocAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token)
+ c.Err = model.NewLocAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token+" Appears to bea CSRF attempt")
token = ""
}
}
@@ -182,6 +187,10 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c.SystemAdminRequired()
}
+ if c.Err == nil && len(c.TeamId) > 0 {
+ c.HasPermissionsToTeam(c.TeamId, "TeamRoute")
+ }
+
if c.Err == nil && h.isUserActivity && token != "" && len(c.Session.UserId) > 0 {
go func() {
if err := (<-Srv.Store.User().UpdateUserAndSessionActivity(c.Session.UserId, c.Session.Id, model.GetMillis())).Err; err != nil {
@@ -308,13 +317,14 @@ func (c *Context) HasPermissionsToUser(userId string, where string) bool {
}
func (c *Context) HasPermissionsToTeam(teamId string, where string) bool {
- if c.Session.TeamId == teamId {
+ if c.IsSystemAdmin() {
return true
}
- // You're a mattermost system admin and you're on the VPN
- if c.IsSystemAdmin() {
- return true
+ for _, teamMember := range c.Session.TeamMembers {
+ if teamId == teamMember.TeamId {
+ return true
+ }
}
c.Err = model.NewLocAppError(where, "api.context.permissions.app_error", nil, "userId="+c.Session.UserId+", teamId="+teamId)
@@ -353,10 +363,17 @@ func (c *Context) IsSystemAdmin() bool {
}
func (c *Context) IsTeamAdmin() bool {
- if model.IsInRole(c.Session.Roles, model.ROLE_TEAM_ADMIN) || c.IsSystemAdmin() {
+
+ if c.IsSystemAdmin() {
return true
}
- return false
+
+ team := c.Session.GetTeamByTeamId(c.TeamId)
+ if team == nil {
+ return false
+ }
+
+ return model.IsInRole(team.Roles, model.ROLE_TEAM_ADMIN)
}
func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) {
@@ -386,7 +403,7 @@ func (c *Context) setTeamURL(url string, valid bool) {
}
func (c *Context) SetTeamURLFromSession() {
- if result := <-Srv.Store.Team().Get(c.Session.TeamId); result.Err == nil {
+ if result := <-Srv.Store.Team().Get(c.TeamId); result.Err == nil {
c.setTeamURL(c.GetSiteURL()+"/"+result.Data.(*model.Team).Name, true)
}
}
@@ -413,6 +430,10 @@ func (c *Context) GetSiteURL() string {
return c.siteURL
}
+func IsApiCall(r *http.Request) bool {
+ return strings.Index(r.URL.Path, "/api/") == 0
+}
+
func GetIpAddress(r *http.Request) string {
address := r.Header.Get(model.HEADER_FORWARDED)
@@ -427,69 +448,69 @@ func GetIpAddress(r *http.Request) string {
return address
}
-func IsTestDomain(r *http.Request) bool {
+// func IsTestDomain(r *http.Request) bool {
- if strings.Index(r.Host, "localhost") == 0 {
- return true
- }
+// if strings.Index(r.Host, "localhost") == 0 {
+// return true
+// }
- if strings.Index(r.Host, "dockerhost") == 0 {
- return true
- }
+// if strings.Index(r.Host, "dockerhost") == 0 {
+// return true
+// }
- if strings.Index(r.Host, "test") == 0 {
- return true
- }
+// if strings.Index(r.Host, "test") == 0 {
+// return true
+// }
- if strings.Index(r.Host, "127.0.") == 0 {
- return true
- }
+// if strings.Index(r.Host, "127.0.") == 0 {
+// return true
+// }
- if strings.Index(r.Host, "192.168.") == 0 {
- return true
- }
+// if strings.Index(r.Host, "192.168.") == 0 {
+// return true
+// }
- if strings.Index(r.Host, "10.") == 0 {
- return true
- }
+// if strings.Index(r.Host, "10.") == 0 {
+// return true
+// }
- if strings.Index(r.Host, "176.") == 0 {
- return true
- }
+// if strings.Index(r.Host, "176.") == 0 {
+// return true
+// }
- return false
-}
+// return false
+// }
-func IsBetaDomain(r *http.Request) bool {
+// func IsBetaDomain(r *http.Request) bool {
- if strings.Index(r.Host, "beta") == 0 {
- return true
- }
+// if strings.Index(r.Host, "beta") == 0 {
+// return true
+// }
- if strings.Index(r.Host, "ci") == 0 {
- return true
- }
+// if strings.Index(r.Host, "ci") == 0 {
+// return true
+// }
- return false
-}
+// return false
+// }
-var privateIpAddress = []*net.IPNet{
- {IP: net.IPv4(10, 0, 0, 1), Mask: net.IPv4Mask(255, 0, 0, 0)},
- {IP: net.IPv4(176, 16, 0, 1), Mask: net.IPv4Mask(255, 255, 0, 0)},
- {IP: net.IPv4(192, 168, 0, 1), Mask: net.IPv4Mask(255, 255, 255, 0)},
- {IP: net.IPv4(127, 0, 0, 1), Mask: net.IPv4Mask(255, 255, 255, 252)},
-}
+// var privateIpAddress = []*net.IPNet{
+// {IP: net.IPv4(10, 0, 0, 1), Mask: net.IPv4Mask(255, 0, 0, 0)},
+// {IP: net.IPv4(176, 16, 0, 1), Mask: net.IPv4Mask(255, 255, 0, 0)},
+// {IP: net.IPv4(192, 168, 0, 1), Mask: net.IPv4Mask(255, 255, 255, 0)},
+// {IP: net.IPv4(127, 0, 0, 1), Mask: net.IPv4Mask(255, 255, 255, 252)},
+// }
-func IsPrivateIpAddress(ipAddress string) bool {
+// func IsPrivateIpAddress(ipAddress string) bool {
- for _, pips := range privateIpAddress {
- if pips.Contains(net.ParseIP(ipAddress)) {
- return true
- }
- }
+// for _, pips := range privateIpAddress {
+// if pips.Contains(net.ParseIP(ipAddress)) {
+// return true
+// }
+// }
- return false
-}
+// return false
+// }
func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request) {
T, _ := utils.GetTranslationsAndLocale(w, r)
@@ -513,9 +534,17 @@ func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request)
func Handle404(w http.ResponseWriter, r *http.Request) {
err := model.NewLocAppError("Handle404", "api.context.404.app_error", nil, "")
+ err.Translate(utils.T)
err.StatusCode = http.StatusNotFound
l4g.Error("%v: code=404 ip=%v", r.URL.Path, GetIpAddress(r))
- RenderWebError(err, w, r)
+
+ if IsApiCall(r) {
+ w.WriteHeader(err.StatusCode)
+ err.DetailedError = "There doesn't appear to be an api call for the url='" + r.URL.Path + "'. Typo? are you missing a team_id or user_id as part of the url?"
+ w.Write([]byte(err.ToJson()))
+ } else {
+ RenderWebError(err, w, r)
+ }
}
func GetSession(token string) *model.Session {
@@ -542,6 +571,20 @@ func GetSession(token string) *model.Session {
return session
}
+func RemoveAllSessionsForUserId(userId string) {
+
+ keys := sessionCache.Keys()
+
+ for _, key := range keys {
+ if ts, ok := sessionCache.Get(key); ok {
+ session := ts.(*model.Session)
+ if session.UserId == userId {
+ sessionCache.Remove(key)
+ }
+ }
+ }
+}
+
func AddSessionToCache(session *model.Session) {
sessionCache.AddWithExpiresInSecs(session.Token, session, int64(*utils.Cfg.ServiceSettings.SessionCacheInMinutes*60))
}
diff --git a/api/context_test.go b/api/context_test.go
index a9e2afa0f..c3c7a9768 100644
--- a/api/context_test.go
+++ b/api/context_test.go
@@ -8,32 +8,6 @@ import (
"testing"
)
-var ipAddressTests = []struct {
- address string
- expected bool
-}{
- {"126.255.255.255", false},
- {"127.0.0.1", true},
- {"127.0.0.4", false},
- {"9.255.255.255", false},
- {"10.0.0.1", true},
- {"11.0.0.1", false},
- {"176.15.155.255", false},
- {"176.16.0.1", true},
- {"176.31.0.1", false},
- {"192.167.255.255", false},
- {"192.168.0.1", true},
- {"192.169.0.1", false},
-}
-
-func TestIpAddress(t *testing.T) {
- for _, v := range ipAddressTests {
- if IsPrivateIpAddress(v.address) != v.expected {
- t.Errorf("expect %v as %v", v.address, v.expected)
- }
- }
-}
-
func TestContext(t *testing.T) {
context := Context{}
@@ -52,9 +26,26 @@ func TestContext(t *testing.T) {
if !context.HasPermissionsToUser("6", "") {
t.Fatal("should have permissions")
}
+}
- // context.IpAddress = "125.0.0.1"
- // if context.HasPermissionsToUser("6", "") {
- // t.Fatal("shouldn't have permissions")
- // }
+func TestCache(t *testing.T) {
+ session := &model.Session{
+ Id: model.NewId(),
+ Token: model.NewId(),
+ UserId: model.NewId(),
+ }
+
+ sessionCache.AddWithExpiresInSecs(session.Token, session, 5*60)
+
+ keys := sessionCache.Keys()
+ if len(keys) <= 0 {
+ t.Fatal("should have items")
+ }
+
+ RemoveAllSessionsForUserId(session.UserId)
+
+ rkeys := sessionCache.Keys()
+ if len(rkeys) != len(keys)-1 {
+ t.Fatal("should have one less")
+ }
}
diff --git a/api/export.go b/api/export.go
index f2f8f87ab..da066379f 100644
--- a/api/export.go
+++ b/api/export.go
@@ -60,7 +60,7 @@ func ExportToFile(options *ExportOptions) (link string, err *model.AppError) {
ExportToWriter(file, options)
}
- return "/api/v1/files/get_export", nil
+ return model.API_URL_SUFFIX + "/files/get_export", nil
}
func ExportToWriter(w io.Writer, options *ExportOptions) *model.AppError {
diff --git a/api/file.go b/api/file.go
index 991516bed..c51a4a046 100644
--- a/api/file.go
+++ b/api/file.go
@@ -57,15 +57,14 @@ const (
var fileInfoCache *utils.Cache = utils.NewLru(1000)
-func InitFile(r *mux.Router) {
+func InitFile() {
l4g.Debug(utils.T("api.file.init.debug"))
- sr := r.PathPrefix("/files").Subrouter()
- sr.Handle("/upload", ApiUserRequired(uploadFile)).Methods("POST")
- sr.Handle("/get/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:([A-Za-z0-9]+/)?.+(\\.[A-Za-z0-9]{3,})?}", ApiAppHandlerTrustRequester(getFile)).Methods("GET")
- sr.Handle("/get_info/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:([A-Za-z0-9]+/)?.+(\\.[A-Za-z0-9]{3,})?}", ApiAppHandler(getFileInfo)).Methods("GET")
- sr.Handle("/get_public_link", ApiUserRequired(getPublicLink)).Methods("POST")
- sr.Handle("/get_export", ApiUserRequired(getExport)).Methods("GET")
+ BaseRoutes.Files.Handle("/upload", ApiUserRequired(uploadFile)).Methods("POST")
+ BaseRoutes.Files.Handle("/get/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:([A-Za-z0-9]+/)?.+(\\.[A-Za-z0-9]{3,})?}", ApiAppHandlerTrustRequester(getFile)).Methods("GET")
+ BaseRoutes.Files.Handle("/get_info/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:([A-Za-z0-9]+/)?.+(\\.[A-Za-z0-9]{3,})?}", ApiAppHandler(getFileInfo)).Methods("GET")
+ BaseRoutes.Files.Handle("/get_public_link", ApiUserRequired(getPublicLink)).Methods("POST")
+ BaseRoutes.Files.Handle("/get_export", ApiUserRequired(getExport)).Methods("GET")
}
func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -101,7 +100,7 @@ func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId)
+ cchan := Srv.Store.Channel().CheckPermissionsTo(c.TeamId, channelId, c.Session.UserId)
files := m.File["files"]
@@ -147,7 +146,7 @@ func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
- path := "teams/" + c.Session.TeamId + "/channels/" + channelId + "/users/" + c.Session.UserId + "/" + uid + "/" + filename
+ path := "teams/" + c.TeamId + "/channels/" + channelId + "/users/" + c.Session.UserId + "/" + uid + "/" + filename
if err := WriteFile(buf.Bytes(), path); err != nil {
c.Err = err
@@ -164,7 +163,7 @@ func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) {
resStruct.ClientIds = append(resStruct.ClientIds, clientId)
}
- handleImagesAndForget(imageNameList, imageDataList, c.Session.TeamId, channelId, c.Session.UserId)
+ handleImagesAndForget(imageNameList, imageDataList, c.TeamId, channelId, c.Session.UserId)
w.Write([]byte(resStruct.ToJson()))
}
@@ -319,9 +318,9 @@ func getFileInfo(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId)
+ cchan := Srv.Store.Channel().CheckPermissionsTo(c.TeamId, channelId, c.Session.UserId)
- path := "teams/" + c.Session.TeamId + "/channels/" + channelId + "/users/" + userId + "/" + filename
+ path := "teams/" + c.TeamId + "/channels/" + channelId + "/users/" + userId + "/" + filename
var info *model.FileInfo
if cached, ok := fileInfoCache.Get(path); ok {
@@ -380,13 +379,13 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
data := r.URL.Query().Get("d")
teamId := r.URL.Query().Get("t")
- cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId)
+ cchan := Srv.Store.Channel().CheckPermissionsTo(c.TeamId, channelId, c.Session.UserId)
path := ""
if len(teamId) == 26 {
path = "teams/" + teamId + "/channels/" + channelId + "/users/" + userId + "/" + filename
} else {
- path = "teams/" + c.Session.TeamId + "/channels/" + channelId + "/users/" + userId + "/" + filename
+ path = "teams/" + c.TeamId + "/channels/" + channelId + "/users/" + userId + "/" + filename
}
fileData := make(chan []byte)
@@ -460,6 +459,7 @@ func getPublicLink(c *Context, w http.ResponseWriter, r *http.Request) {
if !utils.Cfg.FileSettings.EnablePublicLink {
c.Err = model.NewLocAppError("getPublicLink", "api.file.get_public_link.disabled.app_error", nil, "")
c.Err.StatusCode = http.StatusForbidden
+ return
}
props := model.MapFromJson(r.Body)
@@ -480,7 +480,7 @@ func getPublicLink(c *Context, w http.ResponseWriter, r *http.Request) {
userId := matches[0][2]
filename = matches[0][3]
- cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId)
+ cchan := Srv.Store.Channel().CheckPermissionsTo(c.TeamId, channelId, c.Session.UserId)
newProps := make(map[string]string)
newProps["filename"] = filename
@@ -488,7 +488,7 @@ func getPublicLink(c *Context, w http.ResponseWriter, r *http.Request) {
data := model.MapToJson(newProps)
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.FileSettings.PublicLinkSalt))
- url := fmt.Sprintf("%s/api/v1/files/get/%s/%s/%s?d=%s&h=%s&t=%s", c.GetSiteURL(), channelId, userId, filename, url.QueryEscape(data), url.QueryEscape(hash), c.Session.TeamId)
+ url := fmt.Sprintf("%s/files/get/%s/%s/%s?d=%s&h=%s&t=%s", c.GetSiteURL()+model.API_URL_SUFFIX, channelId, userId, filename, url.QueryEscape(data), url.QueryEscape(hash), c.TeamId)
if !c.HasPermissionsToChannel(cchan, "getPublicLink") {
return
@@ -501,7 +501,7 @@ func getPublicLink(c *Context, w http.ResponseWriter, r *http.Request) {
}
func getExport(c *Context, w http.ResponseWriter, r *http.Request) {
- if !c.HasPermissionsToTeam(c.Session.TeamId, "export") || !c.IsTeamAdmin() {
+ if !c.HasPermissionsToTeam(c.TeamId, "export") || !c.IsTeamAdmin() {
c.Err = model.NewLocAppError("getExport", "api.file.get_export.team_admin.app_error", nil, "userId="+c.Session.UserId)
c.Err.StatusCode = http.StatusForbidden
return
diff --git a/api/file_benchmark_test.go b/api/file_benchmark_test.go
index a02bffa0e..d73097072 100644
--- a/api/file_benchmark_test.go
+++ b/api/file_benchmark_test.go
@@ -13,7 +13,9 @@ import (
)
func BenchmarkUploadFile(b *testing.B) {
- _, _, channel := SetupBenchmark()
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel := th.BasicChannel
testPoster := NewAutoPostCreator(Client, channel.Id)
@@ -25,7 +27,10 @@ func BenchmarkUploadFile(b *testing.B) {
}
func BenchmarkGetFile(b *testing.B) {
- team, _, channel := SetupBenchmark()
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+ channel := th.BasicChannel
testPoster := NewAutoPostCreator(Client, channel.Id)
filenames, err := testPoster.UploadTestFile()
@@ -53,7 +58,9 @@ func BenchmarkGetFile(b *testing.B) {
}
func BenchmarkGetPublicLink(b *testing.B) {
- _, _, channel := SetupBenchmark()
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel := th.BasicChannel
testPoster := NewAutoPostCreator(Client, channel.Id)
filenames, err := testPoster.UploadTestFile()
diff --git a/api/file_test.go b/api/file_test.go
index 3aa1a56f9..dd4a8520b 100644
--- a/api/file_test.go
+++ b/api/file_test.go
@@ -22,19 +22,11 @@ import (
)
func TestUploadFile(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+ user := th.BasicUser
+ channel := th.BasicChannel
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
@@ -45,6 +37,9 @@ func TestUploadFile(t *testing.T) {
path := utils.FindDir("tests")
file, err := os.Open(path + "/test.png")
+ if err != nil {
+ t.Fatal(err)
+ }
defer file.Close()
_, err = io.Copy(part, file)
@@ -57,7 +52,7 @@ func TestUploadFile(t *testing.T) {
t.Fatal(err)
}
- _, err = field.Write([]byte(channel1.Id))
+ _, err = field.Write([]byte(channel.Id))
if err != nil {
t.Fatal(err)
}
@@ -67,7 +62,7 @@ func TestUploadFile(t *testing.T) {
t.Fatal(err)
}
- resp, appErr := Client.UploadFile("/files/upload", body.Bytes(), writer.FormDataContentType())
+ resp, appErr := Client.UploadPostAttachment(body.Bytes(), writer.FormDataContentType())
if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_S3 {
if appErr != nil {
t.Fatal(appErr)
@@ -90,17 +85,17 @@ func TestUploadFile(t *testing.T) {
// wait a bit for files to ready
time.Sleep(5 * time.Second)
- err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + filename)
+ err = bucket.Del("teams/" + team.Id + "/channels/" + channel.Id + "/users/" + user.Id + "/" + filename)
if err != nil {
t.Fatal(err)
}
- err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_thumb.jpg")
+ err = bucket.Del("teams/" + team.Id + "/channels/" + channel.Id + "/users/" + user.Id + "/" + fileId + "_thumb.jpg")
if err != nil {
t.Fatal(err)
}
- err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_preview.jpg")
+ err = bucket.Del("teams/" + team.Id + "/channels/" + channel.Id + "/users/" + user.Id + "/" + fileId + "_preview.jpg")
if err != nil {
t.Fatal(err)
}
@@ -115,17 +110,17 @@ func TestUploadFile(t *testing.T) {
// wait a bit for files to ready
time.Sleep(5 * time.Second)
- path := utils.Cfg.FileSettings.Directory + "teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + filename
+ path := utils.Cfg.FileSettings.Directory + "teams/" + team.Id + "/channels/" + channel.Id + "/users/" + user.Id + "/" + filename
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
- path = utils.Cfg.FileSettings.Directory + "teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_thumb.jpg"
+ path = utils.Cfg.FileSettings.Directory + "teams/" + team.Id + "/channels/" + channel.Id + "/users/" + user.Id + "/" + fileId + "_thumb.jpg"
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
- path = utils.Cfg.FileSettings.Directory + "teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_preview.jpg"
+ path = utils.Cfg.FileSettings.Directory + "teams/" + team.Id + "/channels/" + channel.Id + "/users/" + user.Id + "/" + fileId + "_preview.jpg"
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
@@ -137,25 +132,18 @@ func TestUploadFile(t *testing.T) {
}
func TestGetFile(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+ user := th.BasicUser
+ channel := th.BasicChannel
+
enablePublicLink := utils.Cfg.FileSettings.EnablePublicLink
defer func() {
utils.Cfg.FileSettings.EnablePublicLink = enablePublicLink
}()
utils.Cfg.FileSettings.EnablePublicLink = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
-
if utils.Cfg.FileSettings.DriverName != "" {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
@@ -181,7 +169,7 @@ func TestGetFile(t *testing.T) {
t.Fatal(err)
}
- _, err = field.Write([]byte(channel1.Id))
+ _, err = field.Write([]byte(channel.Id))
if err != nil {
t.Fatal(err)
}
@@ -191,7 +179,7 @@ func TestGetFile(t *testing.T) {
t.Fatal(err)
}
- resp, upErr := Client.UploadFile("/files/upload", body.Bytes(), writer.FormDataContentType())
+ resp, upErr := Client.UploadPostAttachment(body.Bytes(), writer.FormDataContentType())
if upErr != nil {
t.Fatal(upErr)
}
@@ -217,8 +205,9 @@ func TestGetFile(t *testing.T) {
team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team)
- user2 := &model.User{TeamId: team2.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
+ LinkUserToTeam(user2, team2)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
newProps := make(map[string]string)
@@ -229,6 +218,7 @@ func TestGetFile(t *testing.T) {
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.FileSettings.PublicLinkSalt))
Client.LoginByEmail(team2.Name, user2.Email, "pwd")
+ Client.SetTeamId(team2.Id)
if _, downErr := Client.GetFile(filenames[0]+"?d="+url.QueryEscape(data)+"&h="+url.QueryEscape(hash)+"&t="+team.Id, false); downErr != nil {
t.Fatal(downErr)
@@ -278,17 +268,17 @@ func TestGetFile(t *testing.T) {
filename := filenames[len(filenames)-2] + "/" + filenames[len(filenames)-1]
fileId := strings.Split(filename, ".")[0]
- err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + filename)
+ err = bucket.Del("teams/" + team.Id + "/channels/" + channel.Id + "/users/" + user.Id + "/" + filename)
if err != nil {
t.Fatal(err)
}
- err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_thumb.jpg")
+ err = bucket.Del("teams/" + team.Id + "/channels/" + channel.Id + "/users/" + user.Id + "/" + fileId + "_thumb.jpg")
if err != nil {
t.Fatal(err)
}
- err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_preview.jpg")
+ err = bucket.Del("teams/" + team.Id + "/channels/" + channel.Id + "/users/" + user.Id + "/" + fileId + "_preview.jpg")
if err != nil {
t.Fatal(err)
}
@@ -297,17 +287,17 @@ func TestGetFile(t *testing.T) {
filename := filenames[len(filenames)-2] + "/" + filenames[len(filenames)-1]
fileId := strings.Split(filename, ".")[0]
- path := utils.Cfg.FileSettings.Directory + "teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + filename
+ path := utils.Cfg.FileSettings.Directory + "teams/" + team.Id + "/channels/" + channel.Id + "/users/" + user.Id + "/" + filename
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
- path = utils.Cfg.FileSettings.Directory + "teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_thumb.jpg"
+ path = utils.Cfg.FileSettings.Directory + "teams/" + team.Id + "/channels/" + channel.Id + "/users/" + user.Id + "/" + fileId + "_thumb.jpg"
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
- path = utils.Cfg.FileSettings.Directory + "teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_preview.jpg"
+ path = utils.Cfg.FileSettings.Directory + "teams/" + team.Id + "/channels/" + channel.Id + "/users/" + user.Id + "/" + fileId + "_preview.jpg"
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
@@ -320,25 +310,18 @@ func TestGetFile(t *testing.T) {
}
func TestGetPublicLink(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+ user := th.BasicUser
+ channel := th.BasicChannel
if utils.Cfg.FileSettings.DriverName != "" {
+ enablePublicLink := utils.Cfg.FileSettings.EnablePublicLink
+ defer func() {
+ utils.Cfg.FileSettings.EnablePublicLink = enablePublicLink
+ }()
+ utils.Cfg.FileSettings.EnablePublicLink = true
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
@@ -364,7 +347,7 @@ func TestGetPublicLink(t *testing.T) {
t.Fatal(err)
}
- _, err = field.Write([]byte(channel1.Id))
+ _, err = field.Write([]byte(channel.Id))
if err != nil {
t.Fatal(err)
}
@@ -374,14 +357,14 @@ func TestGetPublicLink(t *testing.T) {
t.Fatal(err)
}
- resp, upErr := Client.UploadFile("/files/upload", body.Bytes(), writer.FormDataContentType())
+ resp, upErr := Client.UploadPostAttachment(body.Bytes(), writer.FormDataContentType())
if upErr != nil {
t.Fatal(upErr)
}
filenames := resp.Data.(*model.FileUploadResponse).Filenames
- post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a", Filenames: filenames}
+ post1 := &model.Post{ChannelId: channel.Id, Message: "a" + model.NewId() + "a", Filenames: filenames}
rpost1, postErr := Client.CreatePost(post1)
if postErr != nil {
@@ -408,7 +391,8 @@ func TestGetPublicLink(t *testing.T) {
t.Fatal("Should have errored - bad file path")
}
- Client.LoginByEmail(team.Name, user2.Email, "pwd")
+ th.LoginBasic2()
+
data["filename"] = filenames[0]
if _, err := Client.GetPublicLink(data); err == nil {
t.Fatal("should have errored, user not member of channel")
@@ -427,17 +411,17 @@ func TestGetPublicLink(t *testing.T) {
filename := filenames[len(filenames)-2] + "/" + filenames[len(filenames)-1]
fileId := strings.Split(filename, ".")[0]
- err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + filename)
+ err = bucket.Del("teams/" + team.Id + "/channels/" + channel.Id + "/users/" + user.Id + "/" + filename)
if err != nil {
t.Fatal(err)
}
- err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_thumb.jpg")
+ err = bucket.Del("teams/" + team.Id + "/channels/" + channel.Id + "/users/" + user.Id + "/" + fileId + "_thumb.jpg")
if err != nil {
t.Fatal(err)
}
- err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_preview.jpg")
+ err = bucket.Del("teams/" + team.Id + "/channels/" + channel.Id + "/users/" + user.Id + "/" + fileId + "_preview.jpg")
if err != nil {
t.Fatal(err)
}
@@ -446,17 +430,17 @@ func TestGetPublicLink(t *testing.T) {
filename := filenames[len(filenames)-2] + "/" + filenames[len(filenames)-1]
fileId := strings.Split(filename, ".")[0]
- path := utils.Cfg.FileSettings.Directory + "teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + filename
+ path := utils.Cfg.FileSettings.Directory + "teams/" + team.Id + "/channels/" + channel.Id + "/users/" + user.Id + "/" + filename
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
- path = utils.Cfg.FileSettings.Directory + "teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_thumb.jpg"
+ path = utils.Cfg.FileSettings.Directory + "teams/" + team.Id + "/channels/" + channel.Id + "/users/" + user.Id + "/" + fileId + "_thumb.jpg"
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
- path = utils.Cfg.FileSettings.Directory + "teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_preview.jpg"
+ path = utils.Cfg.FileSettings.Directory + "teams/" + team.Id + "/channels/" + channel.Id + "/users/" + user.Id + "/" + fileId + "_preview.jpg"
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
diff --git a/api/import.go b/api/import.go
index 7590277b0..c39ec5220 100644
--- a/api/import.go
+++ b/api/import.go
@@ -22,7 +22,7 @@ func ImportPost(post *model.Post) {
}
}
-func ImportUser(user *model.User) *model.User {
+func ImportUser(teamId string, user *model.User) *model.User {
user.MakeNonNil()
if result := <-Srv.Store.User().Save(user); result.Err != nil {
@@ -31,8 +31,8 @@ func ImportUser(user *model.User) *model.User {
} else {
ruser := result.Data.(*model.User)
- if err := JoinDefaultChannels(ruser, ""); err != nil {
- l4g.Error(utils.T("api.import.import_user.joining_default.error"), ruser.Id, ruser.TeamId, err)
+ if err := JoinDefaultChannels(teamId, ruser, ""); err != nil {
+ l4g.Error(utils.T("api.import.import_user.joining_default.error"), ruser.Id, teamId, err)
}
if cresult := <-Srv.Store.User().VerifyEmail(ruser.Id); cresult.Err != nil {
diff --git a/api/license.go b/api/license.go
index 4bf8cd3b8..1dbb2b281 100644
--- a/api/license.go
+++ b/api/license.go
@@ -6,7 +6,6 @@ package api
import (
"bytes"
l4g "github.com/alecthomas/log4go"
- "github.com/gorilla/mux"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"io"
@@ -19,13 +18,12 @@ const (
INVALID_LICENSE_ERROR = "api.license.add_license.invalid.app_error"
)
-func InitLicense(r *mux.Router) {
+func InitLicense() {
l4g.Debug(utils.T("api.license.init.debug"))
- sr := r.PathPrefix("/license").Subrouter()
- sr.Handle("/add", ApiAdminSystemRequired(addLicense)).Methods("POST")
- sr.Handle("/remove", ApiAdminSystemRequired(removeLicense)).Methods("POST")
- sr.Handle("/client_config", ApiAppHandler(getClientLicenceConfig)).Methods("GET")
+ BaseRoutes.License.Handle("/add", ApiAdminSystemRequired(addLicense)).Methods("POST")
+ BaseRoutes.License.Handle("/remove", ApiAdminSystemRequired(removeLicense)).Methods("POST")
+ BaseRoutes.License.Handle("/client_config", ApiAppHandler(getClientLicenceConfig)).Methods("GET")
}
func LoadLicense() {
diff --git a/api/license_test.go b/api/license_test.go
index 0126d6e54..c5fffd6e9 100644
--- a/api/license_test.go
+++ b/api/license_test.go
@@ -9,7 +9,8 @@ import (
)
func TestGetLicenceConfig(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ Client := th.BasicClient
if result, err := Client.GetClientLicenceConfig(""); err != nil {
t.Fatal(err)
diff --git a/api/oauth.go b/api/oauth.go
index a7119d7e5..0375f4e6f 100644
--- a/api/oauth.go
+++ b/api/oauth.go
@@ -4,7 +4,10 @@
package api
import (
+ "crypto/tls"
+ b64 "encoding/base64"
"fmt"
+ "io"
"net/http"
"net/url"
"strconv"
@@ -12,31 +15,29 @@ import (
l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux"
+ "github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
)
-func InitOAuth(r *mux.Router) {
+func InitOAuth() {
l4g.Debug(utils.T("api.oauth.init.debug"))
- sr := r.PathPrefix("/oauth").Subrouter()
+ BaseRoutes.OAuth.Handle("/register", ApiUserRequired(registerOAuthApp)).Methods("POST")
+ BaseRoutes.OAuth.Handle("/allow", ApiUserRequired(allowOAuth)).Methods("GET")
+ BaseRoutes.OAuth.Handle("/{service:[A-Za-z]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET")
+ BaseRoutes.OAuth.Handle("/{service:[A-Za-z]+}/login", AppHandlerIndependent(loginWithOAuth)).Methods("GET")
+ BaseRoutes.OAuth.Handle("/{service:[A-Za-z]+}/signup", AppHandlerIndependent(signupWithOAuth)).Methods("GET")
+ BaseRoutes.OAuth.Handle("/authorize", ApiUserRequired(authorizeOAuth)).Methods("GET")
+ BaseRoutes.OAuth.Handle("/access_token", ApiAppHandler(getAccessToken)).Methods("POST")
- sr.Handle("/register", ApiUserRequired(registerOAuthApp)).Methods("POST")
- sr.Handle("/allow", ApiUserRequired(allowOAuth)).Methods("GET")
- sr.Handle("/{service:[A-Za-z]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET")
- sr.Handle("/{service:[A-Za-z]+}/login", AppHandlerIndependent(loginWithOAuth)).Methods("GET")
- sr.Handle("/{service:[A-Za-z]+}/signup", AppHandlerIndependent(signupWithOAuth)).Methods("GET")
- sr.Handle("/authorize", ApiUserRequired(authorizeOAuth)).Methods("GET")
- sr.Handle("/access_token", ApiAppHandler(getAccessToken)).Methods("POST")
-
- mr := Srv.Router
- mr.Handle("/authorize", ApiUserRequired(authorizeOAuth)).Methods("GET")
- mr.Handle("/access_token", ApiAppHandler(getAccessToken)).Methods("POST")
+ BaseRoutes.Root.Handle("/authorize", ApiUserRequired(authorizeOAuth)).Methods("GET")
+ BaseRoutes.Root.Handle("/access_token", ApiAppHandler(getAccessToken)).Methods("POST")
// Handle all the old routes, to be later removed
- mr.Handle("/{service:[A-Za-z]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET")
- mr.Handle("/signup/{service:[A-Za-z]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET")
- mr.Handle("/login/{service:[A-Za-z]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET")
+ BaseRoutes.Root.Handle("/{service:[A-Za-z]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET")
+ BaseRoutes.Root.Handle("/signup/{service:[A-Za-z]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET")
+ BaseRoutes.Root.Handle("/login/{service:[A-Za-z]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET")
}
func registerOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -190,40 +191,40 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
uri := c.GetSiteURL() + "/signup/" + service + "/complete"
- if body, team, props, err := AuthorizeOAuthUser(service, code, state, uri); err != nil {
+ if body, teamId, props, err := AuthorizeOAuthUser(service, code, state, uri); err != nil {
c.Err = err
return
} else {
action := props["action"]
switch action {
case model.OAUTH_ACTION_SIGNUP:
- CreateOAuthUser(c, w, r, service, body, team)
+ CreateOAuthUser(c, w, r, service, body, teamId)
if c.Err == nil {
- http.Redirect(w, r, GetProtocol(r)+"://"+r.Host+"/"+team.Name, http.StatusTemporaryRedirect)
+ http.Redirect(w, r, GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
}
break
case model.OAUTH_ACTION_LOGIN:
- LoginByOAuth(c, w, r, service, body, team)
+ LoginByOAuth(c, w, r, service, body)
if c.Err == nil {
- http.Redirect(w, r, GetProtocol(r)+"://"+r.Host+"/"+team.Name, http.StatusTemporaryRedirect)
+ http.Redirect(w, r, GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
}
break
case model.OAUTH_ACTION_EMAIL_TO_SSO:
- CompleteSwitchWithOAuth(c, w, r, service, body, team, props["email"])
+ CompleteSwitchWithOAuth(c, w, r, service, body, props["email"])
if c.Err == nil {
- http.Redirect(w, r, GetProtocol(r)+"://"+r.Host+"/"+team.Name+"/login?extra=signin_change", http.StatusTemporaryRedirect)
+ http.Redirect(w, r, GetProtocol(r)+"://"+r.Host+"/login?extra=signin_change", http.StatusTemporaryRedirect)
}
break
case model.OAUTH_ACTION_SSO_TO_EMAIL:
- LoginByOAuth(c, w, r, service, body, team)
+ LoginByOAuth(c, w, r, service, body)
if c.Err == nil {
- http.Redirect(w, r, GetProtocol(r)+"://"+r.Host+"/"+team.Name+"/"+"/claim?email="+url.QueryEscape(props["email"]), http.StatusTemporaryRedirect)
+ http.Redirect(w, r, GetProtocol(r)+"://"+r.Host+"/claim?email="+url.QueryEscape(props["email"]), http.StatusTemporaryRedirect)
}
break
default:
- LoginByOAuth(c, w, r, service, body, team)
+ LoginByOAuth(c, w, r, service, body)
if c.Err == nil {
- http.Redirect(w, r, GetProtocol(r)+"://"+r.Host+"/"+team.Name, http.StatusTemporaryRedirect)
+ http.Redirect(w, r, GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
}
break
}
@@ -257,7 +258,7 @@ func authorizeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
}
var team *model.Team
- if result := <-Srv.Store.Team().Get(c.Session.TeamId); result.Err != nil {
+ if result := <-Srv.Store.Team().Get(c.TeamId); result.Err != nil {
c.Err = result.Err
return
} else {
@@ -389,7 +390,7 @@ func getAccessToken(c *Context, w http.ResponseWriter, r *http.Request) {
user = result.Data.(*model.User)
}
- session := &model.Session{UserId: user.Id, TeamId: user.TeamId, Roles: user.Roles, IsOAuth: true}
+ session := &model.Session{UserId: user.Id, Roles: user.Roles, IsOAuth: true}
if result := <-Srv.Store.Session().Save(session); result.Err != nil {
c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.internal_session.app_error", nil, "")
@@ -422,24 +423,11 @@ func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
service := params["service"]
loginHint := r.URL.Query().Get("login_hint")
- teamName := r.URL.Query().Get("team")
-
- if len(teamName) == 0 {
- c.Err = model.NewLocAppError("loginWithOAuth", "web.login_with_oauth.invalid_team.app_error", nil, "team_name="+teamName)
- c.Err.StatusCode = http.StatusBadRequest
- return
- }
-
- // Make sure team exists
- if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil {
- c.Err = result.Err
- return
- }
stateProps := map[string]string{}
stateProps["action"] = model.OAUTH_ACTION_LOGIN
- if authUrl, err := GetAuthorizationCode(c, service, teamName, stateProps, loginHint); err != nil {
+ if authUrl, err := GetAuthorizationCode(c, service, stateProps, loginHint); err != nil {
c.Err = err
return
} else {
@@ -450,31 +438,19 @@ func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
service := params["service"]
- teamName := r.URL.Query().Get("team")
if !utils.Cfg.TeamSettings.EnableUserCreation {
- c.Err = model.NewLocAppError("signupTeam", "web.singup_with_oauth.disabled.app_error", nil, "")
+ c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.disabled.app_error", nil, "")
c.Err.StatusCode = http.StatusNotImplemented
return
}
- if len(teamName) == 0 {
- c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.invalid_team.app_error", nil, "team_name="+teamName)
- c.Err.StatusCode = http.StatusBadRequest
- return
- }
-
hash := r.URL.Query().Get("h")
- var team *model.Team
- if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- team = result.Data.(*model.Team)
- }
+ teamId := ""
+ inviteId := r.URL.Query().Get("id")
- if IsVerifyHashRequired(nil, team, hash) {
+ if len(hash) > 0 {
data := r.URL.Query().Get("d")
props := model.MapFromJson(strings.NewReader(data))
@@ -489,19 +465,173 @@ func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if team.Id != props["id"] {
- c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.invalid_team.app_error", nil, data)
- return
+ teamId = props["id"]
+ } else if len(inviteId) != 0 {
+ if result := <-Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil {
+ // soft fail, so we still create user but don't auto-join team
+ l4g.Error("%v", result.Err)
+ } else {
+ teamId = result.Data.(*model.Team).Id
}
}
stateProps := map[string]string{}
stateProps["action"] = model.OAUTH_ACTION_SIGNUP
+ if len(teamId) != 0 {
+ stateProps["team_id"] = teamId
+ }
- if authUrl, err := GetAuthorizationCode(c, service, teamName, stateProps, ""); err != nil {
+ if authUrl, err := GetAuthorizationCode(c, service, stateProps, ""); err != nil {
c.Err = err
return
} else {
http.Redirect(w, r, authUrl, http.StatusFound)
}
}
+
+func GetAuthorizationCode(c *Context, service string, props map[string]string, loginHint string) (string, *model.AppError) {
+
+ sso := utils.Cfg.GetSSOService(service)
+ if sso != nil && !sso.Enable {
+ return "", model.NewLocAppError("GetAuthorizationCode", "api.user.get_authorization_code.unsupported.app_error", nil, "service="+service)
+ }
+
+ clientId := sso.Id
+ endpoint := sso.AuthEndpoint
+ scope := sso.Scope
+
+ props["hash"] = model.HashPassword(clientId)
+ state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(props)))
+
+ redirectUri := c.GetSiteURL() + "/signup/" + service + "/complete"
+
+ authUrl := endpoint + "?response_type=code&client_id=" + clientId + "&redirect_uri=" + url.QueryEscape(redirectUri) + "&state=" + url.QueryEscape(state)
+
+ if len(scope) > 0 {
+ authUrl += "&scope=" + utils.UrlEncode(scope)
+ }
+
+ if len(loginHint) > 0 {
+ authUrl += "&login_hint=" + utils.UrlEncode(loginHint)
+ }
+
+ return authUrl, nil
+}
+
+func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser, string, map[string]string, *model.AppError) {
+ sso := utils.Cfg.GetSSOService(service)
+ if sso == nil || !sso.Enable {
+ return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.unsupported.app_error", nil, "service="+service)
+ }
+
+ stateStr := ""
+ if b, err := b64.StdEncoding.DecodeString(state); err != nil {
+ return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, err.Error())
+ } else {
+ stateStr = string(b)
+ }
+
+ stateProps := model.MapFromJson(strings.NewReader(stateStr))
+
+ if !model.ComparePassword(stateProps["hash"], sso.Id) {
+ return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "")
+ }
+
+ teamId := stateProps["team_id"]
+
+ p := url.Values{}
+ p.Set("client_id", sso.Id)
+ p.Set("client_secret", sso.Secret)
+ p.Set("code", code)
+ p.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE)
+ p.Set("redirect_uri", redirectUri)
+
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections},
+ }
+ client := &http.Client{Transport: tr}
+ req, _ := http.NewRequest("POST", sso.TokenEndpoint, strings.NewReader(p.Encode()))
+
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ req.Header.Set("Accept", "application/json")
+
+ var ar *model.AccessResponse
+ if resp, err := client.Do(req); err != nil {
+ return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, err.Error())
+ } else {
+ ar = model.AccessResponseFromJson(resp.Body)
+ if ar == nil {
+ return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_response.app_error", nil, "")
+ }
+ }
+
+ if strings.ToLower(ar.TokenType) != model.ACCESS_TOKEN_TYPE {
+ return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_token.app_error", nil, "token_type="+ar.TokenType)
+ }
+
+ if len(ar.AccessToken) == 0 {
+ return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.missing.app_error", nil, "")
+ }
+
+ p = url.Values{}
+ p.Set("access_token", ar.AccessToken)
+ req, _ = http.NewRequest("GET", sso.UserApiEndpoint, strings.NewReader(""))
+
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ req.Header.Set("Accept", "application/json")
+ req.Header.Set("Authorization", "Bearer "+ar.AccessToken)
+
+ if resp, err := client.Do(req); err != nil {
+ return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error",
+ map[string]interface{}{"Service": service}, err.Error())
+ } else {
+ return resp.Body, teamId, stateProps, nil
+ }
+
+}
+
+func CompleteSwitchWithOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.ReadCloser, email string) {
+ authData := ""
+ ssoEmail := ""
+ provider := einterfaces.GetOauthProvider(service)
+ if provider == nil {
+ c.Err = model.NewLocAppError("CompleteClaimWithOAuth", "api.user.complete_switch_with_oauth.unavailable.app_error",
+ map[string]interface{}{"Service": service}, "")
+ return
+ } else {
+ ssoUser := provider.GetUserFromJson(userData)
+ authData = ssoUser.AuthData
+ ssoEmail = ssoUser.Email
+ }
+
+ if len(authData) == 0 {
+ c.Err = model.NewLocAppError("CompleteClaimWithOAuth", "api.user.complete_switch_with_oauth.parse.app_error",
+ map[string]interface{}{"Service": service}, "")
+ return
+ }
+
+ if len(email) == 0 {
+ c.Err = model.NewLocAppError("CompleteClaimWithOAuth", "api.user.complete_switch_with_oauth.blank_email.app_error", nil, "")
+ return
+ }
+
+ var user *model.User
+ if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ user = result.Data.(*model.User)
+ }
+
+ RevokeAllSession(c, user.Id)
+ if c.Err != nil {
+ return
+ }
+
+ if result := <-Srv.Store.User().UpdateAuthData(user.Id, service, authData, ssoEmail); result.Err != nil {
+ c.Err = result.Err
+ return
+ }
+
+ sendSignInChangeEmailAndForget(c, user.Email, c.GetSiteURL(), strings.Title(service)+" SSO")
+}
diff --git a/api/oauth_test.go b/api/oauth_test.go
index 57772ccc5..aa3c025a7 100644
--- a/api/oauth_test.go
+++ b/api/oauth_test.go
@@ -5,22 +5,14 @@ package api
import (
"github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
"net/url"
- "strings"
"testing"
)
func TestRegisterApp(t *testing.T) {
- Setup()
-
- team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- rteam, _ := Client.CreateTeam(&team)
-
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Password: "pwd"}
- ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
+ th := Setup().InitBasic()
+ Client := th.BasicClient
app := &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
@@ -38,7 +30,7 @@ func TestRegisterApp(t *testing.T) {
t.Fatal("not logged in - should have failed")
}
- Client.Must(Client.LoginById(ruser.Id, "pwd"))
+ th.LoginBasic()
if result, err := Client.RegisterApp(app); err != nil {
t.Fatal(err)
@@ -70,19 +62,11 @@ func TestRegisterApp(t *testing.T) {
}
func TestAllowOAuth(t *testing.T) {
- Setup()
-
- team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- rteam, _ := Client.CreateTeam(&team)
-
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Password: "pwd"}
- ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
+ th := Setup().InitBasic()
+ Client := th.BasicClient
app := &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
- Client.Must(Client.LoginById(ruser.Id, "pwd"))
-
state := "123"
if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
diff --git a/api/post.go b/api/post.go
index 6f88c815b..cbfbf49f2 100644
--- a/api/post.go
+++ b/api/post.go
@@ -22,21 +22,21 @@ import (
"time"
)
-func InitPost(r *mux.Router) {
+func InitPost() {
l4g.Debug(utils.T("api.post.init.debug"))
- r.Handle("/posts/search", ApiUserRequired(searchPosts)).Methods("GET")
- r.Handle("/posts/{post_id}", ApiUserRequired(getPostById)).Methods("GET")
-
- sr := r.PathPrefix("/channels/{id:[A-Za-z0-9]+}").Subrouter()
- sr.Handle("/create", ApiUserRequired(createPost)).Methods("POST")
- sr.Handle("/update", ApiUserRequired(updatePost)).Methods("POST")
- sr.Handle("/posts/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequiredActivity(getPosts, false)).Methods("GET")
- sr.Handle("/posts/{time:[0-9]+}", ApiUserRequiredActivity(getPostsSince, false)).Methods("GET")
- sr.Handle("/post/{post_id:[A-Za-z0-9]+}", ApiUserRequired(getPost)).Methods("GET")
- sr.Handle("/post/{post_id:[A-Za-z0-9]+}/delete", ApiUserRequired(deletePost)).Methods("POST")
- sr.Handle("/post/{post_id:[A-Za-z0-9]+}/before/{offset:[0-9]+}/{num_posts:[0-9]+}", ApiUserRequired(getPostsBefore)).Methods("GET")
- sr.Handle("/post/{post_id:[A-Za-z0-9]+}/after/{offset:[0-9]+}/{num_posts:[0-9]+}", ApiUserRequired(getPostsAfter)).Methods("GET")
+ BaseRoutes.NeedTeam.Handle("/posts/search", ApiUserRequired(searchPosts)).Methods("GET")
+ BaseRoutes.NeedTeam.Handle("/posts/{post_id}", ApiUserRequired(getPostById)).Methods("GET")
+
+ BaseRoutes.Posts.Handle("/create", ApiUserRequired(createPost)).Methods("POST")
+ BaseRoutes.Posts.Handle("/update", ApiUserRequired(updatePost)).Methods("POST")
+ BaseRoutes.Posts.Handle("/page/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequiredActivity(getPosts, false)).Methods("GET")
+ BaseRoutes.Posts.Handle("/since/{time:[0-9]+}", ApiUserRequiredActivity(getPostsSince, false)).Methods("GET")
+
+ BaseRoutes.NeedPost.Handle("/get", ApiUserRequired(getPost)).Methods("GET")
+ BaseRoutes.NeedPost.Handle("/delete", ApiUserRequired(deletePost)).Methods("POST")
+ BaseRoutes.NeedPost.Handle("/before/{offset:[0-9]+}/{num_posts:[0-9]+}", ApiUserRequired(getPostsBefore)).Methods("GET")
+ BaseRoutes.NeedPost.Handle("/after/{offset:[0-9]+}/{num_posts:[0-9]+}", ApiUserRequired(getPostsAfter)).Methods("GET")
}
func createPost(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -47,7 +47,7 @@ func createPost(c *Context, w http.ResponseWriter, r *http.Request) {
}
// Create and save post object to channel
- cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, post.ChannelId, c.Session.UserId)
+ cchan := Srv.Store.Channel().CheckPermissionsTo(c.TeamId, post.ChannelId, c.Session.UserId)
if !c.HasPermissionsToChannel(cchan, "createPost") {
return
@@ -228,15 +228,16 @@ func CreateWebhookPost(c *Context, channelId, text, overrideUsername, overrideIc
func handlePostEventsAndForget(c *Context, post *model.Post, triggerWebhooks bool) {
go func() {
- tchan := Srv.Store.Team().Get(c.Session.TeamId)
+ tchan := Srv.Store.Team().Get(c.TeamId)
cchan := Srv.Store.Channel().Get(post.ChannelId)
uchan := Srv.Store.User().Get(post.UserId)
- pchan := Srv.Store.User().GetProfiles(c.Session.TeamId)
+ pchan := Srv.Store.User().GetProfiles(c.TeamId)
+ dpchan := Srv.Store.User().GetDirectProfiles(c.Session.UserId)
mchan := Srv.Store.Channel().GetMembers(post.ChannelId)
var team *model.Team
if result := <-tchan; result.Err != nil {
- l4g.Error(utils.T("api.post.handle_post_events_and_forget.team.error"), c.Session.TeamId, result.Err)
+ l4g.Error(utils.T("api.post.handle_post_events_and_forget.team.error"), c.TeamId, result.Err)
return
} else {
team = result.Data.(*model.Team)
@@ -252,12 +253,22 @@ func handlePostEventsAndForget(c *Context, post *model.Post, triggerWebhooks boo
var profiles map[string]*model.User
if result := <-pchan; result.Err != nil {
- l4g.Error(utils.T("api.post.handle_post_events_and_forget.profiles.error"), c.Session.TeamId, result.Err)
+ l4g.Error(utils.T("api.post.handle_post_events_and_forget.profiles.error"), c.TeamId, result.Err)
return
} else {
profiles = result.Data.(map[string]*model.User)
}
+ if result := <-dpchan; result.Err != nil {
+ l4g.Error(utils.T("api.post.handle_post_events_and_forget.profiles.error"), c.TeamId, result.Err)
+ return
+ } else {
+ dps := result.Data.(map[string]*model.User)
+ for k, v := range dps {
+ profiles[k] = v
+ }
+ }
+
var members []model.ChannelMember
if result := <-mchan; result.Err != nil {
l4g.Error(utils.T("api.post.handle_post_events_and_forget.members.error"), post.ChannelId, result.Err)
@@ -282,7 +293,7 @@ func handlePostEventsAndForget(c *Context, post *model.Post, triggerWebhooks boo
}
if channel.Type == model.CHANNEL_DIRECT {
- go makeDirectChannelVisible(c.Session.TeamId, post.ChannelId)
+ go makeDirectChannelVisible(c.TeamId, post.ChannelId)
}
}()
}
@@ -352,7 +363,7 @@ func handleWebhookEventsAndForget(c *Context, post *model.Post, team *model.Team
return
}
- hchan := Srv.Store.Webhook().GetOutgoingByTeam(c.Session.TeamId)
+ hchan := Srv.Store.Webhook().GetOutgoingByTeam(c.TeamId)
hooks := []*model.OutgoingWebhook{}
@@ -416,8 +427,25 @@ func handleWebhookEventsAndForget(c *Context, post *model.Post, team *model.Team
respProps := model.MapFromJson(resp.Body)
// copy the context and create a mock session for posting the message
- mockSession := model.Session{UserId: hook.CreatorId, TeamId: hook.TeamId, IsOAuth: false}
- newContext := &Context{mockSession, model.NewId(), "", c.Path, nil, c.teamURLValid, c.teamURL, c.siteURL, c.T, c.Locale}
+ mockSession := model.Session{
+ UserId: hook.CreatorId,
+ TeamMembers: []*model.TeamMember{{TeamId: hook.TeamId, UserId: hook.CreatorId}},
+ IsOAuth: false,
+ }
+
+ newContext := &Context{
+ Session: mockSession,
+ RequestId: model.NewId(),
+ IpAddress: "",
+ Path: c.Path,
+ Err: nil,
+ teamURLValid: c.teamURLValid,
+ teamURL: c.teamURL,
+ siteURL: c.siteURL,
+ T: c.T,
+ Locale: c.Locale,
+ TeamId: hook.TeamId,
+ }
if text, ok := respProps["text"]; ok {
if _, err := CreateWebhookPost(newContext, post.ChannelId, text, respProps["username"], respProps["icon_url"], post.Props, post.Type); err != nil {
@@ -706,7 +734,7 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections},
}
httpClient := &http.Client{Transport: tr}
- request, _ := http.NewRequest("POST", *utils.Cfg.EmailSettings.PushNotificationServer+"/api/v1/send_push", strings.NewReader(msg.ToJson()))
+ request, _ := http.NewRequest("POST", *utils.Cfg.EmailSettings.PushNotificationServer+model.API_URL_SUFFIX_V1+"/send_push", strings.NewReader(msg.ToJson()))
l4g.Debug(utils.T("api.post.send_notifications_and_forget.push_notification.debug"), msg.DeviceId, msg.Message)
if _, err := httpClient.Do(request); err != nil {
@@ -719,7 +747,7 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
}
}
- message := model.NewMessage(c.Session.TeamId, post.ChannelId, post.UserId, model.ACTION_POSTED)
+ message := model.NewMessage(c.TeamId, post.ChannelId, post.UserId, model.ACTION_POSTED)
message.Add("post", post.ToJson())
message.Add("channel_type", channel.Type)
@@ -780,7 +808,7 @@ func checkForOutOfChannelMentions(c *Context, post *model.Post, channel *model.C
}
SendEphemeralPost(
- c.Session.TeamId,
+ c.TeamId,
post.UserId,
&model.Post{
ChannelId: post.ChannelId,
@@ -847,7 +875,7 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, post.ChannelId, c.Session.UserId)
+ cchan := Srv.Store.Channel().CheckPermissionsTo(c.TeamId, post.ChannelId, c.Session.UserId)
pchan := Srv.Store.Post().Get(post.Id)
if !c.HasPermissionsToChannel(cchan, "updatePost") {
@@ -889,7 +917,7 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
} else {
rpost := result.Data.(*model.Post)
- message := model.NewMessage(c.Session.TeamId, rpost.ChannelId, c.Session.UserId, model.ACTION_POST_EDITED)
+ message := model.NewMessage(c.TeamId, rpost.ChannelId, c.Session.UserId, model.ACTION_POST_EDITED)
message.Add("post", rpost.ToJson())
PublishAndForget(message)
@@ -901,7 +929,7 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
func getPosts(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
- id := params["id"]
+ id := params["channel_id"]
if len(id) != 26 {
c.SetInvalidParam("getPosts", "channelId")
return
@@ -919,7 +947,7 @@ func getPosts(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, id, c.Session.UserId)
+ cchan := Srv.Store.Channel().CheckPermissionsTo(c.TeamId, id, c.Session.UserId)
etagChan := Srv.Store.Post().GetEtag(id)
if !c.HasPermissionsToChannel(cchan, "getPosts") {
@@ -949,7 +977,7 @@ func getPosts(c *Context, w http.ResponseWriter, r *http.Request) {
func getPostsSince(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
- id := params["id"]
+ id := params["channel_id"]
if len(id) != 26 {
c.SetInvalidParam("getPostsSince", "channelId")
return
@@ -961,7 +989,7 @@ func getPostsSince(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, id, c.Session.UserId)
+ cchan := Srv.Store.Channel().CheckPermissionsTo(c.TeamId, id, c.Session.UserId)
pchan := Srv.Store.Post().GetPostsSince(id, time)
if !c.HasPermissionsToChannel(cchan, "getPostsSince") {
@@ -982,7 +1010,7 @@ func getPostsSince(c *Context, w http.ResponseWriter, r *http.Request) {
func getPost(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
- channelId := params["id"]
+ channelId := params["channel_id"]
if len(channelId) != 26 {
c.SetInvalidParam("getPost", "channelId")
return
@@ -994,7 +1022,7 @@ func getPost(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId)
+ cchan := Srv.Store.Channel().CheckPermissionsTo(c.TeamId, channelId, c.Session.UserId)
pchan := Srv.Store.Post().Get(postId)
if !c.HasPermissionsToChannel(cchan, "getPost") {
@@ -1041,7 +1069,7 @@ func getPostById(c *Context, w http.ResponseWriter, r *http.Request) {
}
post := list.Posts[list.Order[0]]
- cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, post.ChannelId, c.Session.UserId)
+ cchan := Srv.Store.Channel().CheckPermissionsTo(c.TeamId, post.ChannelId, c.Session.UserId)
if !c.HasPermissionsToChannel(cchan, "getPostById") {
return
}
@@ -1058,7 +1086,7 @@ func getPostById(c *Context, w http.ResponseWriter, r *http.Request) {
func deletePost(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
- channelId := params["id"]
+ channelId := params["channel_id"]
if len(channelId) != 26 {
c.SetInvalidParam("deletePost", "channelId")
return
@@ -1070,7 +1098,7 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId)
+ cchan := Srv.Store.Channel().CheckPermissionsTo(c.TeamId, channelId, c.Session.UserId)
pchan := Srv.Store.Post().Get(postId)
if result := <-pchan; result.Err != nil {
@@ -1106,11 +1134,11 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- message := model.NewMessage(c.Session.TeamId, post.ChannelId, c.Session.UserId, model.ACTION_POST_DELETED)
+ message := model.NewMessage(c.TeamId, post.ChannelId, c.Session.UserId, model.ACTION_POST_DELETED)
message.Add("post", post.ToJson())
PublishAndForget(message)
- DeletePostFilesAndForget(c.Session.TeamId, post)
+ DeletePostFilesAndForget(c.TeamId, post)
result := make(map[string]string)
result["id"] = postId
@@ -1146,7 +1174,7 @@ func getPostsAfter(c *Context, w http.ResponseWriter, r *http.Request) {
func getPostsBeforeOrAfter(c *Context, w http.ResponseWriter, r *http.Request, before bool) {
params := mux.Vars(r)
- id := params["id"]
+ id := params["channel_id"]
if len(id) != 26 {
c.SetInvalidParam("getPostsBeforeOrAfter", "channelId")
return
@@ -1170,7 +1198,7 @@ func getPostsBeforeOrAfter(c *Context, w http.ResponseWriter, r *http.Request, b
return
}
- cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, id, c.Session.UserId)
+ cchan := Srv.Store.Channel().CheckPermissionsTo(c.TeamId, id, c.Session.UserId)
// We can do better than this etag in this situation
etagChan := Srv.Store.Post().GetEtag(id)
@@ -1215,7 +1243,7 @@ func searchPosts(c *Context, w http.ResponseWriter, r *http.Request) {
for _, params := range paramsList {
// don't allow users to search for everything
if params.Terms != "*" {
- channels = append(channels, Srv.Store.Post().Search(c.Session.TeamId, c.Session.UserId, params))
+ channels = append(channels, Srv.Store.Post().Search(c.TeamId, c.Session.UserId, params))
}
}
diff --git a/api/post_benchmark_test.go b/api/post_benchmark_test.go
index 00eb3c468..4e5f6668f 100644
--- a/api/post_benchmark_test.go
+++ b/api/post_benchmark_test.go
@@ -16,7 +16,10 @@ func BenchmarkCreatePost(b *testing.B) {
var (
NUM_POSTS_RANGE = utils.Range{NUM_POSTS, NUM_POSTS}
)
- _, _, channel := SetupBenchmark()
+
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel := th.BasicChannel
testPoster := NewAutoPostCreator(Client, channel.Id)
@@ -32,7 +35,10 @@ func BenchmarkUpdatePost(b *testing.B) {
NUM_POSTS_RANGE = utils.Range{NUM_POSTS, NUM_POSTS}
UPDATE_POST_LEN = 100
)
- _, _, channel := SetupBenchmark()
+
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel := th.BasicChannel
testPoster := NewAutoPostCreator(Client, channel.Id)
posts, valid := testPoster.CreateTestPosts(NUM_POSTS_RANGE)
@@ -59,7 +65,10 @@ func BenchmarkGetPosts(b *testing.B) {
var (
NUM_POSTS_RANGE = utils.Range{NUM_POSTS, NUM_POSTS}
)
- _, _, channel := SetupBenchmark()
+
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel := th.BasicChannel
testPoster := NewAutoPostCreator(Client, channel.Id)
testPoster.CreateTestPosts(NUM_POSTS_RANGE)
@@ -75,7 +84,10 @@ func BenchmarkSearchPosts(b *testing.B) {
var (
NUM_POSTS_RANGE = utils.Range{NUM_POSTS, NUM_POSTS}
)
- _, _, channel := SetupBenchmark()
+
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel := th.BasicChannel
testPoster := NewAutoPostCreator(Client, channel.Id)
testPoster.CreateTestPosts(NUM_POSTS_RANGE)
@@ -93,7 +105,10 @@ func BenchmarkEtagCache(b *testing.B) {
var (
NUM_POSTS_RANGE = utils.Range{NUM_POSTS, NUM_POSTS}
)
- _, _, channel := SetupBenchmark()
+
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel := th.BasicChannel
testPoster := NewAutoPostCreator(Client, channel.Id)
testPoster.CreateTestPosts(NUM_POSTS_RANGE)
@@ -111,7 +126,10 @@ func BenchmarkDeletePosts(b *testing.B) {
var (
NUM_POSTS_RANGE = utils.Range{NUM_POSTS, NUM_POSTS}
)
- _, _, channel := SetupBenchmark()
+
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel := th.BasicChannel
testPoster := NewAutoPostCreator(Client, channel.Id)
posts, valid := testPoster.CreateTestPosts(NUM_POSTS_RANGE)
diff --git a/api/post_test.go b/api/post_test.go
index 2d978d3e3..b905c143e 100644
--- a/api/post_test.go
+++ b/api/post_test.go
@@ -5,38 +5,24 @@ package api
import (
"github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
"net/http"
- "strings"
+ //"strings"
+ "fmt"
"testing"
"time"
)
func TestCreatePost(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- team2 := &model.Team{DisplayName: "Name Team 2", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
-
- channel2 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+ team2 := th.CreateTeam(th.BasicClient)
+ user1 := th.BasicUser
+ user3 := th.CreateUser(th.BasicClient)
+ LinkUserToTeam(user3, team2)
+ channel1 := th.BasicChannel
+ channel2 := th.CreateChannel(Client, team)
filenames := []string{"/12345678901234567890123456/12345678901234567890123456/12345678901234567890123456/test.png", "/" + channel1.Id + "/" + user1.Id + "/test.png", "www.mattermost.com/fake/url", "junk"}
@@ -97,21 +83,17 @@ func TestCreatePost(t *testing.T) {
t.Fatal("Should have been forbidden")
}
- Client.LoginByEmail(team.Name, user2.Email, "pwd")
+ th.LoginBasic2()
+
post7 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
_, err = Client.CreatePost(post7)
if err.StatusCode != http.StatusForbidden {
t.Fatal("Should have been forbidden")
}
- user3 := &model.User{TeamId: team2.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user3.Id))
-
- Client.LoginByEmail(team2.Name, user3.Email, "pwd")
-
- channel3 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team2.Id}
- channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel)
+ Client.LoginByEmail(team2.Name, user3.Email, user3.Password)
+ Client.SetTeamId(team2.Id)
+ channel3 := th.CreateChannel(Client, team2)
post8 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
_, err = Client.CreatePost(post8)
@@ -125,29 +107,9 @@ func TestCreatePost(t *testing.T) {
}
func TestUpdatePost(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- team2 := &model.Team{DisplayName: "Name Team 2", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
-
- channel2 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel1 := th.BasicChannel
post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
rpost1, err := Client.CreatePost(post1)
@@ -196,19 +158,9 @@ func TestUpdatePost(t *testing.T) {
}
func TestGetPosts(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel1 := th.BasicChannel
time.Sleep(10 * time.Millisecond)
post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
@@ -261,19 +213,9 @@ func TestGetPosts(t *testing.T) {
}
func TestGetPostsSince(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel1 := th.BasicChannel
time.Sleep(10 * time.Millisecond)
post0 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
@@ -331,19 +273,9 @@ func TestGetPostsSince(t *testing.T) {
}
func TestGetPostsBeforeAfter(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel1 := th.BasicChannel
time.Sleep(10 * time.Millisecond)
post0 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
@@ -379,7 +311,8 @@ func TestGetPostsBeforeAfter(t *testing.T) {
t.Fatal("wrong order")
}
- if len(r1.Posts) != 2 {
+ if len(r1.Posts) != 3 {
+ t.Log(r1.Posts)
t.Fatal("wrong size")
}
@@ -408,19 +341,9 @@ func TestGetPostsBeforeAfter(t *testing.T) {
}
func TestSearchPosts(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel1 := th.BasicChannel
post1 := &model.Post{ChannelId: channel1.Id, Message: "search for post1"}
post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
@@ -458,19 +381,9 @@ func TestSearchPosts(t *testing.T) {
}
func TestSearchHashtagPosts(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel1 := th.BasicChannel
post1 := &model.Post{ChannelId: channel1.Id, Message: "#sgtitlereview with space"}
post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
@@ -489,19 +402,10 @@ func TestSearchHashtagPosts(t *testing.T) {
}
func TestSearchPostsInChannel(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel1 := th.BasicChannel
+ team := th.BasicTeam
post1 := &model.Post{ChannelId: channel1.Id, Message: "sgtitlereview with space"}
post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
@@ -529,7 +433,7 @@ func TestSearchPostsInChannel(t *testing.T) {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
- if result := Client.Must(Client.SearchPosts("channel:" + channel1.Name)).Data.(*model.PostList); len(result.Order) != 1 {
+ if result := Client.Must(Client.SearchPosts("channel:" + channel1.Name)).Data.(*model.PostList); len(result.Order) != 2 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
@@ -567,38 +471,29 @@ func TestSearchPostsInChannel(t *testing.T) {
}
func TestSearchPostsFromUser(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
-
- channel2 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel1 := th.BasicChannel
+ team := th.BasicTeam
+ user1 := th.BasicUser
+ user2 := th.BasicUser2
+ channel2 := th.CreateChannel(Client, team)
+ Client.Must(Client.AddChannelMember(channel1.Id, th.BasicUser2.Id))
+ Client.Must(Client.AddChannelMember(channel2.Id, th.BasicUser2.Id))
+ user3 := th.CreateUser(Client)
+ LinkUserToTeam(user3, team)
+ Client.Must(Client.AddChannelMember(channel1.Id, user3.Id))
+ Client.Must(Client.AddChannelMember(channel2.Id, user3.Id))
post1 := &model.Post{ChannelId: channel1.Id, Message: "sgtitlereview with space"}
post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
-
- Client.LoginByEmail(team.Name, user2.Email, "pwd")
- Client.Must(Client.JoinChannel(channel1.Id))
- Client.Must(Client.JoinChannel(channel2.Id))
+ th.LoginBasic2()
post2 := &model.Post{ChannelId: channel2.Id, Message: "sgtitlereview\n with return"}
post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post)
- if result := Client.Must(Client.SearchPosts("from: " + user1.Username)).Data.(*model.PostList); len(result.Order) != 1 {
+ if result := Client.Must(Client.SearchPosts("from: " + user1.Username)).Data.(*model.PostList); len(result.Order) != 2 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
@@ -617,13 +512,7 @@ func TestSearchPostsFromUser(t *testing.T) {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
- user3 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user3.Id))
-
- Client.LoginByEmail(team.Name, user3.Email, "pwd")
- Client.Must(Client.JoinChannel(channel1.Id))
- Client.Must(Client.JoinChannel(channel2.Id))
+ Client.LoginByEmail(team.Name, user3.Email, user3.Password)
// wait for the join/leave messages to be created for user3 since they're done asynchronously
time.Sleep(100 * time.Millisecond)
@@ -649,19 +538,9 @@ func TestSearchPostsFromUser(t *testing.T) {
}
func TestGetPostsCache(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel1 := th.BasicChannel
time.Sleep(10 * time.Millisecond)
post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
@@ -698,23 +577,10 @@ func TestGetPostsCache(t *testing.T) {
}
func TestDeletePosts(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- userAdmin := &model.User{TeamId: team.Id, Email: team.Email, Nickname: "Corey Hulen", Password: "pwd"}
- userAdmin = Client.Must(Client.CreateUser(userAdmin, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(userAdmin.Id))
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel1 := th.BasicChannel
+ UpdateUserToTeamAdmin(th.BasicUser2, th.BasicTeam)
time.Sleep(10 * time.Millisecond)
post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
@@ -745,7 +611,7 @@ func TestDeletePosts(t *testing.T) {
r2 := Client.Must(Client.GetPosts(channel1.Id, 0, 10, "")).Data.(*model.PostList)
- if len(r2.Posts) != 4 {
+ if len(r2.Posts) != 5 {
t.Fatal("should have returned 4 items")
}
@@ -753,27 +619,17 @@ func TestDeletePosts(t *testing.T) {
post4 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
post4 = Client.Must(Client.CreatePost(post4)).Data.(*model.Post)
- Client.LoginByEmail(team.Name, userAdmin.Email, "pwd")
+ th.LoginBasic2()
Client.Must(Client.DeletePost(channel1.Id, post4.Id))
}
func TestEmailMention(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: "success+test@simulator.amazonses.com", Nickname: "Bob Bobby", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel1 := th.BasicChannel
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
-
- post1 := &model.Post{ChannelId: channel1.Id, Message: "bob"}
+ post1 := &model.Post{ChannelId: channel1.Id, Message: th.BasicUser.Username}
post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
// No easy way to verify the email was sent, but this will at least cause the server to throw errors if the code is broken
@@ -781,19 +637,9 @@ func TestEmailMention(t *testing.T) {
}
func TestFuzzyPosts(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel1 := th.BasicChannel
filenames := []string{"junk"}
@@ -808,21 +654,13 @@ func TestFuzzyPosts(t *testing.T) {
}
func TestMakeDirectChannelVisible(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+ user1 := th.BasicUser
+ user2 := th.BasicUser2
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
-
- // user2 will be created with prefs created to show user1 in the sidebar so set that to false to get rid of it
- Client.LoginByEmail(team.Name, user2.Email, "pwd")
+ th.LoginBasic2()
preferences := &model.Preferences{
{
@@ -834,9 +672,11 @@ func TestMakeDirectChannelVisible(t *testing.T) {
}
Client.Must(Client.SetPreferences(preferences))
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
+ Client.Must(Client.Logout())
+ th.LoginBasic()
+ th.BasicClient.SetTeamId(team.Id)
- channel := Client.Must(Client.CreateDirectChannel(map[string]string{"user_id": user2.Id})).Data.(*model.Channel)
+ channel := Client.Must(Client.CreateDirectChannel(user2.Id)).Data.(*model.Channel)
makeDirectChannelVisible(team.Id, channel.Id)
@@ -845,38 +685,17 @@ func TestMakeDirectChannelVisible(t *testing.T) {
} else if pref := result.Data.(*model.Preference); pref.Value != "true" {
t.Fatal("Failed to set direct channel to be visible for user1")
}
-
- Client.LoginByEmail(team.Name, user2.Email, "pwd")
-
- if result, err := Client.GetPreference(model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, user1.Id); err != nil {
- t.Fatal("Errored trying to set direct channel to be visible for user2")
- } else if pref := result.Data.(*model.Preference); pref.Value != "true" {
- t.Fatal("Failed to set direct channel to be visible for user2")
- }
}
func TestGetOutOfChannelMentions(t *testing.T) {
- Setup()
-
- team1 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Type: model.TEAM_OPEN}
- team1 = Client.Must(Client.CreateTeam(team1)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team1.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd", Username: "user1"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- user2 := &model.User{TeamId: team1.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd", Username: "user2"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
-
- user3 := &model.User{TeamId: team1.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd", Username: "user3"}
- user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user3.Id))
-
- Client.Must(Client.LoginByEmail(team1.Name, user1.Email, "pwd"))
-
- channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team1.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel1 := th.BasicChannel
+ team1 := th.BasicTeam
+ user1 := th.BasicUser
+ user2 := th.BasicUser2
+ user3 := th.CreateUser(Client)
+ LinkUserToTeam(user3, team1)
var allProfiles map[string]*model.User
if result := <-Srv.Store.User().GetProfiles(team1.Id); result.Err != nil {
@@ -893,39 +712,37 @@ func TestGetOutOfChannelMentions(t *testing.T) {
}
// test a post that doesn't @mention anybody
- post1 := &model.Post{ChannelId: channel1.Id, Message: "user1 user2 user3"}
+ post1 := &model.Post{ChannelId: channel1.Id, Message: fmt.Sprintf("%v %v %v", user1.Username, user2.Username, user3.Username)}
if mentioned := getOutOfChannelMentions(post1, allProfiles, members); len(mentioned) != 0 {
t.Fatalf("getOutOfChannelMentions returned %v when no users were mentioned", mentioned)
}
// test a post that @mentions someone in the channel
- post2 := &model.Post{ChannelId: channel1.Id, Message: "@user1 is user1"}
+ post2 := &model.Post{ChannelId: channel1.Id, Message: fmt.Sprintf("@%v is %v", user1.Username, user1.Username)}
if mentioned := getOutOfChannelMentions(post2, allProfiles, members); len(mentioned) != 0 {
t.Fatalf("getOutOfChannelMentions returned %v when only users in the channel were mentioned", mentioned)
}
// test a post that @mentions someone not in the channel
- post3 := &model.Post{ChannelId: channel1.Id, Message: "@user2 and @user3 aren't in the channel"}
+ post3 := &model.Post{ChannelId: channel1.Id, Message: fmt.Sprintf("@%v and @%v aren't in the channel", user2.Username, user3.Username)}
if mentioned := getOutOfChannelMentions(post3, allProfiles, members); len(mentioned) != 2 || (mentioned[0].Id != user2.Id && mentioned[0].Id != user3.Id) || (mentioned[1].Id != user2.Id && mentioned[1].Id != user3.Id) {
t.Fatalf("getOutOfChannelMentions returned %v when two users outside the channel were mentioned", mentioned)
}
// test a post that @mentions someone not in the channel as well as someone in the channel
- post4 := &model.Post{ChannelId: channel1.Id, Message: "@user2 and @user1 might be in the channel"}
+ post4 := &model.Post{ChannelId: channel1.Id, Message: fmt.Sprintf("@%v and @%v might be in the channel", user2.Username, user1.Username)}
if mentioned := getOutOfChannelMentions(post4, allProfiles, members); len(mentioned) != 1 || mentioned[0].Id != user2.Id {
t.Fatalf("getOutOfChannelMentions returned %v when someone in the channel and someone outside the channel were mentioned", mentioned)
}
Client.Must(Client.Logout())
- team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Type: model.TEAM_OPEN}
- team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team)
-
- user4 := &model.User{TeamId: team2.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd", Username: "user4"}
- user4 = Client.Must(Client.CreateUser(user4, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user4.Id))
+ team2 := th.CreateTeam(Client)
+ user4 := th.CreateUser(Client)
+ LinkUserToTeam(user4, team2)
- Client.Must(Client.LoginByEmail(team2.Name, user4.Email, "pwd"))
+ Client.Must(Client.LoginByEmail(team2.Name, user4.Email, user4.Password))
+ Client.SetTeamId(team2.Id)
channel2 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team2.Id}
channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
@@ -943,7 +760,7 @@ func TestGetOutOfChannelMentions(t *testing.T) {
}
// test a post that @mentions someone on a different team
- post5 := &model.Post{ChannelId: channel2.Id, Message: "@user2 and @user3 might be in the channel"}
+ post5 := &model.Post{ChannelId: channel2.Id, Message: fmt.Sprintf("@%v and @%v might be in the channel", user2.Username, user3.Username)}
if mentioned := getOutOfChannelMentions(post5, allProfiles, members); len(mentioned) != 0 {
t.Fatalf("getOutOfChannelMentions returned %v when two users on a different team were mentioned", mentioned)
}
diff --git a/api/preference.go b/api/preference.go
index 9550b6c92..d9ddb1a21 100644
--- a/api/preference.go
+++ b/api/preference.go
@@ -11,14 +11,13 @@ import (
"net/http"
)
-func InitPreference(r *mux.Router) {
+func InitPreference() {
l4g.Debug(utils.T("api.preference.init.debug"))
- sr := r.PathPrefix("/preferences").Subrouter()
- sr.Handle("/", ApiUserRequired(getAllPreferences)).Methods("GET")
- sr.Handle("/save", ApiUserRequired(savePreferences)).Methods("POST")
- sr.Handle("/{category:[A-Za-z0-9_]+}", ApiUserRequired(getPreferenceCategory)).Methods("GET")
- sr.Handle("/{category:[A-Za-z0-9_]+}/{name:[A-Za-z0-9_]+}", ApiUserRequired(getPreference)).Methods("GET")
+ BaseRoutes.Preferences.Handle("/", ApiUserRequired(getAllPreferences)).Methods("GET")
+ BaseRoutes.Preferences.Handle("/save", ApiUserRequired(savePreferences)).Methods("POST")
+ BaseRoutes.Preferences.Handle("/{category:[A-Za-z0-9_]+}", ApiUserRequired(getPreferenceCategory)).Methods("GET")
+ BaseRoutes.Preferences.Handle("/{category:[A-Za-z0-9_]+}/{name:[A-Za-z0-9_]+}", ApiUserRequired(getPreference)).Methods("GET")
}
func getAllPreferences(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -44,7 +43,7 @@ func savePreferences(c *Context, w http.ResponseWriter, r *http.Request) {
c.Err = model.NewLocAppError("savePreferences", "api.preference.save_preferences.set.app_error", nil,
c.T("api.preference.save_preferences.set_details.app_error",
map[string]interface{}{"SessionUserId": c.Session.UserId, "PreferenceUserId": preference.UserId}))
- c.Err.StatusCode = http.StatusUnauthorized
+ c.Err.StatusCode = http.StatusForbidden
return
}
}
diff --git a/api/preference_test.go b/api/preference_test.go
index 82bee6315..082f02527 100644
--- a/api/preference_test.go
+++ b/api/preference_test.go
@@ -5,23 +5,13 @@ package api
import (
"github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
"testing"
)
func TestGetAllPreferences(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ user1 := th.BasicUser
category := model.NewId()
@@ -43,7 +33,6 @@ func TestGetAllPreferences(t *testing.T) {
},
}
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
Client.Must(Client.SetPreferences(&preferences1))
if result, err := Client.GetAllPreferences(); err != nil {
@@ -52,27 +41,19 @@ func TestGetAllPreferences(t *testing.T) {
t.Fatal("received the wrong number of preferences")
}
- Client.LoginByEmail(team.Name, user2.Email, "pwd")
+ th.LoginBasic2()
- // note that user2 will automatically have a preference set for them to show user1 for direct messages
if result, err := Client.GetAllPreferences(); err != nil {
t.Fatal(err)
- } else if data := result.Data.(model.Preferences); len(data) != 2 {
+ } else if data := result.Data.(model.Preferences); len(data) == 0 {
t.Fatal("received the wrong number of preferences")
}
}
func TestSetPreferences(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ user1 := th.BasicUser
// save 10 preferences
var preferences model.Preferences
@@ -98,12 +79,7 @@ func TestSetPreferences(t *testing.T) {
t.Fatal(err)
}
- // not able to update as a different user
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
-
- Client.LoginByEmail(team.Name, user2.Email, "pwd")
+ th.LoginBasic2()
if _, err := Client.SetPreferences(&preferences); err == nil {
t.Fatal("shouldn't have been able to update another user's preferences")
@@ -111,18 +87,9 @@ func TestSetPreferences(t *testing.T) {
}
func TestGetPreferenceCategory(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
-
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ user1 := th.BasicUser
category := model.NewId()
@@ -144,11 +111,8 @@ func TestGetPreferenceCategory(t *testing.T) {
},
}
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
Client.Must(Client.SetPreferences(&preferences1))
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
if result, err := Client.GetPreferenceCategory(category); err != nil {
t.Fatal(err)
} else if data := result.Data.(model.Preferences); len(data) != 2 {
@@ -157,7 +121,7 @@ func TestGetPreferenceCategory(t *testing.T) {
t.Fatal("received incorrect preferences")
}
- Client.LoginByEmail(team.Name, user2.Email, "pwd")
+ th.LoginBasic2()
if result, err := Client.GetPreferenceCategory(category); err != nil {
t.Fatal(err)
@@ -167,16 +131,9 @@ func TestGetPreferenceCategory(t *testing.T) {
}
func TestGetPreference(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ user := th.BasicUser
preferences := model.Preferences{
{
diff --git a/api/server.go b/api/server.go
index b84066cbe..6e0ca49f0 100644
--- a/api/server.go
+++ b/api/server.go
@@ -6,6 +6,7 @@ package api
import (
l4g "github.com/alecthomas/log4go"
"github.com/braintree/manners"
+ "github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
@@ -73,11 +74,10 @@ func StartServer() {
}
go func() {
- err := manners.ListenAndServe(utils.Cfg.ServiceSettings.ListenAddress, handler)
+ err := manners.ListenAndServe(utils.Cfg.ServiceSettings.ListenAddress, handlers.RecoveryHandler(handlers.PrintRecoveryStack(true))(handler))
if err != nil {
l4g.Critical(utils.T("api.server.start_server.starting.critical"), err)
time.Sleep(time.Second)
- panic(utils.T("api.server.start_server.starting.panic") + err.Error())
}
}()
}
diff --git a/api/sharding.go b/api/sharding.go
deleted file mode 100644
index 2a5db408c..000000000
--- a/api/sharding.go
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package api
-
-/*
-func createSubDomain(subDomain string, target string) {
-
- if utils.Cfg.AWSSettings.Route53AccessKeyId == "" {
- return
- }
-
- creds := aws.Creds(utils.Cfg.AWSSettings.Route53AccessKeyId, utils.Cfg.AWSSettings.Route53SecretAccessKey, "")
- r53 := route53.New(aws.DefaultConfig.Merge(&aws.Config{Credentials: creds, Region: utils.Cfg.AWSSettings.Route53Region}))
-
- rr := route53.ResourceRecord{
- Value: aws.String(target),
- }
-
- rrs := make([]*route53.ResourceRecord, 1)
- rrs[0] = &rr
-
- change := route53.Change{
- Action: aws.String("CREATE"),
- ResourceRecordSet: &route53.ResourceRecordSet{
- Name: aws.String(fmt.Sprintf("%v.%v", subDomain, utils.Cfg.ServiceSettings.Domain)),
- TTL: aws.Long(300),
- Type: aws.String("CNAME"),
- ResourceRecords: rrs,
- },
- }
-
- changes := make([]*route53.Change, 1)
- changes[0] = &change
-
- r53req := &route53.ChangeResourceRecordSetsInput{
- HostedZoneID: aws.String(utils.Cfg.AWSSettings.Route53ZoneId),
- ChangeBatch: &route53.ChangeBatch{
- Changes: changes,
- },
- }
-
- if _, err := r53.ChangeResourceRecordSets(r53req); err != nil {
- l4g.Error("erro in createSubDomain domain=%v err=%v", subDomain, err)
- return
- }
-}
-
-func doesSubDomainExist(subDomain string) bool {
-
- // if it's configured for testing then skip this step
- if utils.Cfg.AWSSettings.Route53AccessKeyId == "" {
- return false
- }
-
- creds := aws.Creds(utils.Cfg.AWSSettings.Route53AccessKeyId, utils.Cfg.AWSSettings.Route53SecretAccessKey, "")
- r53 := route53.New(aws.DefaultConfig.Merge(&aws.Config{Credentials: creds, Region: utils.Cfg.AWSSettings.Route53Region}))
-
- r53req := &route53.ListResourceRecordSetsInput{
- HostedZoneID: aws.String(utils.Cfg.AWSSettings.Route53ZoneId),
- MaxItems: aws.String("1"),
- StartRecordName: aws.String(fmt.Sprintf("%v.%v.", subDomain, utils.Cfg.ServiceSettings.Domain)),
- }
-
- if result, err := r53.ListResourceRecordSets(r53req); err != nil {
- l4g.Error("error in doesSubDomainExist domain=%v err=%v", subDomain, err)
- return true
- } else {
-
- for _, v := range result.ResourceRecordSets {
- if v.Name != nil && *v.Name == fmt.Sprintf("%v.%v.", subDomain, utils.Cfg.ServiceSettings.Domain) {
- return true
- }
- }
- }
-
- return false
-}
-*/
diff --git a/api/slackimport.go b/api/slackimport.go
index 5ca209c5c..4319fe409 100644
--- a/api/slackimport.go
+++ b/api/slackimport.go
@@ -112,7 +112,6 @@ func SlackAddUsers(teamId string, slackusers []SlackUser, log *bytes.Buffer) map
password := model.NewId()
newUser := model.User{
- TeamId: teamId,
Username: sUser.Username,
FirstName: firstName,
LastName: lastName,
@@ -120,7 +119,7 @@ func SlackAddUsers(teamId string, slackusers []SlackUser, log *bytes.Buffer) map
Password: password,
}
- if mUser := ImportUser(&newUser); mUser != nil {
+ if mUser := ImportUser(teamId, &newUser); mUser != nil {
addedUsers[sUser.Id] = mUser
log.WriteString(utils.T("api.slackimport.slack_add_users.email_pwd", map[string]interface{}{"Email": newUser.Email, "Password": password}))
} else {
diff --git a/api/team.go b/api/team.go
index 255982522..eefdc3d85 100644
--- a/api/team.go
+++ b/api/team.go
@@ -6,38 +6,43 @@ package api
import (
"bytes"
"fmt"
- l4g "github.com/alecthomas/log4go"
- "github.com/gorilla/mux"
- "github.com/mattermost/platform/einterfaces"
- "github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
- "github.com/mattermost/platform/utils"
"html/template"
"net/http"
"net/url"
"strconv"
"strings"
"time"
+
+ l4g "github.com/alecthomas/log4go"
+ "github.com/gorilla/mux"
+
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
)
-func InitTeam(r *mux.Router) {
+func InitTeam() {
l4g.Debug(utils.T("api.team.init.debug"))
- sr := r.PathPrefix("/teams").Subrouter()
- sr.Handle("/create", ApiAppHandler(createTeam)).Methods("POST")
- sr.Handle("/create_from_signup", ApiAppHandler(createTeamFromSignup)).Methods("POST")
- sr.Handle("/create_with_ldap", ApiAppHandler(createTeamWithLdap)).Methods("POST")
- sr.Handle("/create_with_sso/{service:[A-Za-z]+}", ApiAppHandler(createTeamFromSSO)).Methods("POST")
- sr.Handle("/signup", ApiAppHandler(signupTeam)).Methods("POST")
- sr.Handle("/all", ApiAppHandler(getAll)).Methods("GET")
- sr.Handle("/find_team_by_name", ApiAppHandler(findTeamByName)).Methods("POST")
- sr.Handle("/invite_members", ApiUserRequired(inviteMembers)).Methods("POST")
- sr.Handle("/update", ApiUserRequired(updateTeam)).Methods("POST")
- sr.Handle("/me", ApiUserRequired(getMyTeam)).Methods("GET")
- sr.Handle("/get_invite_info", ApiAppHandler(getInviteInfo)).Methods("POST")
+ BaseRoutes.Teams.Handle("/create", ApiAppHandler(createTeam)).Methods("POST")
+ BaseRoutes.Teams.Handle("/create_from_signup", ApiAppHandler(createTeamFromSignup)).Methods("POST")
+ BaseRoutes.Teams.Handle("/signup", ApiAppHandler(signupTeam)).Methods("POST")
+ BaseRoutes.Teams.Handle("/all", ApiAppHandler(getAll)).Methods("GET")
+ BaseRoutes.Teams.Handle("/all_team_listings", ApiUserRequired(GetAllTeamListings)).Methods("GET")
+ BaseRoutes.Teams.Handle("/get_invite_info", ApiAppHandler(getInviteInfo)).Methods("POST")
+ BaseRoutes.Teams.Handle("/find_team_by_name", ApiAppHandler(findTeamByName)).Methods("POST")
+ BaseRoutes.Teams.Handle("/members/{id:[A-Za-z0-9]+}", ApiUserRequired(getMembers)).Methods("GET")
+
+ BaseRoutes.NeedTeam.Handle("/me", ApiUserRequired(getMyTeam)).Methods("GET")
+ BaseRoutes.NeedTeam.Handle("/update", ApiUserRequired(updateTeam)).Methods("POST")
+
+ BaseRoutes.NeedTeam.Handle("/invite_members", ApiUserRequired(inviteMembers)).Methods("POST")
+
+ BaseRoutes.NeedTeam.Handle("/add_user_to_team", ApiUserRequired(addUserToTeam)).Methods("POST")
+
// These should be moved to the global admain console
- sr.Handle("/import_team", ApiUserRequired(importTeam)).Methods("POST")
- sr.Handle("/export_team", ApiUserRequired(exportTeam)).Methods("GET")
+ BaseRoutes.Teams.Handle("/import_team", ApiUserRequired(importTeam)).Methods("POST")
+ BaseRoutes.Teams.Handle("/export_team", ApiUserRequired(exportTeam)).Methods("GET")
+ BaseRoutes.Teams.Handle("/add_user_to_team_from_invite", ApiUserRequired(addUserToTeamFromInvite)).Methods("POST")
}
func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -92,67 +97,6 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.MapToJson(m)))
}
-func createTeamFromSSO(c *Context, w http.ResponseWriter, r *http.Request) {
- params := mux.Vars(r)
- service := params["service"]
-
- sso := utils.Cfg.GetSSOService(service)
- if sso != nil && !sso.Enable {
- c.SetInvalidParam("createTeamFromSSO", "service")
- return
- }
-
- team := model.TeamFromJson(r.Body)
-
- if team == nil {
- c.SetInvalidParam("createTeamFromSSO", "team")
- return
- }
-
- if !isTeamCreationAllowed(c, team.Email) {
- return
- }
-
- team.PreSave()
-
- team.Name = model.CleanTeamName(team.Name)
-
- if err := team.IsValid(*utils.Cfg.TeamSettings.RestrictTeamNames); err != nil {
- c.Err = err
- return
- }
-
- team.Id = ""
-
- found := true
- count := 0
- for found {
- if found = FindTeamByName(c, team.Name, "true"); c.Err != nil {
- return
- } else if found {
- team.Name = team.Name + strconv.Itoa(count)
- count += 1
- }
- }
-
- if result := <-Srv.Store.Team().Save(team); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- rteam := result.Data.(*model.Team)
-
- if _, err := CreateDefaultChannels(c, rteam.Id); err != nil {
- c.Err = nil
- return
- }
-
- data := map[string]string{"follow_link": c.GetSiteURL() + "/api/v1/oauth/" + service + "/signup?team=" + rteam.Name}
- w.Write([]byte(model.MapToJson(data)))
-
- }
-
-}
-
func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) {
if !utils.Cfg.EmailSettings.EnableSignUpWithEmail {
c.Err = model.NewLocAppError("createTeamFromSignup", "api.team.create_team_from_signup.email_disabled.app_error", nil, "")
@@ -186,13 +130,11 @@ func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) {
password := teamSignup.User.Password
teamSignup.User.PreSave()
- teamSignup.User.TeamId = model.NewId()
if err := teamSignup.User.IsValid(); err != nil {
c.Err = err
return
}
teamSignup.User.Id = ""
- teamSignup.User.TeamId = ""
teamSignup.User.Password = password
if !model.ComparePassword(teamSignup.Hash, fmt.Sprintf("%v:%v", teamSignup.Data, utils.Cfg.EmailSettings.InviteSalt)) {
@@ -206,10 +148,7 @@ func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- found := FindTeamByName(c, teamSignup.Team.Name, "true")
- if c.Err != nil {
- return
- }
+ found := FindTeamByName(teamSignup.Team.Name)
if found {
c.Err = model.NewLocAppError("createTeamFromSignup", "api.team.create_team_from_signup.unavailable.app_error", nil, "d="+teamSignup.Team.Name)
@@ -227,15 +166,16 @@ func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- teamSignup.User.TeamId = rteam.Id
teamSignup.User.EmailVerified = true
- ruser, err := CreateUser(rteam, &teamSignup.User)
+ ruser, err := CreateUser(&teamSignup.User)
if err != nil {
c.Err = err
return
}
+ JoinUserToTeam(rteam, ruser)
+
InviteMembers(c, rteam, ruser, teamSignup.Invites)
teamSignup.Team = *rteam
@@ -245,85 +185,38 @@ func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
-func createTeamWithLdap(c *Context, w http.ResponseWriter, r *http.Request) {
- ldap := einterfaces.GetLdapInterface()
- if ldap == nil {
- c.Err = model.NewLocAppError("createTeamWithLdap", "ent.ldap.do_login.licence_disable.app_error", nil, "")
- return
- }
-
- teamSignup := model.TeamSignupFromJson(r.Body)
+func createTeam(c *Context, w http.ResponseWriter, r *http.Request) {
+ team := model.TeamFromJson(r.Body)
- if teamSignup == nil {
- c.SetInvalidParam("createTeam", "teamSignup")
+ if team == nil {
+ c.SetInvalidParam("createTeam", "team")
return
}
- teamSignup.Team.PreSave()
-
- if err := teamSignup.Team.IsValid(*utils.Cfg.TeamSettings.RestrictTeamNames); err != nil {
- c.Err = err
- return
- }
+ var user *model.User
+ if len(c.Session.UserId) > 0 {
+ uchan := Srv.Store.User().Get(c.Session.UserId)
- if !isTeamCreationAllowed(c, teamSignup.Team.Email) {
- return
+ if result := <-uchan; result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ user = result.Data.(*model.User)
+ team.Email = user.Email
+ }
}
- teamSignup.Team.Id = ""
-
- found := FindTeamByName(c, teamSignup.Team.Name, "true")
+ rteam := CreateTeam(c, team)
if c.Err != nil {
return
}
- if found {
- c.Err = model.NewLocAppError("createTeamFromSignup", "api.team.create_team_from_signup.unavailable.app_error", nil, "d="+teamSignup.Team.Name)
- return
- }
-
- user, err := ldap.GetUser(teamSignup.User.Username)
- if err != nil {
- c.Err = err
- return
- }
-
- err = ldap.CheckPassword(teamSignup.User.Username, teamSignup.User.Password)
- if err != nil {
- c.Err = err
- return
- }
-
- if result := <-Srv.Store.Team().Save(&teamSignup.Team); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- rteam := result.Data.(*model.Team)
-
- if _, err := CreateDefaultChannels(c, rteam.Id); err != nil {
- c.Err = nil
- return
- }
-
- user.TeamId = rteam.Id
- ruser, err := CreateUser(rteam, user)
+ if user != nil {
+ err := JoinUserToTeam(team, user)
if err != nil {
c.Err = err
return
}
-
- teamSignup.Team = *rteam
- teamSignup.User = *ruser
-
- w.Write([]byte(teamSignup.ToJson()))
- }
-}
-
-func createTeam(c *Context, w http.ResponseWriter, r *http.Request) {
- team := model.TeamFromJson(r.Body)
- rteam := CreateTeam(c, team)
- if c.Err != nil {
- return
}
w.Write([]byte(rteam.ToJson()))
@@ -360,6 +253,31 @@ func CreateTeam(c *Context, team *model.Team) *model.Team {
}
}
+func JoinUserToTeam(team *model.Team, user *model.User) *model.AppError {
+
+ tm := &model.TeamMember{TeamId: team.Id, UserId: user.Id}
+
+ channelRole := ""
+ if team.Email == user.Email {
+ tm.Roles = model.ROLE_TEAM_ADMIN
+ channelRole = model.CHANNEL_ROLE_ADMIN
+ }
+
+ if tmr := <-Srv.Store.Team().SaveMember(tm); tmr.Err != nil {
+ return tmr.Err
+ }
+
+ // Soft error if there is an issue joining the default channels
+ if err := JoinDefaultChannels(team.Id, user, channelRole); err != nil {
+ l4g.Error(utils.T("api.user.create_user.joining.error"), user.Id, team.Id, err)
+ }
+
+ RemoveAllSessionsForUserId(user.Id)
+ InvalidateCacheForUser(user.Id)
+
+ return nil
+}
+
func isTeamCreationAllowed(c *Context, email string) bool {
email = strings.ToLower(email)
@@ -389,6 +307,24 @@ func isTeamCreationAllowed(c *Context, email string) bool {
return true
}
+func GetAllTeamListings(c *Context, w http.ResponseWriter, r *http.Request) {
+ if result := <-Srv.Store.Team().GetAllTeamListing(); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ teams := result.Data.([]*model.Team)
+ m := make(map[string]*model.Team)
+ for _, v := range teams {
+ m[v.Id] = v
+ if !c.IsSystemAdmin() {
+ m[v.Id].Sanitize()
+ }
+ }
+
+ w.Write([]byte(model.TeamMapToJson(m)))
+ }
+}
+
func getAll(c *Context, w http.ResponseWriter, r *http.Request) {
if result := <-Srv.Store.Team().GetAll(); result.Err != nil {
c.Err = result.Err
@@ -435,52 +371,54 @@ func revokeAllSessions(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
-func findTeamByName(c *Context, w http.ResponseWriter, r *http.Request) {
-
- m := model.MapFromJson(r.Body)
-
- name := strings.ToLower(strings.TrimSpace(m["name"]))
- all := strings.ToLower(strings.TrimSpace(m["all"]))
+func inviteMembers(c *Context, w http.ResponseWriter, r *http.Request) {
+ invites := model.InvitesFromJson(r.Body)
+ if len(invites.Invites) == 0 {
+ c.Err = model.NewLocAppError("Team.InviteMembers", "api.team.invite_members.no_one.app_error", nil, "")
+ c.Err.StatusCode = http.StatusBadRequest
+ return
+ }
- found := FindTeamByName(c, name, all)
+ tchan := Srv.Store.Team().Get(c.TeamId)
+ uchan := Srv.Store.User().Get(c.Session.UserId)
- if c.Err != nil {
+ var team *model.Team
+ if result := <-tchan; result.Err != nil {
+ c.Err = result.Err
return
+ } else {
+ team = result.Data.(*model.Team)
}
- if found {
- w.Write([]byte("true"))
+ var user *model.User
+ if result := <-uchan; result.Err != nil {
+ c.Err = result.Err
+ return
} else {
- w.Write([]byte("false"))
+ user = result.Data.(*model.User)
}
-}
-
-func FindTeamByName(c *Context, name string, all string) bool {
- if name == "" || len(name) > 64 {
- c.SetInvalidParam("findTeamByName", "domain")
- return false
+ ia := make([]string, len(invites.Invites))
+ for _, invite := range invites.Invites {
+ ia = append(ia, invite["email"])
}
- if result := <-Srv.Store.Team().GetByName(name); result.Err != nil {
- return false
- } else {
- return true
- }
+ InviteMembers(c, team, user, ia)
- return false
+ w.Write([]byte(invites.ToJson()))
}
-func inviteMembers(c *Context, w http.ResponseWriter, r *http.Request) {
- invites := model.InvitesFromJson(r.Body)
- if len(invites.Invites) == 0 {
- c.Err = model.NewLocAppError("Team.InviteMembers", "api.team.invite_members.no_one.app_error", nil, "")
- c.Err.StatusCode = http.StatusBadRequest
+func addUserToTeam(c *Context, w http.ResponseWriter, r *http.Request) {
+ params := model.MapFromJson(r.Body)
+ userId := params["user_id"]
+
+ if len(userId) != 26 {
+ c.SetInvalidParam("addUserToTeam", "user_id")
return
}
- tchan := Srv.Store.Team().Get(c.Session.TeamId)
- uchan := Srv.Store.User().Get(c.Session.UserId)
+ tchan := Srv.Store.Team().Get(c.TeamId)
+ uchan := Srv.Store.User().Get(userId)
var team *model.Team
if result := <-tchan; result.Err != nil {
@@ -498,23 +436,116 @@ func inviteMembers(c *Context, w http.ResponseWriter, r *http.Request) {
user = result.Data.(*model.User)
}
- var invNum int64 = 0
- for i, invite := range invites.Invites {
- if result := <-Srv.Store.User().GetByEmail(c.Session.TeamId, invite["email"]); result.Err == nil || result.Err.Id != store.MISSING_ACCOUNT_ERROR {
- invNum = int64(i)
- c.Err = model.NewLocAppError("invite_members", "api.team.invite_members.already.app_error", nil, strconv.FormatInt(invNum, 10))
+ if !c.IsTeamAdmin() {
+ c.Err = model.NewLocAppError("addUserToTeam", "api.team.update_team.permissions.app_error", nil, "userId="+c.Session.UserId)
+ c.Err.StatusCode = http.StatusForbidden
+ return
+ }
+
+ err := JoinUserToTeam(team, user)
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ w.Write([]byte(model.MapToJson(params)))
+}
+
+func addUserToTeamFromInvite(c *Context, w http.ResponseWriter, r *http.Request) {
+
+ params := model.MapFromJson(r.Body)
+ hash := params["hash"]
+ data := params["data"]
+ inviteId := params["invite_id"]
+
+ teamId := ""
+ var team *model.Team
+
+ if len(hash) > 0 {
+ props := model.MapFromJson(strings.NewReader(data))
+
+ if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
+ c.Err = model.NewLocAppError("addUserToTeamFromInvite", "api.user.create_user.signup_link_invalid.app_error", nil, "")
+ return
+ }
+
+ t, err := strconv.ParseInt(props["time"], 10, 64)
+ if err != nil || model.GetMillis()-t > 1000*60*60*48 { // 48 hours
+ c.Err = model.NewLocAppError("addUserToTeamFromInvite", "api.user.create_user.signup_link_expired.app_error", nil, "")
+ return
+ }
+
+ teamId = props["id"]
+
+ // try to load the team to make sure it exists
+ if result := <-Srv.Store.Team().Get(teamId); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ team = result.Data.(*model.Team)
+ }
+ }
+
+ if len(inviteId) > 0 {
+ if result := <-Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ team = result.Data.(*model.Team)
+ teamId = team.Id
+ }
+ }
+
+ if len(teamId) == 0 {
+ c.Err = model.NewLocAppError("addUserToTeamFromInvite", "api.user.create_user.signup_link_invalid.app_error", nil, "")
+ return
+ }
+
+ uchan := Srv.Store.User().Get(c.Session.UserId)
+
+ var user *model.User
+ if result := <-uchan; result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ user = result.Data.(*model.User)
+ }
+
+ tm := c.Session.GetTeamByTeamId(teamId)
+
+ if tm == nil {
+ err := JoinUserToTeam(team, user)
+ if err != nil {
+ c.Err = err
return
}
}
- ia := make([]string, len(invites.Invites))
- for _, invite := range invites.Invites {
- ia = append(ia, invite["email"])
+ team.Sanitize()
+
+ w.Write([]byte(team.ToJson()))
+}
+
+func FindTeamByName(name string) bool {
+ if result := <-Srv.Store.Team().GetByName(name); result.Err != nil {
+ return false
+ } else {
+ return true
}
+}
- InviteMembers(c, team, user, ia)
+func findTeamByName(c *Context, w http.ResponseWriter, r *http.Request) {
- w.Write([]byte(invites.ToJson()))
+ m := model.MapFromJson(r.Body)
+ name := strings.ToLower(strings.TrimSpace(m["name"]))
+
+ found := FindTeamByName(name)
+
+ if found {
+ w.Write([]byte("true"))
+ } else {
+ w.Write([]byte("false"))
+ }
}
func InviteMembers(c *Context, team *model.Team, user *model.User, invites []string) {
@@ -573,7 +604,7 @@ func updateTeam(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- team.Id = c.Session.TeamId
+ team.Id = c.TeamId
if !c.IsTeamAdmin() {
c.Err = model.NewLocAppError("updateTeam", "api.team.update_team.permissions.app_error", nil, "userId="+c.Session.UserId)
@@ -592,7 +623,6 @@ func updateTeam(c *Context, w http.ResponseWriter, r *http.Request) {
oldTeam.DisplayName = team.DisplayName
oldTeam.InviteId = team.InviteId
oldTeam.AllowOpenInvite = team.AllowOpenInvite
- oldTeam.AllowTeamListing = team.AllowTeamListing
oldTeam.CompanyName = team.CompanyName
oldTeam.AllowedDomains = team.AllowedDomains
//oldTeam.Type = team.Type
@@ -617,16 +647,11 @@ func PermanentDeleteTeam(c *Context, team *model.Team) *model.AppError {
return result.Err
}
- if result := <-Srv.Store.User().GetForExport(team.Id); result.Err != nil {
+ if result := <-Srv.Store.Channel().PermanentDeleteByTeam(team.Id); result.Err != nil {
return result.Err
- } else {
- users := result.Data.([]*model.User)
- for _, user := range users {
- PermanentDeleteUser(c, user)
- }
}
- if result := <-Srv.Store.Channel().PermanentDeleteByTeam(team.Id); result.Err != nil {
+ if result := <-Srv.Store.Team().RemoveAllMembersByTeam(team.Id); result.Err != nil {
return result.Err
}
@@ -642,11 +667,11 @@ func PermanentDeleteTeam(c *Context, team *model.Team) *model.AppError {
func getMyTeam(c *Context, w http.ResponseWriter, r *http.Request) {
- if len(c.Session.TeamId) == 0 {
+ if len(c.TeamId) == 0 {
return
}
- if result := <-Srv.Store.Team().Get(c.Session.TeamId); result.Err != nil {
+ if result := <-Srv.Store.Team().Get(c.TeamId); result.Err != nil {
c.Err = result.Err
return
} else if HandleEtag(result.Data.(*model.Team).Etag(), w, r) {
@@ -659,7 +684,7 @@ func getMyTeam(c *Context, w http.ResponseWriter, r *http.Request) {
}
func importTeam(c *Context, w http.ResponseWriter, r *http.Request) {
- if !c.HasPermissionsToTeam(c.Session.TeamId, "import") || !c.IsTeamAdmin() {
+ if !c.HasPermissionsToTeam(c.TeamId, "import") || !c.IsTeamAdmin() {
c.Err = model.NewLocAppError("importTeam", "api.team.import_team.admin.app_error", nil, "userId="+c.Session.UserId)
c.Err.StatusCode = http.StatusForbidden
return
@@ -714,7 +739,7 @@ func importTeam(c *Context, w http.ResponseWriter, r *http.Request) {
switch importFrom {
case "slack":
var err *model.AppError
- if err, log = SlackImport(fileData, fileSize, c.Session.TeamId); err != nil {
+ if err, log = SlackImport(fileData, fileSize, c.TeamId); err != nil {
c.Err = err
c.Err.StatusCode = http.StatusBadRequest
}
@@ -726,7 +751,7 @@ func importTeam(c *Context, w http.ResponseWriter, r *http.Request) {
}
func exportTeam(c *Context, w http.ResponseWriter, r *http.Request) {
- if !c.HasPermissionsToTeam(c.Session.TeamId, "export") || !c.IsTeamAdmin() {
+ if !c.HasPermissionsToTeam(c.TeamId, "export") || !c.IsTeamAdmin() {
c.Err = model.NewLocAppError("exportTeam", "api.team.export_team.admin.app_error", nil, "userId="+c.Session.UserId)
c.Err.StatusCode = http.StatusForbidden
return
@@ -765,3 +790,23 @@ func getInviteInfo(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.MapToJson(result)))
}
}
+
+func getMembers(c *Context, w http.ResponseWriter, r *http.Request) {
+ params := mux.Vars(r)
+ id := params["id"]
+
+ if c.Session.GetTeamByTeamId(id) == nil {
+ if !c.HasSystemAdminPermissions("getMembers") {
+ return
+ }
+ }
+
+ if result := <-Srv.Store.Team().GetMembers(id); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ members := result.Data.([]*model.TeamMember)
+ w.Write([]byte(model.TeamMembersToJson(members)))
+ return
+ }
+}
diff --git a/api/team_test.go b/api/team_test.go
index bbbc8385d..161c7e620 100644
--- a/api/team_test.go
+++ b/api/team_test.go
@@ -13,7 +13,9 @@ import (
)
func TestSignupTeam(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ th.BasicClient.Logout()
+ Client := th.BasicClient
_, err := Client.SignupTeam("test@nowhere.com", "name")
if err != nil {
@@ -22,7 +24,9 @@ func TestSignupTeam(t *testing.T) {
}
func TestCreateFromSignupTeam(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ th.BasicClient.Logout()
+ Client := th.BasicClient
props := make(map[string]string)
props["email"] = strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com"
@@ -47,6 +51,8 @@ func TestCreateFromSignupTeam(t *testing.T) {
}
ruser := rts.Data.(*model.TeamSignup).User
+ rteam := rts.Data.(*model.TeamSignup).Team
+ Client.SetTeamId(rteam.Id)
if result, err := Client.LoginById(ruser.Id, user.Password); err != nil {
t.Fatal(err)
@@ -69,7 +75,9 @@ func TestCreateFromSignupTeam(t *testing.T) {
}
func TestCreateTeam(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ th.BasicClient.Logout()
+ Client := th.BasicClient
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, err := Client.CreateTeam(&team)
@@ -77,11 +85,13 @@ func TestCreateTeam(t *testing.T) {
t.Fatal(err)
}
- user := &model.User{TeamId: rteam.Data.(*model.Team).Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ LinkUserToTeam(user, rteam.Data.(*model.Team))
store.Must(Srv.Store.User().VerifyEmail(user.Id))
Client.LoginByEmail(team.Name, user.Email, "pwd")
+ Client.SetTeamId(rteam.Data.(*model.Team).Id)
c1 := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList)
if len(c1.Channels) != 2 {
@@ -108,23 +118,144 @@ func TestCreateTeam(t *testing.T) {
}
}
+func TestAddUserToTeam(t *testing.T) {
+ th := Setup().InitBasic()
+ th.BasicClient.Logout()
+ Client := th.BasicClient
+
+ props := make(map[string]string)
+ props["email"] = strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com"
+ props["name"] = "Test Company name"
+ props["time"] = fmt.Sprintf("%v", model.GetMillis())
+
+ data := model.MapToJson(props)
+ hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt))
+
+ team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: props["email"], Type: model.TEAM_OPEN}
+ user := model.User{Email: props["email"], Nickname: "Corey Hulen", Password: "hello"}
+
+ ts := model.TeamSignup{Team: team, User: user, Invites: []string{"success+test@simulator.amazonses.com"}, Data: data, Hash: hash}
+
+ rts, err := Client.CreateTeamFromSignup(&ts)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if rts.Data.(*model.TeamSignup).Team.DisplayName != team.DisplayName {
+ t.Fatal("full name didn't match")
+ }
+
+ ruser := rts.Data.(*model.TeamSignup).User
+ rteam := rts.Data.(*model.TeamSignup).Team
+ Client.SetTeamId(rteam.Id)
+
+ if result, err := Client.LoginById(ruser.Id, user.Password); err != nil {
+ t.Fatal(err)
+ } else {
+ if result.Data.(*model.User).Email != user.Email {
+ t.Fatal("email's didn't match")
+ }
+ }
+
+ user2 := th.CreateUser(th.BasicClient)
+ if result, err := th.BasicClient.AddUserToTeam(user2.Id); err != nil {
+ t.Fatal(err)
+ } else {
+ rm := result.Data.(map[string]string)
+ if rm["user_id"] != user2.Id {
+ t.Fatal("email's didn't match")
+ }
+ }
+}
+
+func TestAddUserToTeamFromInvite(t *testing.T) {
+ th := Setup().InitBasic()
+ th.BasicClient.Logout()
+ Client := th.BasicClient
+
+ props := make(map[string]string)
+ props["email"] = strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com"
+ props["name"] = "Test Company name"
+ props["time"] = fmt.Sprintf("%v", model.GetMillis())
+
+ data := model.MapToJson(props)
+ hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt))
+
+ team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: props["email"], Type: model.TEAM_OPEN}
+ user := model.User{Email: props["email"], Nickname: "Corey Hulen", Password: "hello"}
+
+ ts := model.TeamSignup{Team: team, User: user, Invites: []string{"success+test@simulator.amazonses.com"}, Data: data, Hash: hash}
+
+ rts, err := Client.CreateTeamFromSignup(&ts)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if rts.Data.(*model.TeamSignup).Team.DisplayName != team.DisplayName {
+ t.Fatal("full name didn't match")
+ }
+
+ ruser := rts.Data.(*model.TeamSignup).User
+ rteam := rts.Data.(*model.TeamSignup).Team
+ Client.SetTeamId(rteam.Id)
+
+ if result, err := Client.LoginById(ruser.Id, user.Password); err != nil {
+ t.Fatal(err)
+ } else {
+ if result.Data.(*model.User).Email != user.Email {
+ t.Fatal("email's didn't match")
+ }
+ }
+
+ user2 := th.CreateUser(th.BasicClient)
+ Client.Must(Client.Logout())
+ Client.Must(Client.LoginByEmail("", user2.Email, user2.Password))
+
+ if result, err := th.BasicClient.AddUserToTeamFromInvite("", "", rteam.InviteId); err != nil {
+ t.Fatal(err)
+ } else {
+ rtm := result.Data.(*model.Team)
+ if rtm.Id != rteam.Id {
+ t.Fatal()
+ }
+ }
+}
+
func TestGetAllTeams(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ th.BasicClient.Logout()
+ Client := th.BasicClient
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN, AllowTeamListing: true}
+ team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ LinkUserToTeam(user, team)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
Client.LoginByEmail(team.Name, user.Email, "pwd")
+ Client.SetTeamId(team.Id)
+
+ if r1, err := Client.GetAllTeams(); err != nil {
+ t.Fatal(err)
+ } else {
+ teams := r1.Data.(map[string]*model.Team)
+ if teams[team.Id].Name != team.Name {
+ t.Fatal()
+ }
+ if teams[team.Id].Email != "" {
+ t.Fatal("Non admin users shoudn't get full listings")
+ }
+ }
+
+ c := &Context{}
+ c.RequestId = model.NewId()
+ c.IpAddress = "cmd_line"
+ UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
- enableIncomingHooks := *utils.Cfg.TeamSettings.EnableTeamListing
- defer func() {
- *utils.Cfg.TeamSettings.EnableTeamListing = enableIncomingHooks
- }()
- *utils.Cfg.TeamSettings.EnableTeamListing = true
+ Client.LoginByEmail(team.Name, user.Email, "pwd")
+ Client.SetTeamId(team.Id)
if r1, err := Client.GetAllTeams(); err != nil {
t.Fatal(err)
@@ -133,6 +264,35 @@ func TestGetAllTeams(t *testing.T) {
if teams[team.Id].Name != team.Name {
t.Fatal()
}
+ if teams[team.Id].Email != team.Email {
+ t.Fatal()
+ }
+ }
+}
+
+func TestGetAllTeamListings(t *testing.T) {
+ th := Setup().InitBasic()
+ th.BasicClient.Logout()
+ Client := th.BasicClient
+
+ team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN, AllowOpenInvite: true}
+ team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
+
+ user := &model.User{Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ LinkUserToTeam(user, team)
+ store.Must(Srv.Store.User().VerifyEmail(user.Id))
+
+ Client.LoginByEmail(team.Name, user.Email, "pwd")
+ Client.SetTeamId(team.Id)
+
+ if r1, err := Client.GetAllTeamListings(); err != nil {
+ t.Fatal(err)
+ } else {
+ teams := r1.Data.(map[string]*model.Team)
+ if teams[team.Id].Name != team.Name {
+ t.Fatal()
+ }
if teams[team.Id].Email != "" {
t.Fatal("Non admin users shoudn't get full listings")
}
@@ -144,6 +304,7 @@ func TestGetAllTeams(t *testing.T) {
UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
Client.LoginByEmail(team.Name, user.Email, "pwd")
+ Client.SetTeamId(team.Id)
if r1, err := Client.GetAllTeams(); err != nil {
t.Fatal(err)
@@ -159,16 +320,20 @@ func TestGetAllTeams(t *testing.T) {
}
func TestTeamPermDelete(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ th.BasicClient.Logout()
+ Client := th.BasicClient
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
+ LinkUserToTeam(user1, team)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
+ Client.SetTeamId(team.Id)
channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
@@ -198,19 +363,23 @@ func TestTeamPermDelete(t *testing.T) {
}
func TestInviteMembers(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ th.BasicClient.Logout()
+ Client := th.BasicClient
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ LinkUserToTeam(user, team)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
Client.LoginByEmail(team.Name, user.Email, "pwd")
+ Client.SetTeamId(team.Id)
invite := make(map[string]string)
- invite["email"] = model.NewId() + "success+test@simulator.amazonses.com"
+ invite["email"] = "success+" + model.NewId() + "@simulator.amazonses.com"
invite["first_name"] = "Test"
invite["last_name"] = "Guy"
invites := &model.Invites{Invites: []map[string]string{invite}}
@@ -227,20 +396,25 @@ func TestInviteMembers(t *testing.T) {
}
func TestUpdateTeamDisplayName(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ th.BasicClient.Logout()
+ Client := th.BasicClient
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
+ team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: "test@nowhere.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{Email: team.Email, Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ LinkUserToTeam(user, team)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
+ LinkUserToTeam(user2, team)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
Client.LoginByEmail(team.Name, user2.Email, "pwd")
+ Client.SetTeamId(team.Id)
vteam := &model.Team{DisplayName: team.DisplayName, Name: team.Name, Email: team.Email, Type: team.Type}
vteam.DisplayName = "NewName"
@@ -262,6 +436,9 @@ func TestUpdateTeamDisplayName(t *testing.T) {
}
func TestFuzzyTeamCreate(t *testing.T) {
+ th := Setup().InitBasic()
+ th.BasicClient.Logout()
+ Client := th.BasicClient
for i := 0; i < len(utils.FUZZY_STRINGS_NAMES) || i < len(utils.FUZZY_STRINGS_EMAILS); i++ {
testDisplayName := "Name"
@@ -284,19 +461,24 @@ func TestFuzzyTeamCreate(t *testing.T) {
}
func TestGetMyTeam(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ th.BasicClient.Logout()
+ Client := th.BasicClient
- team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- rteam, _ := Client.CreateTeam(&team)
+ team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
+ rteam, _ := Client.CreateTeam(team)
+ team = rteam.Data.(*model.Team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser, _ := Client.CreateUser(&user, "")
+ LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team))
store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id))
Client.LoginByEmail(team.Name, user.Email, user.Password)
+ Client.SetTeamId(team.Id)
if result, err := Client.GetMyTeam(""); err != nil {
- t.Fatal("Failed to get user")
+ t.Fatal(err)
} else {
if result.Data.(*model.Team).DisplayName != team.DisplayName {
t.Fatal("team names did not match")
@@ -309,3 +491,14 @@ func TestGetMyTeam(t *testing.T) {
}
}
}
+
+func TestGetTeamMembers(t *testing.T) {
+ th := Setup().InitBasic()
+
+ if result, err := th.BasicClient.GetTeamMembers(th.BasicTeam.Id); err != nil {
+ t.Fatal(err)
+ } else {
+ members := result.Data.([]*model.TeamMember)
+ t.Log(members)
+ }
+}
diff --git a/api/user.go b/api/user.go
index 08d096c51..16ba45dc4 100644
--- a/api/user.go
+++ b/api/user.go
@@ -5,8 +5,6 @@ package api
import (
"bytes"
- "crypto/tls"
- b64 "encoding/base64"
"fmt"
l4g "github.com/alecthomas/log4go"
"github.com/disintegration/imaging"
@@ -34,45 +32,44 @@ import (
"time"
)
-func InitUser(r *mux.Router) {
+func InitUser() {
l4g.Debug(utils.T("api.user.init.debug"))
- sr := r.PathPrefix("/users").Subrouter()
- sr.Handle("/create", ApiAppHandler(createUser)).Methods("POST")
- sr.Handle("/update", ApiUserRequired(updateUser)).Methods("POST")
- sr.Handle("/update_roles", ApiUserRequired(updateRoles)).Methods("POST")
- sr.Handle("/update_active", ApiUserRequired(updateActive)).Methods("POST")
- sr.Handle("/update_notify", ApiUserRequired(updateUserNotify)).Methods("POST")
- sr.Handle("/newpassword", ApiUserRequired(updatePassword)).Methods("POST")
- sr.Handle("/send_password_reset", ApiAppHandler(sendPasswordReset)).Methods("POST")
- sr.Handle("/reset_password", ApiAppHandler(resetPassword)).Methods("POST")
- sr.Handle("/login", ApiAppHandler(login)).Methods("POST")
- sr.Handle("/logout", ApiUserRequired(logout)).Methods("POST")
- sr.Handle("/login_ldap", ApiAppHandler(loginLdap)).Methods("POST")
- sr.Handle("/revoke_session", ApiUserRequired(revokeSession)).Methods("POST")
- sr.Handle("/attach_device", ApiUserRequired(attachDeviceId)).Methods("POST")
- sr.Handle("/verify_email", ApiAppHandler(verifyEmail)).Methods("POST")
- sr.Handle("/resend_verification", ApiAppHandler(resendVerification)).Methods("POST")
- sr.Handle("/mfa", ApiAppHandler(checkMfa)).Methods("POST")
- sr.Handle("/generate_mfa_qr", ApiUserRequiredTrustRequester(generateMfaQrCode)).Methods("GET")
- sr.Handle("/update_mfa", ApiUserRequired(updateMfa)).Methods("POST")
-
- sr.Handle("/newimage", ApiUserRequired(uploadProfileImage)).Methods("POST")
-
- sr.Handle("/me", ApiAppHandler(getMe)).Methods("GET")
- sr.Handle("/me_logged_in", ApiAppHandler(getMeLoggedIn)).Methods("GET")
- sr.Handle("/status", ApiUserRequiredActivity(getStatuses, false)).Methods("POST")
- sr.Handle("/profiles", ApiUserRequired(getProfiles)).Methods("GET")
- sr.Handle("/profiles/{id:[A-Za-z0-9]+}", ApiUserRequired(getProfiles)).Methods("GET")
- sr.Handle("/{id:[A-Za-z0-9]+}", ApiUserRequired(getUser)).Methods("GET")
- sr.Handle("/{id:[A-Za-z0-9]+}/sessions", ApiUserRequired(getSessions)).Methods("GET")
- sr.Handle("/{id:[A-Za-z0-9]+}/audits", ApiUserRequired(getAudits)).Methods("GET")
- sr.Handle("/{id:[A-Za-z0-9]+}/image", ApiUserRequiredTrustRequester(getProfileImage)).Methods("GET")
-
- sr.Handle("/claim/email_to_oauth", ApiAppHandler(emailToOAuth)).Methods("POST")
- sr.Handle("/claim/oauth_to_email", ApiUserRequired(oauthToEmail)).Methods("POST")
- sr.Handle("/claim/email_to_ldap", ApiAppHandler(emailToLdap)).Methods("POST")
- sr.Handle("/claim/ldap_to_email", ApiAppHandler(ldapToEmail)).Methods("POST")
+ BaseRoutes.Users.Handle("/create", ApiAppHandler(createUser)).Methods("POST")
+ BaseRoutes.Users.Handle("/update", ApiUserRequired(updateUser)).Methods("POST")
+ BaseRoutes.Users.Handle("/update_roles", ApiUserRequired(updateRoles)).Methods("POST")
+ BaseRoutes.Users.Handle("/update_active", ApiUserRequired(updateActive)).Methods("POST")
+ BaseRoutes.Users.Handle("/update_notify", ApiUserRequired(updateUserNotify)).Methods("POST")
+ BaseRoutes.Users.Handle("/newpassword", ApiUserRequired(updatePassword)).Methods("POST")
+ BaseRoutes.Users.Handle("/send_password_reset", ApiAppHandler(sendPasswordReset)).Methods("POST")
+ BaseRoutes.Users.Handle("/reset_password", ApiAppHandler(resetPassword)).Methods("POST")
+ BaseRoutes.Users.Handle("/login", ApiAppHandler(login)).Methods("POST")
+ BaseRoutes.Users.Handle("/logout", ApiAppHandler(logout)).Methods("POST")
+ BaseRoutes.Users.Handle("/login_ldap", ApiAppHandler(loginLdap)).Methods("POST")
+ BaseRoutes.Users.Handle("/revoke_session", ApiUserRequired(revokeSession)).Methods("POST")
+ BaseRoutes.Users.Handle("/attach_device", ApiUserRequired(attachDeviceId)).Methods("POST")
+ BaseRoutes.Users.Handle("/verify_email", ApiAppHandler(verifyEmail)).Methods("POST")
+ BaseRoutes.Users.Handle("/resend_verification", ApiAppHandler(resendVerification)).Methods("POST")
+ BaseRoutes.Users.Handle("/newimage", ApiUserRequired(uploadProfileImage)).Methods("POST")
+ BaseRoutes.Users.Handle("/me", ApiAppHandler(getMe)).Methods("GET")
+ BaseRoutes.Users.Handle("/initial_load", ApiAppHandler(getInitialLoad)).Methods("GET")
+ BaseRoutes.Users.Handle("/status", ApiUserRequiredActivity(getStatuses, false)).Methods("POST")
+ BaseRoutes.Users.Handle("/direct_profiles", ApiUserRequired(getDirectProfiles)).Methods("GET")
+ BaseRoutes.Users.Handle("/profiles/{id:[A-Za-z0-9]+}", ApiUserRequired(getProfiles)).Methods("GET")
+
+ BaseRoutes.Users.Handle("/mfa", ApiAppHandler(checkMfa)).Methods("POST")
+ BaseRoutes.Users.Handle("/generate_mfa_qr", ApiUserRequiredTrustRequester(generateMfaQrCode)).Methods("GET")
+ BaseRoutes.Users.Handle("/update_mfa", ApiUserRequired(updateMfa)).Methods("POST")
+
+ BaseRoutes.Users.Handle("/claim/email_to_oauth", ApiAppHandler(emailToOAuth)).Methods("POST")
+ BaseRoutes.Users.Handle("/claim/oauth_to_email", ApiUserRequired(oauthToEmail)).Methods("POST")
+ BaseRoutes.Users.Handle("/claim/email_to_ldap", ApiAppHandler(emailToLdap)).Methods("POST")
+ BaseRoutes.Users.Handle("/claim/ldap_to_email", ApiAppHandler(ldapToEmail)).Methods("POST")
+
+ BaseRoutes.NeedUser.Handle("/get", ApiUserRequired(getUser)).Methods("GET")
+ BaseRoutes.NeedUser.Handle("/sessions", ApiUserRequired(getSessions)).Methods("GET")
+ BaseRoutes.NeedUser.Handle("/audits", ApiUserRequired(getAudits)).Methods("GET")
+ BaseRoutes.NeedUser.Handle("/image", ApiUserRequiredTrustRequester(getProfileImage)).Methods("GET")
}
func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -89,24 +86,13 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- // the user's username is checked to be valid when they are saved to the database
-
- user.EmailVerified = false
-
- var team *model.Team
-
- if result := <-Srv.Store.Team().Get(user.TeamId); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- team = result.Data.(*model.Team)
- }
-
hash := r.URL.Query().Get("h")
-
+ teamId := ""
+ var team *model.Team
sendWelcomeEmail := true
+ user.EmailVerified = false
- if IsVerifyHashRequired(user, team, hash) {
+ if len(hash) > 0 {
data := r.URL.Query().Get("d")
props := model.MapFromJson(strings.NewReader(data))
@@ -121,9 +107,14 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if user.TeamId != props["id"] {
- c.Err = model.NewLocAppError("createUser", "api.user.create_user.team_name.app_error", nil, data)
+ teamId = props["id"]
+
+ // try to load the team to make sure it exists
+ if result := <-Srv.Store.Team().Get(teamId); result.Err != nil {
+ c.Err = result.Err
return
+ } else {
+ team = result.Data.(*model.Team)
}
user.Email = props["email"]
@@ -131,8 +122,33 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
sendWelcomeEmail = false
}
- if user.IsSSOUser() {
- user.EmailVerified = true
+ inviteId := r.URL.Query().Get("iid")
+ if len(inviteId) > 0 {
+ if result := <-Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ team = result.Data.(*model.Team)
+ teamId = team.Id
+ }
+ }
+
+ firstAccount := false
+ if sessionCache.Len() == 0 {
+ if cr := <-Srv.Store.User().GetTotalUsersCount(); cr.Err != nil {
+ c.Err = cr.Err
+ return
+ } else {
+ count := cr.Data.(int64)
+ if count <= 0 {
+ firstAccount = true
+ }
+ }
+ }
+
+ if !firstAccount && !*utils.Cfg.TeamSettings.EnableOpenServer && len(teamId) == 0 {
+ c.Err = model.NewLocAppError("createUser", "api.user.create_user.no_open_server", nil, "email="+user.Email)
+ return
}
if !CheckUserDomain(user, utils.Cfg.TeamSettings.RestrictCreationToDomains) {
@@ -140,14 +156,24 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- ruser, err := CreateUser(team, user)
+ ruser, err := CreateUser(user)
if err != nil {
c.Err = err
return
}
+ if len(teamId) > 0 {
+ err := JoinUserToTeam(team, ruser)
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ addDirectChannelsAndForget(team.Id, ruser)
+ }
+
if sendWelcomeEmail {
- sendWelcomeEmailAndForget(c, ruser.Id, ruser.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team), ruser.EmailVerified)
+ sendWelcomeEmailAndForget(c, ruser.Id, ruser.Email, c.GetSiteURL(), ruser.EmailVerified)
}
w.Write([]byte(ruser.ToJson()))
@@ -196,26 +222,19 @@ func IsVerifyHashRequired(user *model.User, team *model.Team, hash string) bool
return shouldVerifyHash
}
-func CreateUser(team *model.Team, user *model.User) (*model.User, *model.AppError) {
+func CreateUser(user *model.User) (*model.User, *model.AppError) {
- channelRole := ""
- if team.Email == user.Email {
- user.Roles = model.ROLE_TEAM_ADMIN
- channelRole = model.CHANNEL_ROLE_ADMIN
-
- // Below is a speical case where the first user in the entire
- // system is granted the system_admin role instead of admin
- if result := <-Srv.Store.User().GetTotalUsersCount(); result.Err != nil {
- return nil, result.Err
- } else {
- count := result.Data.(int64)
- if count <= 0 {
- user.Roles = model.ROLE_SYSTEM_ADMIN
- }
- }
+ user.Roles = ""
+ // Below is a speical case where the first user in the entire
+ // system is granted the system_admin role instead of admin
+ if result := <-Srv.Store.User().GetTotalUsersCount(); result.Err != nil {
+ return nil, result.Err
} else {
- user.Roles = ""
+ count := result.Data.(int64)
+ if count <= 0 {
+ user.Roles = model.ROLE_SYSTEM_ADMIN
+ }
}
user.MakeNonNil()
@@ -226,13 +245,6 @@ func CreateUser(team *model.Team, user *model.User) (*model.User, *model.AppErro
} else {
ruser := result.Data.(*model.User)
- // Soft error if there is an issue joining the default channels
- if err := JoinDefaultChannels(ruser, channelRole); err != nil {
- l4g.Error(utils.T("api.user.create_user.joining.error"), ruser.Id, ruser.TeamId, err)
- }
-
- addDirectChannelsAndForget(ruser)
-
if user.EmailVerified {
if cresult := <-Srv.Store.User().VerifyEmail(ruser.Id); cresult.Err != nil {
l4g.Error(utils.T("api.user.create_user.verified.error"), cresult.Err)
@@ -247,15 +259,13 @@ func CreateUser(team *model.Team, user *model.User) (*model.User, *model.AppErro
ruser.Sanitize(map[string]bool{})
// This message goes to every channel, so the channelId is irrelevant
- message := model.NewMessage(team.Id, "", ruser.Id, model.ACTION_NEW_USER)
-
- PublishAndForget(message)
+ PublishAndForget(model.NewMessage("", "", ruser.Id, model.ACTION_NEW_USER))
return ruser, nil
}
}
-func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.Reader, team *model.Team) *model.User {
+func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.Reader, teamId string) *model.User {
var user *model.User
provider := einterfaces.GetOauthProvider(service)
if provider == nil {
@@ -270,49 +280,59 @@ func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service
return nil
}
- suchan := Srv.Store.User().GetByAuth(team.Id, user.AuthData, service)
- euchan := Srv.Store.User().GetByEmail(team.Id, user.Email)
+ suchan := Srv.Store.User().GetByAuth(user.AuthData, service)
+ euchan := Srv.Store.User().GetByEmail(user.Email)
- if team.Email == "" {
- team.Email = user.Email
- if result := <-Srv.Store.Team().Update(team); result.Err != nil {
- c.Err = result.Err
- return nil
- }
- } else {
- found := true
- count := 0
- for found {
- if found = IsUsernameTaken(user.Username, team.Id); c.Err != nil {
- return nil
- } else if found {
- user.Username = user.Username + strconv.Itoa(count)
- count += 1
- }
+ var tchan store.StoreChannel
+ if len(teamId) != 0 {
+ tchan = Srv.Store.Team().Get(teamId)
+ }
+
+ found := true
+ count := 0
+ for found {
+ if found = IsUsernameTaken(user.Username); found {
+ user.Username = user.Username + strconv.Itoa(count)
+ count += 1
}
}
if result := <-suchan; result.Err == nil {
- c.Err = model.NewLocAppError("signupCompleteOAuth", "api.user.create_oauth_user.already_used.app_error",
- map[string]interface{}{"Service": service, "DisplayName": team.DisplayName}, "email="+user.Email)
+ c.Err = model.NewLocAppError("CreateOAuthUser", "api.user.create_oauth_user.already_used.app_error",
+ map[string]interface{}{"Service": service}, "email="+user.Email)
return nil
}
if result := <-euchan; result.Err == nil {
- c.Err = model.NewLocAppError("signupCompleteOAuth", "api.user.create_oauth_user.already_attached.app_error",
- map[string]interface{}{"Service": service, "DisplayName": team.DisplayName}, "email="+user.Email)
+ c.Err = model.NewLocAppError("CreateOAuthUser", "api.user.create_oauth_user.already_attached.app_error",
+ map[string]interface{}{"Service": service}, "email="+user.Email)
return nil
}
- user.TeamId = team.Id
user.EmailVerified = true
- ruser, err := CreateUser(team, user)
+ ruser, err := CreateUser(user)
if err != nil {
c.Err = err
return nil
}
+ if tchan != nil {
+ if result := <-tchan; result.Err != nil {
+ c.Err = result.Err
+ return nil
+ } else {
+ team := result.Data.(*model.Team)
+ err = JoinUserToTeam(team, user)
+ if err != nil {
+ c.Err = err
+ return nil
+ }
+
+ addDirectChannelsAndForget(team.Id, user)
+ }
+ }
+
Login(c, w, r, ruser, "")
if c.Err != nil {
return nil
@@ -321,23 +341,23 @@ func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service
return ruser
}
-func sendWelcomeEmailAndForget(c *Context, userId, email, teamName, teamDisplayName, siteURL, teamURL string, verified bool) {
+func sendWelcomeEmailAndForget(c *Context, userId string, email string, siteURL string, verified bool) {
go func() {
subjectPage := utils.NewHTMLTemplate("welcome_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.welcome_subject", map[string]interface{}{"TeamDisplayName": teamDisplayName})
+ subjectPage.Props["Subject"] = c.T("api.templates.welcome_subject", map[string]interface{}{"TeamDisplayName": siteURL})
bodyPage := utils.NewHTMLTemplate("welcome_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
- bodyPage.Props["Title"] = c.T("api.templates.welcome_body.title", map[string]interface{}{"TeamDisplayName": teamDisplayName})
+ bodyPage.Props["Title"] = c.T("api.templates.welcome_body.title", map[string]interface{}{"TeamDisplayName": siteURL})
bodyPage.Props["Info"] = c.T("api.templates.welcome_body.info")
bodyPage.Props["Button"] = c.T("api.templates.welcome_body.button")
bodyPage.Props["Info2"] = c.T("api.templates.welcome_body.info2")
bodyPage.Props["Info3"] = c.T("api.templates.welcome_body.info3")
- bodyPage.Props["TeamURL"] = teamURL
+ bodyPage.Props["TeamURL"] = siteURL
if !verified {
- link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&teamname=%s&email=%s", siteURL, userId, model.HashPassword(userId), teamName, email)
+ link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&email=%s", siteURL, userId, model.HashPassword(userId), email)
bodyPage.Props["VerifyUrl"] = link
}
@@ -347,11 +367,11 @@ func sendWelcomeEmailAndForget(c *Context, userId, email, teamName, teamDisplayN
}()
}
-func addDirectChannelsAndForget(user *model.User) {
+func addDirectChannelsAndForget(teamId string, user *model.User) {
go func() {
var profiles map[string]*model.User
- if result := <-Srv.Store.User().GetProfiles(user.TeamId); result.Err != nil {
- l4g.Error(utils.T("api.user.add_direct_channels_and_forget.failed.error"), user.Id, user.TeamId, result.Err.Error())
+ if result := <-Srv.Store.User().GetProfiles(teamId); result.Err != nil {
+ l4g.Error(utils.T("api.user.add_direct_channels_and_forget.failed.error"), user.Id, teamId, result.Err.Error())
return
} else {
profiles = result.Data.(map[string]*model.User)
@@ -381,23 +401,23 @@ func addDirectChannelsAndForget(user *model.User) {
}
if result := <-Srv.Store.Preference().Save(&preferences); result.Err != nil {
- l4g.Error(utils.T("api.user.add_direct_channels_and_forget.failed.error"), user.Id, user.TeamId, result.Err.Error())
+ l4g.Error(utils.T("api.user.add_direct_channels_and_forget.failed.error"), user.Id, teamId, result.Err.Error())
}
}()
}
-func SendVerifyEmailAndForget(c *Context, userId, userEmail, teamName, teamDisplayName, siteURL, teamURL string) {
+func SendVerifyEmailAndForget(c *Context, userId, userEmail, siteURL string) {
go func() {
- link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&teamname=%s&email=%s", siteURL, userId, model.HashPassword(userId), teamName, userEmail)
+ link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&email=%s", siteURL, userId, model.HashPassword(userId), userEmail)
subjectPage := utils.NewHTMLTemplate("verify_subject", c.Locale)
subjectPage.Props["Subject"] = c.T("api.templates.verify_subject",
- map[string]interface{}{"TeamDisplayName": teamDisplayName, "SiteName": utils.ClientCfg["SiteName"]})
+ map[string]interface{}{"TeamDisplayName": utils.ClientCfg["SiteName"], "SiteName": utils.ClientCfg["SiteName"]})
bodyPage := utils.NewHTMLTemplate("verify_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
- bodyPage.Props["Title"] = c.T("api.templates.verify_body.title", map[string]interface{}{"TeamDisplayName": teamDisplayName})
+ bodyPage.Props["Title"] = c.T("api.templates.verify_body.title", map[string]interface{}{"TeamDisplayName": utils.ClientCfg["SiteName"]})
bodyPage.Props["Info"] = c.T("api.templates.verify_body.info")
bodyPage.Props["VerifyUrl"] = link
bodyPage.Props["Button"] = c.T("api.templates.verify_body.button")
@@ -408,6 +428,54 @@ func SendVerifyEmailAndForget(c *Context, userId, userEmail, teamName, teamDispl
}()
}
+func login(c *Context, w http.ResponseWriter, r *http.Request) {
+ props := model.MapFromJson(r.Body)
+
+ if len(props["password"]) == 0 {
+ c.Err = model.NewLocAppError("login", "api.user.login.blank_pwd.app_error", nil, "")
+ c.Err.StatusCode = http.StatusBadRequest
+ return
+ }
+
+ var user *model.User
+ if len(props["id"]) != 0 {
+ user = LoginById(c, w, r, props["id"], props["password"], props["token"], props["device_id"])
+ } else if len(props["email"]) != 0 {
+ user = LoginByEmail(c, w, r, props["email"], props["name"], props["password"], props["token"], props["device_id"])
+ } else if len(props["username"]) != 0 {
+ user = LoginByUsername(c, w, r, props["username"], props["name"], props["password"], props["token"], props["device_id"])
+ } else {
+ c.Err = model.NewLocAppError("login", "api.user.login.not_provided.app_error", nil, "")
+ c.Err.StatusCode = http.StatusBadRequest
+ return
+ }
+
+ if c.Err != nil {
+ return
+ }
+
+ if user != nil {
+ user.Sanitize(map[string]bool{})
+ } else {
+ user = &model.User{}
+ }
+ w.Write([]byte(user.ToJson()))
+}
+
+func doUserPasswordAuthenticationAndLogin(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, password string, mfaToken string, deviceId string) bool {
+ c.LogAuditWithUserId(user.Id, "attempt")
+ if err := checkPasswordAndAllCriteria(user, password, mfaToken); err != nil {
+ c.LogAuditWithUserId(user.Id, "fail")
+ c.Err = err
+ c.Err.StatusCode = http.StatusUnauthorized
+ return false
+ } else {
+ Login(c, w, r, user, deviceId)
+ c.LogAuditWithUserId(user.Id, "success")
+ return true
+ }
+}
+
func LoginById(c *Context, w http.ResponseWriter, r *http.Request, userId, password, mfaToken, deviceId string) *model.User {
if result := <-Srv.Store.User().Get(userId); result.Err != nil {
c.Err = result.Err
@@ -415,8 +483,13 @@ func LoginById(c *Context, w http.ResponseWriter, r *http.Request, userId, passw
} else {
user := result.Data.(*model.User)
- if authenticateUserPasswordAndToken(c, user, password, mfaToken) {
- Login(c, w, r, user, deviceId)
+ if len(user.AuthData) != 0 {
+ c.Err = model.NewLocAppError("LoginById", "api.user.login_by_email.sign_in.app_error",
+ map[string]interface{}{"AuthService": user.AuthService}, "")
+ return nil
+ }
+
+ if doUserPasswordAuthenticationAndLogin(c, w, r, user, password, mfaToken, deviceId) {
return user
}
}
@@ -425,18 +498,9 @@ func LoginById(c *Context, w http.ResponseWriter, r *http.Request, userId, passw
}
func LoginByEmail(c *Context, w http.ResponseWriter, r *http.Request, email, name, password, mfaToken, deviceId string) *model.User {
- var team *model.Team
-
- if result := <-Srv.Store.Team().GetByName(name); result.Err != nil {
+ if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil {
c.Err = result.Err
- return nil
- } else {
- team = result.Data.(*model.Team)
- }
-
- if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil {
- c.Err = result.Err
- c.Err.StatusCode = http.StatusForbidden
+ c.Err.StatusCode = http.StatusUnauthorized
return nil
} else {
user := result.Data.(*model.User)
@@ -447,8 +511,7 @@ func LoginByEmail(c *Context, w http.ResponseWriter, r *http.Request, email, nam
return nil
}
- if authenticateUserPasswordAndToken(c, user, password, mfaToken) {
- Login(c, w, r, user, deviceId)
+ if doUserPasswordAuthenticationAndLogin(c, w, r, user, password, mfaToken, deviceId) {
return user
}
}
@@ -457,18 +520,9 @@ func LoginByEmail(c *Context, w http.ResponseWriter, r *http.Request, email, nam
}
func LoginByUsername(c *Context, w http.ResponseWriter, r *http.Request, username, name, password, mfaToken, deviceId string) *model.User {
- var team *model.Team
-
- if result := <-Srv.Store.Team().GetByName(name); result.Err != nil {
+ if result := <-Srv.Store.User().GetByUsername(username); result.Err != nil {
c.Err = result.Err
- return nil
- } else {
- team = result.Data.(*model.Team)
- }
-
- if result := <-Srv.Store.User().GetByUsername(team.Id, username); result.Err != nil {
- c.Err = result.Err
- c.Err.StatusCode = http.StatusForbidden
+ c.Err.StatusCode = http.StatusUnauthorized
return nil
} else {
user := result.Data.(*model.User)
@@ -479,8 +533,7 @@ func LoginByUsername(c *Context, w http.ResponseWriter, r *http.Request, usernam
return nil
}
- if authenticateUserPasswordAndToken(c, user, password, mfaToken) {
- Login(c, w, r, user, deviceId)
+ if doUserPasswordAuthenticationAndLogin(c, w, r, user, password, mfaToken, deviceId) {
return user
}
}
@@ -488,7 +541,7 @@ func LoginByUsername(c *Context, w http.ResponseWriter, r *http.Request, usernam
return nil
}
-func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.Reader, team *model.Team) *model.User {
+func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.Reader) *model.User {
buf := bytes.Buffer{}
buf.ReadFrom(userData)
@@ -509,9 +562,9 @@ func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service st
}
var user *model.User
- if result := <-Srv.Store.User().GetByAuth(team.Id, authData, service); result.Err != nil {
- if result.Err.Id == store.MISSING_AUTH_ACCOUNT_ERROR && team.AllowOpenInvite {
- return CreateOAuthUser(c, w, r, service, bytes.NewReader(buf.Bytes()), team)
+ if result := <-Srv.Store.User().GetByAuth(authData, service); result.Err != nil {
+ if result.Err.Id == store.MISSING_AUTH_ACCOUNT_ERROR {
+ return CreateOAuthUser(c, w, r, service, bytes.NewReader(buf.Bytes()), "")
}
c.Err = result.Err
return nil
@@ -522,81 +575,77 @@ func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service st
}
}
-func authenticateUserPasswordAndToken(c *Context, user *model.User, password string, token string) bool {
- return checkUserLoginAttempts(c, user) && checkUserMfa(c, user, token) && checkUserPassword(c, user, password)
-}
-
-func checkUserLoginAttempts(c *Context, user *model.User) bool {
- if user.FailedAttempts >= utils.Cfg.ServiceSettings.MaximumLoginAttempts {
- c.LogAuditWithUserId(user.Id, "fail")
- c.Err = model.NewLocAppError("checkUserLoginAttempts", "api.user.check_user_login_attempts.too_many.app_error", nil, "user_id="+user.Id)
- c.Err.StatusCode = http.StatusForbidden
- return false
+func loginLdap(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !*utils.Cfg.LdapSettings.Enable {
+ c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.disabled.app_error", nil, "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
}
- return true
-}
-
-func checkUserPassword(c *Context, user *model.User, password string) bool {
- if !model.ComparePassword(user.Password, password) {
- c.LogAuditWithUserId(user.Id, "fail")
- c.Err = model.NewLocAppError("checkUserPassword", "api.user.check_user_password.invalid.app_error", nil, "user_id="+user.Id)
- c.Err.StatusCode = http.StatusForbidden
-
- if result := <-Srv.Store.User().UpdateFailedPasswordAttempts(user.Id, user.FailedAttempts+1); result.Err != nil {
- c.LogError(result.Err)
- }
+ props := model.MapFromJson(r.Body)
- return false
- } else {
- if result := <-Srv.Store.User().UpdateFailedPasswordAttempts(user.Id, 0); result.Err != nil {
- c.LogError(result.Err)
- }
+ password := props["password"]
+ id := props["id"]
+ mfaToken := props["token"]
- return true
+ if len(password) == 0 {
+ c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.blank_pwd.app_error", nil, "")
+ c.Err.StatusCode = http.StatusBadRequest
+ return
}
-}
-func checkUserMfa(c *Context, user *model.User, token string) bool {
- if !user.MfaActive || !utils.IsLicensed || !*utils.License.Features.MFA || !*utils.Cfg.ServiceSettings.EnableMultifactorAuthentication {
- return true
+ if len(id) == 0 {
+ c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.need_id.app_error", nil, "")
+ c.Err.StatusCode = http.StatusBadRequest
+ return
}
- mfaInterface := einterfaces.GetMfaInterface()
- if mfaInterface == nil {
- c.Err = model.NewLocAppError("checkUserMfa", "api.user.check_user_mfa.not_available.app_error", nil, "")
+ ldapInterface := einterfaces.GetLdapInterface()
+ if ldapInterface == nil {
+ c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.not_available.app_error", nil, "")
c.Err.StatusCode = http.StatusNotImplemented
- return false
+ return
}
- if ok, err := mfaInterface.ValidateToken(user.MfaSecret, token); err != nil {
+ user, err := ldapInterface.DoLogin(id, password)
+ if err != nil {
+ if user != nil {
+ c.LogAuditWithUserId(user.Id, "attempt")
+ c.LogAuditWithUserId(user.Id, "fail")
+ } else {
+ c.LogAudit("attempt")
+ c.LogAudit("fail")
+ }
c.Err = err
- return false
- } else if !ok {
- c.Err = model.NewLocAppError("checkUserMfa", "api.user.check_user_mfa.bad_code.app_error", nil, "")
- return false
- } else {
- return true
+ c.Err.StatusCode = http.StatusUnauthorized
+ return
}
-}
-
-// User MUST be validated before calling Login
-func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) {
c.LogAuditWithUserId(user.Id, "attempt")
- if !user.EmailVerified && utils.Cfg.EmailSettings.RequireEmailVerification {
- c.Err = model.NewLocAppError("Login", "api.user.login.not_verified.app_error", nil, "user_id="+user.Id)
- c.Err.StatusCode = http.StatusForbidden
+ if err = checkUserAdditionalAuthenticationCriteria(user, mfaToken); err != nil {
+ c.LogAuditWithUserId(user.Id, "fail")
+ c.Err = err
+ c.Err.StatusCode = http.StatusUnauthorized
return
}
- if user.DeleteAt > 0 {
- c.Err = model.NewLocAppError("Login", "api.user.login.inactive.app_error", nil, "user_id="+user.Id)
- c.Err.StatusCode = http.StatusForbidden
- return
+ // User is authenticated at this point
+
+ Login(c, w, r, user, props["device_id"])
+ c.LogAuditWithUserId(user.Id, "success")
+
+ if user != nil {
+ user.Sanitize(map[string]bool{})
+ } else {
+ user = &model.User{}
}
+ w.Write([]byte(user.ToJson()))
+}
- session := &model.Session{UserId: user.Id, TeamId: user.TeamId, Roles: user.Roles, DeviceId: deviceId, IsOAuth: false}
+// User MUST be authenticated completely before calling Login
+func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) {
+
+ session := &model.Session{UserId: user.Id, Roles: user.Roles, DeviceId: deviceId, IsOAuth: false}
maxAge := *utils.Cfg.ServiceSettings.SessionLengthWebInDays * 60 * 60 * 24
@@ -607,7 +656,7 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User,
// A special case where we logout of all other sessions with the same Id
if result := <-Srv.Store.Session().GetSessions(user.Id); result.Err != nil {
c.Err = result.Err
- c.Err.StatusCode = http.StatusForbidden
+ c.Err.StatusCode = http.StatusInternalServerError
return
} else {
sessions := result.Data.([]*model.Session)
@@ -653,7 +702,7 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User,
if result := <-Srv.Store.Session().Save(session); result.Err != nil {
c.Err = result.Err
- c.Err.StatusCode = http.StatusForbidden
+ c.Err.StatusCode = http.StatusInternalServerError
return
} else {
session = result.Data.(*model.Session)
@@ -675,110 +724,6 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User,
http.SetCookie(w, sessionCookie)
c.Session = *session
- c.LogAuditWithUserId(user.Id, "success")
-}
-
-func login(c *Context, w http.ResponseWriter, r *http.Request) {
- props := model.MapFromJson(r.Body)
-
- if len(props["password"]) == 0 {
- c.Err = model.NewLocAppError("login", "api.user.login.blank_pwd.app_error", nil, "")
- c.Err.StatusCode = http.StatusForbidden
- return
- }
-
- var user *model.User
- if len(props["id"]) != 0 {
- user = LoginById(c, w, r, props["id"], props["password"], props["token"], props["device_id"])
- } else if len(props["email"]) != 0 && len(props["name"]) != 0 {
- user = LoginByEmail(c, w, r, props["email"], props["name"], props["password"], props["token"], props["device_id"])
- } else if len(props["username"]) != 0 && len(props["name"]) != 0 {
- user = LoginByUsername(c, w, r, props["username"], props["name"], props["password"], props["token"], props["device_id"])
- } else {
- c.Err = model.NewLocAppError("login", "api.user.login.not_provided.app_error", nil, "")
- c.Err.StatusCode = http.StatusForbidden
- return
- }
-
- if c.Err != nil {
- return
- }
-
- if user != nil {
- user.Sanitize(map[string]bool{})
- } else {
- user = &model.User{}
- }
- w.Write([]byte(user.ToJson()))
-}
-
-func loginLdap(c *Context, w http.ResponseWriter, r *http.Request) {
- if !*utils.Cfg.LdapSettings.Enable {
- c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.disabled.app_error", nil, "")
- c.Err.StatusCode = http.StatusNotImplemented
- return
- }
-
- props := model.MapFromJson(r.Body)
-
- password := props["password"]
- id := props["id"]
- teamName := props["teamName"]
- mfaToken := props["token"]
-
- if len(password) == 0 {
- c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.blank_pwd.app_error", nil, "")
- c.Err.StatusCode = http.StatusForbidden
- return
- }
-
- if len(id) == 0 {
- c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.need_id.app_error", nil, "")
- c.Err.StatusCode = http.StatusForbidden
- return
- }
-
- teamc := Srv.Store.Team().GetByName(teamName)
-
- ldapInterface := einterfaces.GetLdapInterface()
- if ldapInterface == nil {
- c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.not_available.app_error", nil, "")
- c.Err.StatusCode = http.StatusNotImplemented
- return
- }
-
- var team *model.Team
- if result := <-teamc; result.Err != nil {
- c.Err = result.Err
- return
- } else {
- team = result.Data.(*model.Team)
- }
-
- user, err := ldapInterface.DoLogin(team, id, password)
- if err != nil {
- c.Err = err
- return
- }
-
- if !checkUserLoginAttempts(c, user) {
- return
- }
-
- if !checkUserMfa(c, user, mfaToken) {
- return
- }
-
- // User is authenticated at this point
-
- Login(c, w, r, user, props["device_id"])
-
- if user != nil {
- user.Sanitize(map[string]bool{})
- } else {
- user = &model.User{}
- }
- w.Write([]byte(user.ToJson()))
}
func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -805,7 +750,7 @@ func attachDeviceId(c *Context, w http.ResponseWriter, r *http.Request) {
// A special case where we logout of all other sessions with the same Id
if result := <-Srv.Store.Session().GetSessions(c.Session.UserId); result.Err != nil {
c.Err = result.Err
- c.Err.StatusCode = http.StatusForbidden
+ c.Err.StatusCode = http.StatusInternalServerError
return
} else {
sessions := result.Data.([]*model.Session)
@@ -875,7 +820,7 @@ func RevokeAllSession(c *Context, userId string) {
func getSessions(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
- id := params["id"]
+ id := params["user_id"]
if !c.HasPermissionsToUser(id, "getSessions") {
return
@@ -907,9 +852,11 @@ func logout(c *Context, w http.ResponseWriter, r *http.Request) {
func Logout(c *Context, w http.ResponseWriter, r *http.Request) {
c.LogAudit("")
c.RemoveSessionCookie(w, r)
- if result := <-Srv.Store.Session().Remove(c.Session.Id); result.Err != nil {
- c.Err = result.Err
- return
+ if c.Session.Id != "" {
+ if result := <-Srv.Store.Session().Remove(c.Session.Id); result.Err != nil {
+ c.Err = result.Err
+ return
+ }
}
}
@@ -934,29 +881,99 @@ func getMe(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
-func getMeLoggedIn(c *Context, w http.ResponseWriter, r *http.Request) {
- data := make(map[string]string)
- data["logged_in"] = "false"
- data["team_name"] = ""
+func getInitialLoad(c *Context, w http.ResponseWriter, r *http.Request) {
+
+ il := model.InitialLoad{}
+
+ var cchan store.StoreChannel
+
+ if sessionCache.Len() == 0 {
+ // Below is a speical case when intializating a new server
+ // Lets check to make sure the server is really empty
+
+ cchan = Srv.Store.User().GetTotalUsersCount()
+ }
if len(c.Session.UserId) != 0 {
- teamChan := Srv.Store.Team().Get(c.Session.TeamId)
- var team *model.Team
- if tr := <-teamChan; tr.Err != nil {
- c.Err = tr.Err
+ uchan := Srv.Store.User().Get(c.Session.UserId)
+ pchan := Srv.Store.Preference().GetAll(c.Session.UserId)
+ tchan := Srv.Store.Team().GetTeamsByUserId(c.Session.UserId)
+ dpchan := Srv.Store.User().GetDirectProfiles(c.Session.UserId)
+
+ il.TeamMembers = c.Session.TeamMembers
+
+ if ru := <-uchan; ru.Err != nil {
+ c.Err = ru.Err
+ return
+ } else {
+ il.User = ru.Data.(*model.User)
+ il.User.Sanitize(map[string]bool{})
+ }
+
+ if rp := <-pchan; rp.Err != nil {
+ c.Err = rp.Err
+ return
+ } else {
+ il.Preferences = rp.Data.(model.Preferences)
+ }
+
+ if rt := <-tchan; rt.Err != nil {
+ c.Err = rt.Err
+ return
+ } else {
+ il.Teams = rt.Data.([]*model.Team)
+
+ for _, team := range il.Teams {
+ team.Sanitize()
+ }
+ }
+
+ if dp := <-dpchan; dp.Err != nil {
+ c.Err = dp.Err
return
} else {
- team = tr.Data.(*model.Team)
+ profiles := dp.Data.(map[string]*model.User)
+
+ for k, p := range profiles {
+ options := utils.Cfg.GetSanitizeOptions()
+ options["passwordupdate"] = false
+
+ if c.IsSystemAdmin() {
+ options["fullname"] = true
+ options["email"] = true
+ } else {
+ p.ClearNonProfileFields()
+ }
+
+ p.Sanitize(options)
+ profiles[k] = p
+ }
+
+ il.DirectProfiles = profiles
}
- data["logged_in"] = "true"
- data["team_name"] = team.Name
}
- w.Write([]byte(model.MapToJson(data)))
+
+ if cchan != nil {
+ if cr := <-cchan; cr.Err != nil {
+ c.Err = cr.Err
+ return
+ } else {
+ count := cr.Data.(int64)
+ if count <= 0 {
+ il.NoAccounts = true
+ }
+ }
+ }
+
+ il.ClientCfg = utils.ClientCfg
+ il.LicenseCfg = utils.ClientLicense
+
+ w.Write([]byte(il.ToJson()))
}
func getUser(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
- id := params["id"]
+ id := params["user_id"]
if !c.HasPermissionsToUser(id, "getUser") {
return
@@ -977,17 +994,12 @@ func getUser(c *Context, w http.ResponseWriter, r *http.Request) {
func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
- id, ok := params["id"]
- if ok {
- // You must be system admin to access another team
- if id != c.Session.TeamId {
- if !c.HasSystemAdminPermissions("getProfiles") {
- return
- }
- }
+ id := params["id"]
- } else {
- id = c.Session.TeamId
+ if c.Session.GetTeamByTeamId(id) == nil {
+ if !c.HasSystemAdminPermissions("getProfiles") {
+ return
+ }
}
etag := (<-Srv.Store.User().GetEtagForProfiles(id)).Data.(string)
@@ -1008,10 +1020,44 @@ func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
if c.IsSystemAdmin() {
options["fullname"] = true
options["email"] = true
+ } else {
+ p.ClearNonProfileFields()
+ }
+
+ p.Sanitize(options)
+ profiles[k] = p
+ }
+
+ w.Header().Set(model.HEADER_ETAG_SERVER, etag)
+ w.Write([]byte(model.UserMapToJson(profiles)))
+ return
+ }
+}
+
+func getDirectProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
+ etag := (<-Srv.Store.User().GetEtagForDirectProfiles(c.Session.UserId)).Data.(string)
+ if HandleEtag(etag, w, r) {
+ return
+ }
+
+ if result := <-Srv.Store.User().GetDirectProfiles(c.Session.UserId); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ profiles := result.Data.(map[string]*model.User)
+
+ for k, p := range profiles {
+ options := utils.Cfg.GetSanitizeOptions()
+ options["passwordupdate"] = false
+
+ if c.IsSystemAdmin() {
+ options["fullname"] = true
+ options["email"] = true
+ } else {
+ p.ClearNonProfileFields()
}
p.Sanitize(options)
- p.ClearNonProfileFields()
profiles[k] = p
}
@@ -1023,7 +1069,7 @@ func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
func getAudits(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
- id := params["id"]
+ id := params["user_id"]
if !c.HasPermissionsToUser(id, "getAudits") {
return
@@ -1133,7 +1179,7 @@ func createProfileImage(username string, userId string) ([]byte, *model.AppError
func getProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
- id := params["id"]
+ id := params["user_id"]
if result := <-Srv.Store.User().Get(id); result.Err != nil {
c.Err = result.Err
@@ -1148,7 +1194,7 @@ func getProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
} else {
- path := "teams/" + c.Session.TeamId + "/users/" + id + "/profile.png"
+ path := "/users/" + id + "/profile.png"
if data, err := ReadFile(path); err != nil {
@@ -1249,7 +1295,7 @@ func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- path := "teams/" + c.Session.TeamId + "/users/" + c.Session.UserId + "/profile.png"
+ path := "users/" + c.Session.UserId + "/profile.png"
if err := WriteFile(buf.Bytes(), path); err != nil {
c.Err = model.NewLocAppError("uploadProfileImage", "api.user.upload_profile_user.upload_profile.app_error", nil, "")
@@ -1285,15 +1331,10 @@ func updateUser(c *Context, w http.ResponseWriter, r *http.Request) {
rusers := result.Data.([2]*model.User)
if rusers[0].Email != rusers[1].Email {
- if tresult := <-Srv.Store.Team().Get(rusers[1].TeamId); tresult.Err != nil {
- l4g.Error(tresult.Err.Message)
- } else {
- team := tresult.Data.(*model.Team)
- sendEmailChangeEmailAndForget(c, rusers[1].Email, rusers[0].Email, team.DisplayName, c.GetTeamURLFromTeam(team), c.GetSiteURL())
+ sendEmailChangeEmailAndForget(c, rusers[1].Email, rusers[0].Email, c.GetSiteURL())
- if utils.Cfg.EmailSettings.RequireEmailVerification {
- SendEmailChangeVerifyEmailAndForget(c, rusers[0].Id, rusers[0].Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
- }
+ if utils.Cfg.EmailSettings.RequireEmailVerification {
+ SendEmailChangeVerifyEmailAndForget(c, rusers[0].Id, rusers[0].Email, c.GetSiteURL())
}
}
@@ -1346,12 +1387,10 @@ func updatePassword(c *Context, w http.ResponseWriter, r *http.Request) {
user := result.Data.(*model.User)
- tchan := Srv.Store.Team().Get(user.TeamId)
-
if user.AuthData != "" {
c.LogAudit("failed - tried to update user password who was logged in through oauth")
c.Err = model.NewLocAppError("updatePassword", "api.user.update_password.oauth.app_error", nil, "auth_service="+user.AuthService)
- c.Err.StatusCode = http.StatusForbidden
+ c.Err.StatusCode = http.StatusBadRequest
return
}
@@ -1363,17 +1402,11 @@ func updatePassword(c *Context, w http.ResponseWriter, r *http.Request) {
if uresult := <-Srv.Store.User().UpdatePassword(c.Session.UserId, model.HashPassword(newPassword)); uresult.Err != nil {
c.Err = model.NewLocAppError("updatePassword", "api.user.update_password.failed.app_error", nil, uresult.Err.Error())
- c.Err.StatusCode = http.StatusForbidden
return
} else {
c.LogAudit("completed")
- if tresult := <-tchan; tresult.Err != nil {
- l4g.Error(tresult.Err.Message)
- } else {
- team := tresult.Data.(*model.Team)
- sendPasswordChangeEmailAndForget(c, user.Email, team.DisplayName, c.GetTeamURLFromTeam(team), c.GetSiteURL(), c.T("api.user.update_password.menu"))
- }
+ sendPasswordChangeEmailAndForget(c, user.Email, c.GetSiteURL(), c.T("api.user.update_password.menu"))
data := make(map[string]string)
data["user_id"] = uresult.Data.(string)
@@ -1391,11 +1424,18 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) {
}
new_roles := props["new_roles"]
- if !model.IsValidRoles(new_roles) {
+ if !model.IsValidUserRoles(new_roles) {
c.SetInvalidParam("updateRoles", "new_roles")
return
}
+ // If you are not the system admin then you can only demote yourself
+ if !c.IsSystemAdmin() && user_id != c.Session.UserId {
+ c.Err = model.NewLocAppError("updateRoles", "api.user.update_roles.system_admin_set.app_error", nil, "")
+ c.Err.StatusCode = http.StatusForbidden
+ }
+
+ // Only another system admin can add the system admin role
if model.IsInRole(new_roles, model.ROLE_SYSTEM_ADMIN) && !c.IsSystemAdmin() {
c.Err = model.NewLocAppError("updateRoles", "api.user.update_roles.system_admin_set.app_error", nil, "")
c.Err.StatusCode = http.StatusForbidden
@@ -1410,22 +1450,6 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) {
user = result.Data.(*model.User)
}
- if !c.HasPermissionsToTeam(user.TeamId, "updateRoles") {
- return
- }
-
- if !c.IsTeamAdmin() {
- c.Err = model.NewLocAppError("updateRoles", "api.user.update_roles.permissions.app_error", nil, "userId="+user_id)
- c.Err.StatusCode = http.StatusForbidden
- return
- }
-
- if user.IsInRole(model.ROLE_SYSTEM_ADMIN) && !c.IsSystemAdmin() {
- c.Err = model.NewLocAppError("updateRoles", "api.user.update_roles.system_admin_mod.app_error", nil, "")
- c.Err.StatusCode = http.StatusForbidden
- return
- }
-
ruser := UpdateRoles(c, user, new_roles)
if c.Err != nil {
return
@@ -1456,29 +1480,6 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) {
}
func UpdateRoles(c *Context, user *model.User, roles string) *model.User {
- // make sure there is at least 1 other active admin
-
- if !model.IsInRole(roles, model.ROLE_SYSTEM_ADMIN) {
- if model.IsInRole(user.Roles, model.ROLE_TEAM_ADMIN) && !model.IsInRole(roles, model.ROLE_TEAM_ADMIN) {
- if result := <-Srv.Store.User().GetProfiles(user.TeamId); result.Err != nil {
- c.Err = result.Err
- return nil
- } else {
- activeAdmins := -1
- profileUsers := result.Data.(map[string]*model.User)
- for _, profileUser := range profileUsers {
- if profileUser.DeleteAt == 0 && model.IsInRole(profileUser.Roles, model.ROLE_TEAM_ADMIN) {
- activeAdmins = activeAdmins + 1
- }
- }
-
- if activeAdmins <= 0 {
- c.Err = model.NewLocAppError("updateRoles", "api.user.update_roles.one_admin.app_error", nil, "")
- return nil
- }
- }
- }
- }
user.Roles = roles
@@ -1513,37 +1514,15 @@ func updateActive(c *Context, w http.ResponseWriter, r *http.Request) {
user = result.Data.(*model.User)
}
- if !c.HasPermissionsToTeam(user.TeamId, "updateActive") {
- return
- }
+ // true when you're trying to de-activate yourself
+ isSelfDeactive := !active && user_id == c.Session.UserId
- if !c.IsTeamAdmin() {
+ if !isSelfDeactive && !c.IsSystemAdmin() {
c.Err = model.NewLocAppError("updateActive", "api.user.update_active.permissions.app_error", nil, "userId="+user_id)
c.Err.StatusCode = http.StatusForbidden
return
}
- // make sure there is at least 1 other active admin
- if !active && model.IsInRole(user.Roles, model.ROLE_TEAM_ADMIN) {
- if result := <-Srv.Store.User().GetProfiles(user.TeamId); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- activeAdmins := -1
- profileUsers := result.Data.(map[string]*model.User)
- for _, profileUser := range profileUsers {
- if profileUser.DeleteAt == 0 && model.IsInRole(profileUser.Roles, model.ROLE_TEAM_ADMIN) {
- activeAdmins = activeAdmins + 1
- }
- }
-
- if activeAdmins <= 0 {
- c.Err = model.NewLocAppError("updateRoles", "api.user.update_roles.one_admin.app_error", nil, "userId="+user_id)
- return
- }
- }
- }
-
ruser := UpdateActive(c, user, active)
if c.Err == nil {
@@ -1631,12 +1610,33 @@ func PermanentDeleteUser(c *Context, user *model.User) *model.AppError {
return result.Err
}
+ if result := <-Srv.Store.Team().RemoveAllMembersByUser(user.Id); result.Err != nil {
+ return result.Err
+ }
+
+ if result := <-Srv.Store.PasswordRecovery().Delete(user.Id); result.Err != nil {
+ return result.Err
+ }
+
l4g.Warn(utils.T("api.user.permanent_delete_user.deleted.warn"), user.Email, user.Id)
c.LogAuditWithUserId("", fmt.Sprintf("success userId=%v", user.Id))
return nil
}
+func PermanentDeleteAllUsers(c *Context) *model.AppError {
+ if result := <-Srv.Store.User().GetAll(); result.Err != nil {
+ return result.Err
+ } else {
+ users := result.Data.([]*model.User)
+ for _, user := range users {
+ PermanentDeleteUser(c, user)
+ }
+ }
+
+ return nil
+}
+
func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(r.Body)
@@ -1646,41 +1646,28 @@ func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- name := props["name"]
- if len(name) == 0 {
- c.SetInvalidParam("sendPasswordReset", "name")
- return
- }
-
- var team *model.Team
- if result := <-Srv.Store.Team().GetByName(name); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- team = result.Data.(*model.Team)
- }
-
var user *model.User
- if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil {
- c.Err = model.NewLocAppError("sendPasswordReset", "api.user.send_password_reset.find.app_error", nil, "email="+email+" team_id="+team.Id)
+ if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil {
+ c.Err = model.NewLocAppError("sendPasswordReset", "api.user.send_password_reset.find.app_error", nil, "email="+email)
return
} else {
user = result.Data.(*model.User)
}
if len(user.AuthData) != 0 {
- c.Err = model.NewLocAppError("sendPasswordReset", "api.user.send_password_reset.sso.app_error", nil, "userId="+user.Id+", teamId="+team.Id)
+ c.Err = model.NewLocAppError("sendPasswordReset", "api.user.send_password_reset.sso.app_error", nil, "userId="+user.Id)
return
}
- newProps := make(map[string]string)
- newProps["user_id"] = user.Id
- newProps["time"] = fmt.Sprintf("%v", model.GetMillis())
+ recovery := &model.PasswordRecovery{}
+ recovery.UserId = user.Id
- data := model.MapToJson(newProps)
- hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.PasswordResetSalt))
+ if result := <-Srv.Store.PasswordRecovery().SaveOrUpdate(recovery); result.Err != nil {
+ c.Err = result.Err
+ return
+ }
- link := fmt.Sprintf("%s/reset_password_complete?d=%s&h=%s", c.GetTeamURLFromTeam(team), url.QueryEscape(data), url.QueryEscape(hash))
+ link := fmt.Sprintf("%s/reset_password_complete?code=%s", c.GetSiteURL(), url.QueryEscape(recovery.Code))
subjectPage := utils.NewHTMLTemplate("reset_subject", c.Locale)
subjectPage.Props["Subject"] = c.T("api.templates.reset_subject")
@@ -1706,110 +1693,89 @@ func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(r.Body)
newPassword := props["new_password"]
- if len(newPassword) < 5 {
+ if len(newPassword) < model.MIN_PASSWORD_LENGTH {
c.SetInvalidParam("resetPassword", "new_password")
return
}
- name := props["name"]
- if len(name) == 0 {
- c.SetInvalidParam("resetPassword", "name")
+ code := props["code"]
+ if len(code) != model.PASSWORD_RECOVERY_CODE_SIZE {
+ c.SetInvalidParam("resetPassword", "code")
return
}
- userId := props["user_id"]
- hash := props["hash"]
- timeStr := ""
-
- if !c.IsSystemAdmin() {
- if len(hash) == 0 {
- c.SetInvalidParam("resetPassword", "hash")
- return
- }
+ c.LogAudit("attempt")
- data := model.MapFromJson(strings.NewReader(props["data"]))
+ userId := ""
- userId = data["user_id"]
+ if result := <-Srv.Store.PasswordRecovery().GetByCode(code); result.Err != nil {
+ c.LogAuditWithUserId(userId, "fail - bad code")
+ c.Err = model.NewLocAppError("resetPassword", "api.user.reset_password.invalid_link.app_error", nil, result.Err.Error())
+ return
+ } else {
+ recovery := result.Data.(*model.PasswordRecovery)
- timeStr = data["time"]
- if len(timeStr) == 0 {
- c.SetInvalidParam("resetPassword", "data:time")
+ if model.GetMillis()-recovery.CreateAt < model.PASSWORD_RECOVER_EXPIRY_TIME {
+ userId = recovery.UserId
+ } else {
+ c.LogAuditWithUserId(userId, "fail - link expired")
+ c.Err = model.NewLocAppError("resetPassword", "api.user.reset_password.link_expired.app_error", nil, "")
return
}
+
+ go func() {
+ if result := <-Srv.Store.PasswordRecovery().Delete(userId); result.Err != nil {
+ l4g.Error("%v", result.Err)
+ }
+ }()
}
- if len(userId) != 26 {
- c.SetInvalidParam("resetPassword", "user_id")
+ if err := ResetPassword(c, userId, newPassword); err != nil {
+ c.Err = err
return
}
- c.LogAuditWithUserId(userId, "attempt")
+ c.LogAuditWithUserId(userId, "success")
- var team *model.Team
- if result := <-Srv.Store.Team().GetByName(name); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- team = result.Data.(*model.Team)
- }
+ rdata := map[string]string{}
+ rdata["status"] = "ok"
+ w.Write([]byte(model.MapToJson(rdata)))
+}
+func ResetPassword(c *Context, userId, newPassword string) *model.AppError {
var user *model.User
if result := <-Srv.Store.User().Get(userId); result.Err != nil {
- c.Err = result.Err
- return
+ return result.Err
} else {
user = result.Data.(*model.User)
}
if len(user.AuthData) != 0 {
- c.Err = model.NewLocAppError("resetPassword", "api.user.reset_password.sso.app_error", nil, "userId="+user.Id+", teamId="+team.Id)
- return
- }
+ return model.NewLocAppError("ResetPassword", "api.user.reset_password.sso.app_error", nil, "userId="+user.Id)
- if user.TeamId != team.Id {
- c.Err = model.NewLocAppError("resetPassword", "api.user.reset_password.wrong_team.app_error", nil, "userId="+user.Id+", teamId="+team.Id)
- c.Err.StatusCode = http.StatusForbidden
- return
- }
-
- if !c.IsSystemAdmin() {
- if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", props["data"], utils.Cfg.EmailSettings.PasswordResetSalt)) {
- c.Err = model.NewLocAppError("resetPassword", "api.user.reset_password.invalid_link.app_error", nil, "")
- return
- }
-
- t, err := strconv.ParseInt(timeStr, 10, 64)
- if err != nil || model.GetMillis()-t > 1000*60*60 { // one hour
- c.Err = model.NewLocAppError("resetPassword", "api.user.reset_password.link_expired.app_error", nil, "")
- return
- }
}
if result := <-Srv.Store.User().UpdatePassword(userId, model.HashPassword(newPassword)); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- c.LogAuditWithUserId(userId, "success")
+ return result.Err
}
- sendPasswordChangeEmailAndForget(c, user.Email, team.DisplayName, c.GetTeamURLFromTeam(team), c.GetSiteURL(), c.T("api.user.reset_password.method"))
+ sendPasswordChangeEmailAndForget(c, user.Email, c.GetSiteURL(), c.T("api.user.reset_password.method"))
- props["new_password"] = ""
- w.Write([]byte(model.MapToJson(props)))
+ return nil
}
-func sendPasswordChangeEmailAndForget(c *Context, email, teamDisplayName, teamURL, siteURL, method string) {
+func sendPasswordChangeEmailAndForget(c *Context, email, siteURL, method string) {
go func() {
subjectPage := utils.NewHTMLTemplate("password_change_subject", c.Locale)
subjectPage.Props["Subject"] = c.T("api.templates.password_change_subject",
- map[string]interface{}{"TeamDisplayName": teamDisplayName, "SiteName": utils.ClientCfg["SiteName"]})
+ map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName, "SiteName": utils.Cfg.TeamSettings.SiteName})
bodyPage := utils.NewHTMLTemplate("password_change_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = c.T("api.templates.password_change_body.title")
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.password_change_body.info",
- map[string]interface{}{"TeamDisplayName": teamDisplayName, "TeamURL": teamURL, "Method": method}))
+ map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName, "TeamURL": siteURL, "Method": method}))
if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.user.send_password_change_email_and_forget.error"), err)
@@ -1818,19 +1784,19 @@ func sendPasswordChangeEmailAndForget(c *Context, email, teamDisplayName, teamUR
}()
}
-func sendEmailChangeEmailAndForget(c *Context, oldEmail, newEmail, teamDisplayName, teamURL, siteURL string) {
+func sendEmailChangeEmailAndForget(c *Context, oldEmail, newEmail, siteURL string) {
go func() {
subjectPage := utils.NewHTMLTemplate("email_change_subject", c.Locale)
subjectPage.Props["Subject"] = c.T("api.templates.email_change_subject",
- map[string]interface{}{"TeamDisplayName": teamDisplayName})
+ map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName})
subjectPage.Props["SiteName"] = utils.Cfg.TeamSettings.SiteName
bodyPage := utils.NewHTMLTemplate("email_change_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = c.T("api.templates.email_change_body.title")
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.email_change_body.info",
- map[string]interface{}{"TeamDisplayName": teamDisplayName, "NewEmail": newEmail}))
+ map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName, "NewEmail": newEmail}))
if err := utils.SendMail(oldEmail, subjectPage.Render(), bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.user.send_email_change_email_and_forget.error"), err)
@@ -1839,21 +1805,21 @@ func sendEmailChangeEmailAndForget(c *Context, oldEmail, newEmail, teamDisplayNa
}()
}
-func SendEmailChangeVerifyEmailAndForget(c *Context, userId, newUserEmail, teamName, teamDisplayName, siteURL, teamURL string) {
+func SendEmailChangeVerifyEmailAndForget(c *Context, userId, newUserEmail, siteURL string) {
go func() {
- link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&teamname=%s&email=%s", siteURL, userId, model.HashPassword(userId), teamName, newUserEmail)
+ link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&email=%s", siteURL, userId, model.HashPassword(userId), newUserEmail)
subjectPage := utils.NewHTMLTemplate("email_change_verify_subject", c.Locale)
subjectPage.Props["Subject"] = c.T("api.templates.email_change_verify_subject",
- map[string]interface{}{"TeamDisplayName": teamDisplayName})
+ map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName})
subjectPage.Props["SiteName"] = utils.Cfg.TeamSettings.SiteName
bodyPage := utils.NewHTMLTemplate("email_change_verify_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = c.T("api.templates.email_change_verify_body.title")
bodyPage.Props["Info"] = c.T("api.templates.email_change_verify_body.info",
- map[string]interface{}{"TeamDisplayName": teamDisplayName})
+ map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName})
bodyPage.Props["VerifyUrl"] = link
bodyPage.Props["VerifyButton"] = c.T("api.templates.email_change_verify_body.button")
@@ -1929,7 +1895,7 @@ func getStatuses(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if result := <-Srv.Store.User().GetProfiles(c.Session.TeamId); result.Err != nil {
+ if result := <-Srv.Store.User().GetProfileByIds(userIds); result.Err != nil {
c.Err = result.Err
return
} else {
@@ -1937,17 +1903,6 @@ func getStatuses(c *Context, w http.ResponseWriter, r *http.Request) {
statuses := map[string]string{}
for _, profile := range profiles {
- found := false
- for _, uid := range userIds {
- if uid == profile.Id {
- found = true
- }
- }
-
- if !found {
- continue
- }
-
if profile.IsOffline() {
statuses[profile.Id] = model.USER_OFFLINE
} else if profile.IsAway() {
@@ -1957,131 +1912,18 @@ func getStatuses(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
- //w.Header().Set("Cache-Control", "max-age=9, public") // 2 mins
w.Write([]byte(model.MapToJson(statuses)))
return
}
}
-func GetAuthorizationCode(c *Context, service, teamName string, props map[string]string, loginHint string) (string, *model.AppError) {
-
- sso := utils.Cfg.GetSSOService(service)
- if sso != nil && !sso.Enable {
- return "", model.NewLocAppError("GetAuthorizationCode", "api.user.get_authorization_code.unsupported.app_error", nil, "service="+service)
- }
-
- clientId := sso.Id
- endpoint := sso.AuthEndpoint
- scope := sso.Scope
-
- props["hash"] = model.HashPassword(clientId)
- props["team"] = teamName
- state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(props)))
-
- redirectUri := c.GetSiteURL() + "/signup/" + service + "/complete"
-
- authUrl := endpoint + "?response_type=code&client_id=" + clientId + "&redirect_uri=" + url.QueryEscape(redirectUri) + "&state=" + url.QueryEscape(state)
-
- if len(scope) > 0 {
- authUrl += "&scope=" + utils.UrlEncode(scope)
- }
-
- if len(loginHint) > 0 {
- authUrl += "&login_hint=" + utils.UrlEncode(loginHint)
- }
-
- return authUrl, nil
-}
-
-func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser, *model.Team, map[string]string, *model.AppError) {
- sso := utils.Cfg.GetSSOService(service)
- if sso == nil || !sso.Enable {
- return nil, nil, nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.unsupported.app_error", nil, "service="+service)
- }
-
- stateStr := ""
- if b, err := b64.StdEncoding.DecodeString(state); err != nil {
- return nil, nil, nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, err.Error())
- } else {
- stateStr = string(b)
- }
-
- stateProps := model.MapFromJson(strings.NewReader(stateStr))
-
- if !model.ComparePassword(stateProps["hash"], sso.Id) {
- return nil, nil, nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "")
- }
-
- ok := true
- teamName := ""
- if teamName, ok = stateProps["team"]; !ok {
- return nil, nil, nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state_team.app_error", nil, "")
- }
-
- tchan := Srv.Store.Team().GetByName(teamName)
-
- p := url.Values{}
- p.Set("client_id", sso.Id)
- p.Set("client_secret", sso.Secret)
- p.Set("code", code)
- p.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE)
- p.Set("redirect_uri", redirectUri)
-
- tr := &http.Transport{
- TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections},
- }
- client := &http.Client{Transport: tr}
- req, _ := http.NewRequest("POST", sso.TokenEndpoint, strings.NewReader(p.Encode()))
-
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- req.Header.Set("Accept", "application/json")
-
- var ar *model.AccessResponse
- if resp, err := client.Do(req); err != nil {
- return nil, nil, nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, err.Error())
- } else {
- ar = model.AccessResponseFromJson(resp.Body)
- if ar == nil {
- return nil, nil, nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_response.app_error", nil, "")
- }
- }
-
- if strings.ToLower(ar.TokenType) != model.ACCESS_TOKEN_TYPE {
- return nil, nil, nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_token.app_error", nil, "token_type="+ar.TokenType)
- }
-
- if len(ar.AccessToken) == 0 {
- return nil, nil, nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.missing.app_error", nil, "")
- }
-
- p = url.Values{}
- p.Set("access_token", ar.AccessToken)
- req, _ = http.NewRequest("GET", sso.UserApiEndpoint, strings.NewReader(""))
-
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- req.Header.Set("Accept", "application/json")
- req.Header.Set("Authorization", "Bearer "+ar.AccessToken)
-
- if resp, err := client.Do(req); err != nil {
- return nil, nil, nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error",
- map[string]interface{}{"Service": service}, err.Error())
- } else {
- if result := <-tchan; result.Err != nil {
- return nil, nil, nil, result.Err
- } else {
- return resp.Body, result.Data.(*model.Team), stateProps, nil
- }
- }
-
-}
-
-func IsUsernameTaken(name string, teamId string) bool {
+func IsUsernameTaken(name string) bool {
if !model.IsValidUsername(name) {
return false
}
- if result := <-Srv.Store.User().GetByUsername(teamId, name); result.Err != nil {
+ if result := <-Srv.Store.User().GetByUsername(name); result.Err != nil {
return false
} else {
return true
@@ -2099,12 +1941,6 @@ func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- teamName := props["team_name"]
- if len(teamName) == 0 {
- c.SetInvalidParam("emailToOAuth", "team_name")
- return
- }
-
service := props["service"]
if len(service) == 0 {
c.SetInvalidParam("emailToOAuth", "service")
@@ -2119,17 +1955,8 @@ func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
c.LogAudit("attempt")
- var team *model.Team
- if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil {
- c.LogAudit("fail - couldn't get team")
- c.Err = result.Err
- return
- } else {
- team = result.Data.(*model.Team)
- }
-
var user *model.User
- if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil {
+ if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil {
c.LogAudit("fail - couldn't get user")
c.Err = result.Err
return
@@ -2137,8 +1964,9 @@ func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
user = result.Data.(*model.User)
}
- if !checkUserLoginAttempts(c, user) || !checkUserPassword(c, user, password) {
- c.LogAuditWithUserId(user.Id, "fail - invalid password")
+ if err := checkPasswordAndAllCriteria(user, password, ""); err != nil {
+ c.LogAuditWithUserId(user.Id, "failed - bad authentication")
+ c.Err = err
return
}
@@ -2147,7 +1975,7 @@ func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
stateProps["email"] = email
m := map[string]string{}
- if authUrl, err := GetAuthorizationCode(c, service, teamName, stateProps, ""); err != nil {
+ if authUrl, err := GetAuthorizationCode(c, service, stateProps, ""); err != nil {
c.LogAuditWithUserId(user.Id, "fail - oauth issue")
c.Err = err
return
@@ -2159,52 +1987,6 @@ func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.MapToJson(m)))
}
-func CompleteSwitchWithOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.ReadCloser, team *model.Team, email string) {
- authData := ""
- ssoEmail := ""
- provider := einterfaces.GetOauthProvider(service)
- if provider == nil {
- c.Err = model.NewLocAppError("CompleteClaimWithOAuth", "api.user.complete_switch_with_oauth.unavailable.app_error",
- map[string]interface{}{"Service": service}, "")
- return
- } else {
- ssoUser := provider.GetUserFromJson(userData)
- authData = ssoUser.AuthData
- ssoEmail = ssoUser.Email
- }
-
- if len(authData) == 0 {
- c.Err = model.NewLocAppError("CompleteClaimWithOAuth", "api.user.complete_switch_with_oauth.parse.app_error",
- map[string]interface{}{"Service": service}, "")
- return
- }
-
- if len(email) == 0 {
- c.Err = model.NewLocAppError("CompleteClaimWithOAuth", "api.user.complete_switch_with_oauth.blank_email.app_error", nil, "")
- return
- }
-
- var user *model.User
- if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- user = result.Data.(*model.User)
- }
-
- RevokeAllSession(c, user.Id)
- if c.Err != nil {
- return
- }
-
- if result := <-Srv.Store.User().UpdateAuthData(user.Id, service, authData, ssoEmail); result.Err != nil {
- c.Err = result.Err
- return
- }
-
- sendSignInChangeEmailAndForget(c, user.Email, team.DisplayName, c.GetSiteURL()+"/"+team.Name, c.GetSiteURL(), strings.Title(service)+" SSO")
-}
-
func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(r.Body)
@@ -2214,12 +1996,6 @@ func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- teamName := props["team_name"]
- if len(teamName) == 0 {
- c.SetInvalidParam("oauthToEmail", "team_name")
- return
- }
-
email := props["email"]
if len(email) == 0 {
c.SetInvalidParam("oauthToEmail", "email")
@@ -2228,17 +2004,8 @@ func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
c.LogAudit("attempt")
- var team *model.Team
- if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil {
- c.LogAudit("fail - couldn't get team")
- c.Err = result.Err
- return
- } else {
- team = result.Data.(*model.Team)
- }
-
var user *model.User
- if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil {
+ if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil {
c.LogAudit("fail - couldn't get user")
c.Err = result.Err
return
@@ -2259,7 +2026,7 @@ func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- sendSignInChangeEmailAndForget(c, user.Email, team.DisplayName, c.GetSiteURL()+"/"+team.Name, c.GetSiteURL(), c.T("api.templates.signin_change_email.body.method_email"))
+ sendSignInChangeEmailAndForget(c, user.Email, c.GetSiteURL(), c.T("api.templates.signin_change_email.body.method_email"))
RevokeAllSession(c, c.Session.UserId)
if c.Err != nil {
@@ -2267,7 +2034,7 @@ func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
}
m := map[string]string{}
- m["follow_link"] = c.GetTeamURL() + "/login?extra=signin_change"
+ m["follow_link"] = "/login?extra=signin_change"
c.LogAudit("success")
w.Write([]byte(model.MapToJson(m)))
@@ -2288,12 +2055,6 @@ func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- teamName := props["team_name"]
- if len(teamName) == 0 {
- c.SetInvalidParam("emailToLdap", "team_name")
- return
- }
-
ldapId := props["ldap_id"]
if len(ldapId) == 0 {
c.SetInvalidParam("emailToLdap", "ldap_id")
@@ -2308,17 +2069,8 @@ func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) {
c.LogAudit("attempt")
- var team *model.Team
- if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil {
- c.LogAudit("fail - couldn't get team")
- c.Err = result.Err
- return
- } else {
- team = result.Data.(*model.Team)
- }
-
var user *model.User
- if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil {
+ if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil {
c.LogAudit("fail - couldn't get user")
c.Err = result.Err
return
@@ -2326,8 +2078,9 @@ func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) {
user = result.Data.(*model.User)
}
- if !checkUserLoginAttempts(c, user) || !checkUserPassword(c, user, emailPassword) {
- c.LogAuditWithUserId(user.Id, "fail - invalid email password")
+ if err := checkPasswordAndAllCriteria(user, emailPassword, ""); err != nil {
+ c.LogAuditWithUserId(user.Id, "failed - bad authentication")
+ c.Err = err
return
}
@@ -2343,16 +2096,16 @@ func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if err := ldapInterface.SwitchToEmail(user.Id, ldapId, ldapPassword); err != nil {
+ if err := ldapInterface.SwitchToLdap(user.Id, ldapId, ldapPassword); err != nil {
c.LogAuditWithUserId(user.Id, "fail - ldap switch failed")
c.Err = err
return
}
- sendSignInChangeEmailAndForget(c, user.Email, team.DisplayName, c.GetSiteURL()+"/"+team.Name, c.GetSiteURL(), "LDAP")
+ sendSignInChangeEmailAndForget(c, user.Email, c.GetSiteURL(), "LDAP")
m := map[string]string{}
- m["follow_link"] = c.GetTeamURL() + "/login?extra=signin_change"
+ m["follow_link"] = "/login?extra=signin_change"
c.LogAudit("success")
w.Write([]byte(model.MapToJson(m)))
@@ -2373,12 +2126,6 @@ func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- teamName := props["team_name"]
- if len(teamName) == 0 {
- c.SetInvalidParam("ldapToEmail", "team_name")
- return
- }
-
ldapPassword := props["ldap_password"]
if len(ldapPassword) == 0 {
c.SetInvalidParam("ldapToEmail", "ldap_password")
@@ -2387,17 +2134,8 @@ func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
c.LogAudit("attempt")
- var team *model.Team
- if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil {
- c.LogAudit("fail - couldn't get team")
- c.Err = result.Err
- return
- } else {
- team = result.Data.(*model.Team)
- }
-
var user *model.User
- if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil {
+ if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil {
c.LogAudit("fail - couldn't get user")
c.Err = result.Err
return
@@ -2434,27 +2172,27 @@ func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- sendSignInChangeEmailAndForget(c, user.Email, team.DisplayName, c.GetSiteURL()+"/"+team.Name, c.GetSiteURL(), c.T("api.templates.signin_change_email.body.method_email"))
+ sendSignInChangeEmailAndForget(c, user.Email, c.GetSiteURL(), c.T("api.templates.signin_change_email.body.method_email"))
m := map[string]string{}
- m["follow_link"] = c.GetTeamURL() + "/login?extra=signin_change"
+ m["follow_link"] = "/login?extra=signin_change"
c.LogAudit("success")
w.Write([]byte(model.MapToJson(m)))
}
-func sendSignInChangeEmailAndForget(c *Context, email, teamDisplayName, teamURL, siteURL, method string) {
+func sendSignInChangeEmailAndForget(c *Context, email, siteURL, method string) {
go func() {
subjectPage := utils.NewHTMLTemplate("signin_change_subject", c.Locale)
subjectPage.Props["Subject"] = c.T("api.templates.singin_change_email.subject",
- map[string]interface{}{"TeamDisplayName": teamDisplayName, "SiteName": utils.ClientCfg["SiteName"]})
+ map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
bodyPage := utils.NewHTMLTemplate("signin_change_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = c.T("api.templates.signin_change_email.body.title")
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.singin_change_email.body.info",
- map[string]interface{}{"TeamDisplayName": teamDisplayName, "TeamURL": teamURL, "Method": method}))
+ map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"], "Method": method}))
if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.user.send_sign_in_change_email_and_forget.error"), err)
@@ -2488,49 +2226,34 @@ func verifyEmail(c *Context, w http.ResponseWriter, r *http.Request) {
}
c.Err = model.NewLocAppError("verifyEmail", "api.user.verify_email.bad_link.app_error", nil, "")
- c.Err.StatusCode = http.StatusForbidden
+ c.Err.StatusCode = http.StatusBadRequest
}
func resendVerification(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(r.Body)
- teamName := props["team_name"]
- if len(teamName) == 0 {
- c.SetInvalidParam("resendVerification", "team_name")
- return
- }
-
email := props["email"]
if len(email) == 0 {
c.SetInvalidParam("resendVerification", "email")
return
}
- var team *model.Team
- if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- team = result.Data.(*model.Team)
- }
-
- if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil {
+ if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil {
c.Err = result.Err
return
} else {
user := result.Data.(*model.User)
if user.LastActivityAt > 0 {
- SendEmailChangeVerifyEmailAndForget(c, user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
+ SendEmailChangeVerifyEmailAndForget(c, user.Id, user.Email, c.GetSiteURL())
} else {
- SendVerifyEmailAndForget(c, user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
+ SendVerifyEmailAndForget(c, user.Id, user.Email, c.GetSiteURL())
}
}
}
func generateMfaQrCode(c *Context, w http.ResponseWriter, r *http.Request) {
uchan := Srv.Store.User().Get(c.Session.UserId)
- tchan := Srv.Store.Team().Get(c.Session.TeamId)
var user *model.User
if result := <-uchan; result.Err != nil {
@@ -2540,14 +2263,6 @@ func generateMfaQrCode(c *Context, w http.ResponseWriter, r *http.Request) {
user = result.Data.(*model.User)
}
- var team *model.Team
- if result := <-tchan; result.Err != nil {
- c.Err = result.Err
- return
- } else {
- team = result.Data.(*model.Team)
- }
-
mfaInterface := einterfaces.GetMfaInterface()
if mfaInterface == nil {
c.Err = model.NewLocAppError("generateMfaQrCode", "api.user.generate_mfa_qr.not_available.app_error", nil, "")
@@ -2555,7 +2270,7 @@ func generateMfaQrCode(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- img, err := mfaInterface.GenerateQrCode(team, user)
+ img, err := mfaInterface.GenerateQrCode(user)
if err != nil {
c.Err = err
return
@@ -2583,28 +2298,13 @@ func updateMfa(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
- mfaInterface := einterfaces.GetMfaInterface()
- if mfaInterface == nil {
- c.Err = model.NewLocAppError("generateMfaQrCode", "api.user.update_mfa.not_available.app_error", nil, "")
- c.Err.StatusCode = http.StatusNotImplemented
- return
- }
-
if activate {
- var user *model.User
- if result := <-Srv.Store.User().Get(c.Session.UserId); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- user = result.Data.(*model.User)
- }
-
- if err := mfaInterface.Activate(user, token); err != nil {
+ if err := ActivateMfa(c.Session.UserId, token); err != nil {
c.Err = err
return
}
} else {
- if err := mfaInterface.Deactivate(c.Session.UserId); err != nil {
+ if err := DeactivateMfa(c.Session.UserId); err != nil {
c.Err = err
return
}
@@ -2615,6 +2315,43 @@ func updateMfa(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.MapToJson(rdata)))
}
+func ActivateMfa(userId, token string) *model.AppError {
+ mfaInterface := einterfaces.GetMfaInterface()
+ if mfaInterface == nil {
+ err := model.NewLocAppError("ActivateMfa", "api.user.update_mfa.not_available.app_error", nil, "")
+ err.StatusCode = http.StatusNotImplemented
+ return err
+ }
+
+ var user *model.User
+ if result := <-Srv.Store.User().Get(userId); result.Err != nil {
+ return result.Err
+ } else {
+ user = result.Data.(*model.User)
+ }
+
+ if err := mfaInterface.Activate(user, token); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func DeactivateMfa(userId string) *model.AppError {
+ mfaInterface := einterfaces.GetMfaInterface()
+ if mfaInterface == nil {
+ err := model.NewLocAppError("DeactivateMfa", "api.user.update_mfa.not_available.app_error", nil, "")
+ err.StatusCode = http.StatusNotImplemented
+ return err
+ }
+
+ if err := mfaInterface.Deactivate(userId); err != nil {
+ return err
+ }
+
+ return nil
+}
+
func checkMfa(c *Context, w http.ResponseWriter, r *http.Request) {
if !utils.IsLicensed || !*utils.License.Features.MFA || !*utils.Cfg.ServiceSettings.EnableMultifactorAuthentication {
rdata := map[string]string{}
@@ -2633,33 +2370,19 @@ func checkMfa(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- teamName := props["team_name"]
- if len(teamName) == 0 {
- c.SetInvalidParam("checkMfa", "team_name")
- return
- }
-
loginId := props["login_id"]
if len(loginId) == 0 {
c.SetInvalidParam("checkMfa", "login_id")
return
}
- var team *model.Team
- if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- team = result.Data.(*model.Team)
- }
-
var uchan store.StoreChannel
if method == model.USER_AUTH_SERVICE_EMAIL {
- uchan = Srv.Store.User().GetByEmail(team.Id, loginId)
+ uchan = Srv.Store.User().GetByEmail(loginId)
} else if method == model.USER_AUTH_SERVICE_USERNAME {
- uchan = Srv.Store.User().GetByUsername(team.Id, loginId)
+ uchan = Srv.Store.User().GetByUsername(loginId)
} else if method == model.USER_AUTH_SERVICE_LDAP {
- uchan = Srv.Store.User().GetByAuth(team.Id, loginId, model.USER_AUTH_SERVICE_LDAP)
+ uchan = Srv.Store.User().GetByAuth(loginId, model.USER_AUTH_SERVICE_LDAP)
}
rdata := map[string]string{}
diff --git a/api/user_test.go b/api/user_test.go
index 33f3fdad4..3c744120c 100644
--- a/api/user_test.go
+++ b/api/user_test.go
@@ -6,11 +6,6 @@ package api
import (
"bytes"
"fmt"
- "github.com/goamz/goamz/aws"
- "github.com/goamz/goamz/s3"
- "github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
- "github.com/mattermost/platform/utils"
"image"
"image/color"
"io"
@@ -20,21 +15,31 @@ import (
"strings"
"testing"
"time"
+
+ "github.com/goamz/goamz/aws"
+ "github.com/goamz/goamz/s3"
+
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/store"
+ "github.com/mattermost/platform/utils"
)
func TestCreateUser(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "hello"}
+ user := model.User{Email: strings.ToLower("success+"+model.NewId()) + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "hello", Username: "n" + model.NewId()}
ruser, err := Client.CreateUser(&user, "")
if err != nil {
t.Fatal(err)
}
+ LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team))
+
if ruser.Data.(*model.User).Nickname != user.Nickname {
t.Fatal("nickname didn't match")
}
@@ -48,13 +53,15 @@ func TestCreateUser(t *testing.T) {
}
ruser.Data.(*model.User).Id = ""
+ ruser.Data.(*model.User).Username = "n" + model.NewId()
if _, err := Client.CreateUser(ruser.Data.(*model.User), ""); err != nil {
if err.Message != "An account with that email already exists." {
t.Fatal(err)
}
}
- ruser.Data.(*model.User).Email = "test2@nowhere.com"
+ ruser.Data.(*model.User).Email = "success+" + model.NewId() + "@simulator.amazonses.com"
+ ruser.Data.(*model.User).Username = user.Username
if _, err := Client.CreateUser(ruser.Data.(*model.User), ""); err != nil {
if err.Message != "An account with that username already exists." {
t.Fatal(err)
@@ -73,34 +80,16 @@ func TestCreateUser(t *testing.T) {
}
}
-func TestCreateUserAllowedDomains(t *testing.T) {
- Setup()
-
- team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_INVITE, AllowedDomains: "spinpunch.com, @nowh.com,@hello.com"}
- rteam, _ := Client.CreateTeam(&team)
-
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "hello"}
-
- _, err := Client.CreateUser(&user, "")
- if err == nil {
- t.Fatal("should have failed")
- }
-
- user.Email = "test@nowh.com"
- _, err = Client.CreateUser(&user, "")
- if err != nil {
- t.Fatal(err)
- }
-}
-
func TestLogin(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Username: "corey", Password: "pwd"}
+ user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Username: "corey" + model.NewId(), Password: "pwd"}
ruser, _ := Client.CreateUser(&user, "")
+ LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team))
store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id))
if result, err := Client.LoginById(ruser.Data.(*model.User).Id, user.Password); err != nil {
@@ -155,7 +144,7 @@ func TestLogin(t *testing.T) {
team2 := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_INVITE}
rteam2 := Client.Must(Client.CreateTeam(&team2))
- user2 := model.User{TeamId: rteam2.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
if _, err := Client.CreateUserFromSignup(&user2, "junk", "1231312"); err == nil {
t.Fatal("Should have errored, signed up without hashed email")
@@ -179,14 +168,11 @@ func TestLogin(t *testing.T) {
}
func TestLoginWithDeviceId(t *testing.T) {
- Setup()
-
- team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- rteam, _ := Client.CreateTeam(&team)
-
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+ user := th.BasicUser
+ Client.Must(Client.Logout())
deviceId := model.NewId()
if result, err := Client.LoginByEmailWithDevice(team.Name, user.Email, user.Password, deviceId); err != nil {
@@ -210,20 +196,17 @@ func TestLoginWithDeviceId(t *testing.T) {
}
func TestSessions(t *testing.T) {
- Setup()
-
- team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- rteam, _ := Client.CreateTeam(&team)
-
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+ user := th.BasicUser
+ Client.Must(Client.Logout())
deviceId := model.NewId()
Client.LoginByEmailWithDevice(team.Name, user.Email, user.Password, deviceId)
Client.LoginByEmail(team.Name, user.Email, user.Password)
- r1, err := Client.GetSessions(ruser.Id)
+ r1, err := Client.GetSessions(user.Id)
if err != nil {
t.Fatal(err)
}
@@ -249,7 +232,7 @@ func TestSessions(t *testing.T) {
t.Fatal(err)
}
- r2, err := Client.GetSessions(ruser.Id)
+ r2, err := Client.GetSessions(user.Id)
if err != nil {
t.Fatal(err)
}
@@ -262,24 +245,28 @@ func TestSessions(t *testing.T) {
}
func TestGetUser(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser, _ := Client.CreateUser(&user, "")
+ LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team))
store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id))
- user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser2, _ := Client.CreateUser(&user2, "")
+ LinkUserToTeam(ruser2.Data.(*model.User), rteam.Data.(*model.Team))
store.Must(Srv.Store.User().VerifyEmail(ruser2.Data.(*model.User).Id))
team2 := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam2, _ := Client.CreateTeam(&team2)
- user3 := model.User{TeamId: rteam2.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user3 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser3, _ := Client.CreateUser(&user3, "")
+ LinkUserToTeam(ruser3.Data.(*model.User), rteam2.Data.(*model.Team))
store.Must(Srv.Store.User().VerifyEmail(ruser3.Data.(*model.User).Id))
Client.LoginByEmail(team.Name, user.Email, user.Password)
@@ -353,14 +340,36 @@ func TestGetUser(t *testing.T) {
}
}
+func TestGetDirectProfiles(t *testing.T) {
+ th := Setup().InitBasic()
+
+ th.BasicClient.Must(th.BasicClient.CreateDirectChannel(th.BasicUser2.Id))
+
+ if result, err := th.BasicClient.GetDirectProfiles(""); err != nil {
+ t.Fatal(err)
+ } else {
+ users := result.Data.(map[string]*model.User)
+
+ if len(users) != 1 {
+ t.Fatal("map was wrong length")
+ }
+
+ if users[th.BasicUser2.Id] == nil {
+ t.Fatal("missing expected user")
+ }
+ }
+}
+
func TestGetAudits(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser, _ := Client.CreateUser(&user, "")
+ LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team))
store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id))
time.Sleep(100 * time.Millisecond)
@@ -390,7 +399,8 @@ func TestGetAudits(t *testing.T) {
}
func TestUserCreateImage(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
b, err := createProfileImage("Corey Hulen", "eo1zkdr96pdj98pjmq8zy35wba")
if err != nil {
@@ -412,8 +422,9 @@ func TestUserCreateImage(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ LinkUserToTeam(user, team)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
Client.LoginByEmail(team.Name, user.Email, "pwd")
@@ -428,26 +439,27 @@ func TestUserCreateImage(t *testing.T) {
s := s3.New(auth, aws.Regions[utils.Cfg.FileSettings.AmazonS3Region])
bucket := s.Bucket(utils.Cfg.FileSettings.AmazonS3Bucket)
- if err := bucket.Del("teams/" + user.TeamId + "/users/" + user.Id + "/profile.png"); err != nil {
+ if err := bucket.Del("/users/" + user.Id + "/profile.png"); err != nil {
t.Fatal(err)
}
} else {
- path := utils.Cfg.FileSettings.Directory + "teams/" + user.TeamId + "/users/" + user.Id + "/profile.png"
+ path := utils.Cfg.FileSettings.Directory + "/users/" + user.Id + "/profile.png"
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
}
-
}
func TestUserUploadProfileImage(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ LinkUserToTeam(user, team)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
if utils.Cfg.FileSettings.DriverName != "" {
@@ -455,13 +467,14 @@ func TestUserUploadProfileImage(t *testing.T) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
- if _, upErr := Client.UploadFile("/users/newimage", body.Bytes(), writer.FormDataContentType()); upErr == nil {
+ if _, upErr := Client.UploadProfileFile(body.Bytes(), writer.FormDataContentType()); upErr == nil {
t.Fatal("Should have errored")
}
Client.LoginByEmail(team.Name, user.Email, "pwd")
+ Client.SetTeamId(team.Id)
- if _, upErr := Client.UploadFile("/users/newimage", body.Bytes(), writer.FormDataContentType()); upErr == nil {
+ if _, upErr := Client.UploadProfileFile(body.Bytes(), writer.FormDataContentType()); upErr == nil {
t.Fatal("Should have errored")
}
@@ -486,7 +499,7 @@ func TestUserUploadProfileImage(t *testing.T) {
t.Fatal(err)
}
- if _, upErr := Client.UploadFile("/users/newimage", body.Bytes(), writer.FormDataContentType()); upErr == nil {
+ if _, upErr := Client.UploadProfileFile(body.Bytes(), writer.FormDataContentType()); upErr == nil {
t.Fatal("Should have errored")
}
@@ -512,7 +525,7 @@ func TestUserUploadProfileImage(t *testing.T) {
t.Fatal(err)
}
- if _, upErr := Client.UploadFile("/users/newimage", body.Bytes(), writer.FormDataContentType()); upErr != nil {
+ if _, upErr := Client.UploadProfileFile(body.Bytes(), writer.FormDataContentType()); upErr != nil {
t.Fatal(upErr)
}
@@ -526,11 +539,11 @@ func TestUserUploadProfileImage(t *testing.T) {
s := s3.New(auth, aws.Regions[utils.Cfg.FileSettings.AmazonS3Region])
bucket := s.Bucket(utils.Cfg.FileSettings.AmazonS3Bucket)
- if err := bucket.Del("teams/" + user.TeamId + "/users/" + user.Id + "/profile.png"); err != nil {
+ if err := bucket.Del("users/" + user.Id + "/profile.png"); err != nil {
t.Fatal(err)
}
} else {
- path := utils.Cfg.FileSettings.Directory + "teams/" + user.TeamId + "/users/" + user.Id + "/profile.png"
+ path := utils.Cfg.FileSettings.Directory + "users/" + user.Id + "/profile.png"
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
@@ -538,22 +551,24 @@ func TestUserUploadProfileImage(t *testing.T) {
} else {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
- if _, upErr := Client.UploadFile("/users/newimage", body.Bytes(), writer.FormDataContentType()); upErr.StatusCode != http.StatusNotImplemented {
+ if _, upErr := Client.UploadProfileFile(body.Bytes(), writer.FormDataContentType()); upErr.StatusCode != http.StatusNotImplemented {
t.Fatal("Should have failed with 501 - Not Implemented")
}
}
}
func TestUserUpdate(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
time1 := model.GetMillis()
- user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd", LastActivityAt: time1, LastPingAt: time1, Roles: ""}
+ user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd", LastActivityAt: time1, LastPingAt: time1, Roles: ""}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ LinkUserToTeam(user, team)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
if _, err := Client.UpdateUser(user); err == nil {
@@ -561,6 +576,7 @@ func TestUserUpdate(t *testing.T) {
}
Client.LoginByEmail(team.Name, user.Email, "pwd")
+ Client.SetTeamId(team.Id)
time.Sleep(100 * time.Millisecond)
@@ -569,7 +585,6 @@ func TestUserUpdate(t *testing.T) {
time.Sleep(100 * time.Millisecond)
user.Nickname = "Jim Jimmy"
- user.TeamId = "12345678901234567890123456"
user.LastActivityAt = time2
user.LastPingAt = time2
user.Roles = model.ROLE_TEAM_ADMIN
@@ -581,9 +596,6 @@ func TestUserUpdate(t *testing.T) {
if result.Data.(*model.User).Nickname != "Jim Jimmy" {
t.Fatal("Nickname did not update properly")
}
- if result.Data.(*model.User).TeamId != team.Id {
- t.Fatal("TeamId should not have updated")
- }
if result.Data.(*model.User).LastActivityAt == time2 {
t.Fatal("LastActivityAt should not have updated")
}
@@ -598,21 +610,13 @@ func TestUserUpdate(t *testing.T) {
}
}
- user.TeamId = "junk"
- if _, err := Client.UpdateUser(user); err == nil {
- t.Fatal("Should have errored - tried to change teamId to junk")
- }
-
- user.TeamId = team.Id
- if _, err := Client.UpdateUser(nil); err == nil {
- t.Fatal("Should have errored")
- }
-
- user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
+ LinkUserToTeam(user2, team)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
Client.LoginByEmail(team.Name, user2.Email, "pwd")
+ Client.SetTeamId(team.Id)
user.Nickname = "Tim Timmy"
@@ -622,13 +626,16 @@ func TestUserUpdate(t *testing.T) {
}
func TestUserUpdatePassword(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
+ Client.SetTeamId(team.Id)
- user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ LinkUserToTeam(user, team)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
if _, err := Client.UpdateUserPassword(user.Id, "pwd", "newpwd"); err == nil {
@@ -670,8 +677,9 @@ func TestUserUpdatePassword(t *testing.T) {
t.Fatal(err)
}
- user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
+ LinkUserToTeam(user2, team)
Client.LoginByEmail(team.Name, user2.Email, "pwd")
@@ -681,17 +689,20 @@ func TestUserUpdatePassword(t *testing.T) {
}
func TestUserUpdateRoles(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: "test@nowhere.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ LinkUserToTeam(user, team)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
- user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
+ LinkUserToTeam(user2, team)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
data := make(map[string]string)
@@ -703,6 +714,7 @@ func TestUserUpdateRoles(t *testing.T) {
}
Client.LoginByEmail(team.Name, user2.Email, "pwd")
+ Client.SetTeamId(team.Id)
if _, err := Client.UpdateUserRoles(data); err == nil {
t.Fatal("Should have errored, not admin")
@@ -711,11 +723,13 @@ func TestUserUpdateRoles(t *testing.T) {
team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team)
- user3 := &model.User{TeamId: team2.Id, Email: "test@nowhere.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user3 := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User)
+ LinkUserToTeam(user3, team2)
store.Must(Srv.Store.User().VerifyEmail(user3.Id))
Client.LoginByEmail(team2.Name, user3.Email, "pwd")
+ Client.SetTeamId(team2.Id)
data["user_id"] = user2.Id
@@ -740,27 +754,25 @@ func TestUserUpdateRoles(t *testing.T) {
data["user_id"] = user2.Id
- if result, err := Client.UpdateUserRoles(data); err != nil {
- t.Log(data["new_roles"])
- t.Fatal(err)
- } else {
- if result.Data.(*model.User).Roles != "admin" {
- t.Fatal("Roles did not update properly")
- }
+ if _, err := Client.UpdateUserRoles(data); err == nil {
+ t.Fatal("Should have errored, bad role")
}
}
func TestUserUpdateDeviceId(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: "test@nowhere.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ LinkUserToTeam(user, team)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
Client.LoginByEmail(team.Name, user.Email, "pwd")
+ Client.SetTeamId(team.Id)
deviceId := model.PUSH_NOTIFY_APPLE + ":1234567890"
if _, err := Client.AttachDeviceId(deviceId); err != nil {
@@ -779,17 +791,20 @@ func TestUserUpdateDeviceId(t *testing.T) {
}
func TestUserUpdateActive(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: "test@nowhere.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ LinkUserToTeam(user, team)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
- user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
+ LinkUserToTeam(user2, team)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
if _, err := Client.UpdateActive(user.Id, false); err == nil {
@@ -797,25 +812,31 @@ func TestUserUpdateActive(t *testing.T) {
}
Client.LoginByEmail(team.Name, user2.Email, "pwd")
+ Client.SetTeamId(team.Id)
if _, err := Client.UpdateActive(user.Id, false); err == nil {
t.Fatal("Should have errored, not admin")
}
+ Client.Must(Client.Logout())
+
team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team)
- user3 := &model.User{TeamId: team2.Id, Email: "test@nowhere.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user3 := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User)
+ LinkUserToTeam(user2, team2)
store.Must(Srv.Store.User().VerifyEmail(user3.Id))
Client.LoginByEmail(team2.Name, user3.Email, "pwd")
+ Client.SetTeamId(team2.Id)
if _, err := Client.UpdateActive(user.Id, false); err == nil {
- t.Fatal("Should have errored, wrong team")
+ t.Fatal("Should have errored, not yourself")
}
Client.LoginByEmail(team.Name, user.Email, "pwd")
+ Client.SetTeamId(team.Id)
if _, err := Client.UpdateActive("junk", false); err == nil {
t.Fatal("Should have errored, bad id")
@@ -824,35 +845,22 @@ func TestUserUpdateActive(t *testing.T) {
if _, err := Client.UpdateActive("12345678901234567890123456", false); err == nil {
t.Fatal("Should have errored, bad id")
}
-
- if result, err := Client.UpdateActive(user2.Id, false); err != nil {
- t.Fatal(err)
- } else {
- if result.Data.(*model.User).DeleteAt == 0 {
- t.Fatal("active did not update properly")
- }
- }
-
- if result, err := Client.UpdateActive(user2.Id, true); err != nil {
- t.Fatal(err)
- } else {
- if result.Data.(*model.User).DeleteAt != 0 {
- t.Fatal("active did not update properly true")
- }
- }
}
func TestUserPermDelete(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
+ LinkUserToTeam(user1, team)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
+ Client.SetTeamId(team.Id)
channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
@@ -882,161 +890,117 @@ func TestUserPermDelete(t *testing.T) {
}
func TestSendPasswordReset(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ LinkUserToTeam(user, team)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
- data := make(map[string]string)
- data["email"] = user.Email
- data["name"] = team.Name
-
- if _, err := Client.SendPasswordReset(data); err != nil {
+ if _, err := Client.SendPasswordReset(user.Email); err != nil {
t.Fatal(err)
}
- data["email"] = ""
- if _, err := Client.SendPasswordReset(data); err == nil {
+ if _, err := Client.SendPasswordReset(""); err == nil {
t.Fatal("Should have errored - no email")
}
- data["email"] = "junk@junk.com"
- if _, err := Client.SendPasswordReset(data); err == nil {
+ if _, err := Client.SendPasswordReset("junk@junk.com"); err == nil {
t.Fatal("Should have errored - bad email")
}
- data["email"] = user.Email
- data["name"] = ""
- if _, err := Client.SendPasswordReset(data); err == nil {
- t.Fatal("Should have errored - no name")
- }
-
- data["name"] = "junk"
- if _, err := Client.SendPasswordReset(data); err == nil {
- t.Fatal("Should have errored - bad name")
- }
-
- user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", AuthData: "1", AuthService: "random"}
+ user2 := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", AuthData: "1", AuthService: "random"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
+ LinkUserToTeam(user2, team)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
- data["email"] = user2.Email
- data["name"] = team.Name
- if _, err := Client.SendPasswordReset(data); err == nil {
+ if _, err := Client.SendPasswordReset(user2.Email); err == nil {
t.Fatal("should have errored - SSO user can't send reset password link")
}
}
func TestResetPassword(t *testing.T) {
- Setup()
+ th := Setup().InitSystemAdmin()
+ Client := th.SystemAdminClient
+ team := th.SystemAdminTeam
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ LinkUserToTeam(user, team)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
- data := make(map[string]string)
- data["new_password"] = "newpwd"
- props := make(map[string]string)
- props["user_id"] = user.Id
- props["time"] = fmt.Sprintf("%v", model.GetMillis())
- data["data"] = model.MapToJson(props)
- data["hash"] = model.HashPassword(fmt.Sprintf("%v:%v", data["data"], utils.Cfg.EmailSettings.PasswordResetSalt))
- data["name"] = team.Name
+ Client.Must(Client.SendPasswordReset(user.Email))
- if _, err := Client.ResetPassword(data); err != nil {
- t.Fatal(err)
+ var recovery *model.PasswordRecovery
+ if result := <-Srv.Store.PasswordRecovery().Get(user.Id); result.Err != nil {
+ t.Fatal(result.Err)
+ } else {
+ recovery = result.Data.(*model.PasswordRecovery)
}
- data["new_password"] = ""
- if _, err := Client.ResetPassword(data); err == nil {
+ if _, err := Client.ResetPassword(recovery.Code, ""); err == nil {
t.Fatal("Should have errored - no password")
}
- data["new_password"] = "npwd"
- if _, err := Client.ResetPassword(data); err == nil {
+ if _, err := Client.ResetPassword(recovery.Code, "newp"); err == nil {
t.Fatal("Should have errored - password too short")
}
- data["new_password"] = "newpwd"
- data["hash"] = ""
- if _, err := Client.ResetPassword(data); err == nil {
- t.Fatal("Should have errored - no hash")
+ if _, err := Client.ResetPassword("", "newpwd"); err == nil {
+ t.Fatal("Should have errored - no code")
}
- data["hash"] = "junk"
- if _, err := Client.ResetPassword(data); err == nil {
- t.Fatal("Should have errored - bad hash")
+ if _, err := Client.ResetPassword("junk", "newpwd"); err == nil {
+ t.Fatal("Should have errored - bad code")
}
- props["user_id"] = ""
- data["data"] = model.MapToJson(props)
- if _, err := Client.ResetPassword(data); err == nil {
- t.Fatal("Should have errored - no user id")
+ code := ""
+ for i := 0; i < model.PASSWORD_RECOVERY_CODE_SIZE; i++ {
+ code += "a"
}
-
- data["user_id"] = "12345678901234567890123456"
- data["data"] = model.MapToJson(props)
- if _, err := Client.ResetPassword(data); err == nil {
- t.Fatal("Should have errored - bad user id")
- }
-
- props["user_id"] = user.Id
- props["time"] = ""
- data["data"] = model.MapToJson(props)
- if _, err := Client.ResetPassword(data); err == nil {
- t.Fatal("Should have errored - no time")
+ if _, err := Client.ResetPassword(code, "newpwd"); err == nil {
+ t.Fatal("Should have errored - bad code")
}
- props["time"] = fmt.Sprintf("%v", model.GetMillis())
- data["data"] = model.MapToJson(props)
- data["domain"] = ""
- if _, err := Client.ResetPassword(data); err == nil {
- t.Fatal("Should have errored - no domain")
+ if _, err := Client.ResetPassword(recovery.Code, "newpwd"); err != nil {
+ t.Fatal(err)
}
- data["domain"] = "junk"
- if _, err := Client.ResetPassword(data); err == nil {
- t.Fatal("Should have errored - bad domain")
- }
+ Client.Logout()
+ Client.Must(Client.LoginById(user.Id, "newpwd"))
+ Client.SetTeamId(team.Id)
- team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test2@nowhere.com", Type: model.TEAM_OPEN}
- team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team)
+ Client.Must(Client.SendPasswordReset(user.Email))
- data["domain"] = team2.Name
- if _, err := Client.ResetPassword(data); err == nil {
- t.Fatal("Should have errored - domain team doesn't match user team")
+ if result := <-Srv.Store.PasswordRecovery().Get(user.Id); result.Err != nil {
+ t.Fatal(result.Err)
+ } else {
+ recovery = result.Data.(*model.PasswordRecovery)
}
- user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", AuthData: "1", AuthService: "random"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
+ if result := <-Srv.Store.User().UpdateAuthData(user.Id, "random", "1", ""); result.Err != nil {
+ t.Fatal(result.Err)
+ }
- data["new_password"] = "newpwd"
- props["user_id"] = user2.Id
- props["time"] = fmt.Sprintf("%v", model.GetMillis())
- data["data"] = model.MapToJson(props)
- data["hash"] = model.HashPassword(fmt.Sprintf("%v:%v", data["data"], utils.Cfg.EmailSettings.PasswordResetSalt))
- data["name"] = team.Name
- if _, err := Client.ResetPassword(data); err == nil {
- t.Fatal("should have errored - SSO user can't reset password")
+ if _, err := Client.ResetPassword(recovery.Code, "newpwd"); err == nil {
+ t.Fatal("Should have errored - sso user")
}
}
func TestUserUpdateNotify(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd", Roles: ""}
+ user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd", Roles: ""}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ LinkUserToTeam(user, team)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
data := make(map[string]string)
@@ -1050,6 +1014,7 @@ func TestUserUpdateNotify(t *testing.T) {
}
Client.LoginByEmail(team.Name, user.Email, "pwd")
+ Client.SetTeamId(team.Id)
if result, err := Client.UpdateUserNotify(data); err != nil {
t.Fatal(err)
@@ -1099,7 +1064,8 @@ func TestUserUpdateNotify(t *testing.T) {
}
func TestFuzzyUserCreate(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
@@ -1115,30 +1081,36 @@ func TestFuzzyUserCreate(t *testing.T) {
testEmail = utils.FUZZY_STRINGS_EMAILS[i]
}
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + testEmail, Nickname: testName, Password: "hello"}
+ user := model.User{Email: strings.ToLower(model.NewId()) + testEmail, Nickname: testName, Password: "hello"}
- _, err := Client.CreateUser(&user, "")
+ ruser, err := Client.CreateUser(&user, "")
if err != nil {
t.Fatal(err)
}
+
+ LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team))
}
}
func TestStatuses(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
+ LinkUserToTeam(ruser, rteam.Data.(*model.Team))
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
- user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser2 := Client.Must(Client.CreateUser(&user2, "")).Data.(*model.User)
+ LinkUserToTeam(ruser2, rteam.Data.(*model.Team))
store.Must(Srv.Store.User().VerifyEmail(ruser2.Id))
Client.LoginByEmail(team.Name, user.Email, user.Password)
+ Client.SetTeamId(team.Id)
userIds := []string{ruser2.Id}
@@ -1150,6 +1122,7 @@ func TestStatuses(t *testing.T) {
statuses := r1.Data.(map[string]string)
if len(statuses) != 1 {
+ t.Log(statuses)
t.Fatal("invalid number of statuses")
}
@@ -1162,13 +1135,15 @@ func TestStatuses(t *testing.T) {
}
func TestEmailToOAuth(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
+ LinkUserToTeam(ruser, rteam.Data.(*model.Team))
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
m := map[string]string{}
@@ -1211,17 +1186,20 @@ func TestEmailToOAuth(t *testing.T) {
}
func TestOAuthToEmail(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
+ LinkUserToTeam(ruser, rteam.Data.(*model.Team))
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
- user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser2 := Client.Must(Client.CreateUser(&user2, "")).Data.(*model.User)
+ LinkUserToTeam(ruser2, rteam.Data.(*model.Team))
store.Must(Srv.Store.User().VerifyEmail(ruser2.Id))
m := map[string]string{}
@@ -1246,12 +1224,6 @@ func TestOAuthToEmail(t *testing.T) {
t.Fatal("should have failed - missing email")
}
- m["email"] = ruser.Email
- m["team_name"] = "junk"
- if _, err := Client.OAuthToEmail(m); err == nil {
- t.Fatal("should have failed - bad team name")
- }
-
m["team_name"] = team.Name
m["email"] = "junk"
if _, err := Client.OAuthToEmail(m); err == nil {
@@ -1265,13 +1237,15 @@ func TestOAuthToEmail(t *testing.T) {
}
func TestLDAPToEmail(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
+ LinkUserToTeam(ruser, rteam.Data.(*model.Team))
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
Client.LoginByEmail(team.Name, user.Email, user.Password)
@@ -1316,13 +1290,15 @@ func TestLDAPToEmail(t *testing.T) {
}
func TestEmailToLDAP(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
+ LinkUserToTeam(ruser, rteam.Data.(*model.Team))
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
Client.LoginByEmail(team.Name, user.Email, user.Password)
@@ -1377,49 +1353,83 @@ func TestEmailToLDAP(t *testing.T) {
}
}
-func TestMeLoggedIn(t *testing.T) {
- Setup()
+func TestMeInitialLoad(t *testing.T) {
+ th := Setup().InitBasic()
- team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- rteam, _ := Client.CreateTeam(&team)
+ if result, err := th.BasicClient.GetInitialLoad(); err != nil {
+ t.Fatal(err)
+ } else {
+ il := result.Data.(*model.InitialLoad)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
+ if il.User == nil {
+ t.Fatal("should be valid")
+ }
- Client.AuthToken = "invalid"
+ if il.Preferences == nil {
+ t.Fatal("should be valid")
+ }
- if result, err := Client.GetMeLoggedIn(); err != nil {
- t.Fatal(err)
- } else {
- meLoggedIn := result.Data.(map[string]string)
+ if len(il.Teams) != 1 {
+ t.Fatal("should be valid")
+ }
- if val, ok := meLoggedIn["logged_in"]; !ok || val != "false" {
- t.Fatal("Got: " + val)
+ if len(il.TeamMembers) != 1 {
+ t.Fatal("should be valid")
+ }
+
+ if len(il.ClientCfg) == 0 {
+ t.Fatal("should be valid")
+ }
+
+ if len(il.LicenseCfg) == 0 {
+ t.Fatal("should be valid")
}
}
- Client.LoginByEmail(team.Name, user.Email, user.Password)
+ th.BasicClient.Logout()
- if result, err := Client.GetMeLoggedIn(); err != nil {
+ if result, err := th.BasicClient.GetInitialLoad(); err != nil {
t.Fatal(err)
} else {
- meLoggedIn := result.Data.(map[string]string)
+ il := result.Data.(*model.InitialLoad)
+
+ if il.User != nil {
+ t.Fatal("should be valid")
+ }
- if val, ok := meLoggedIn["logged_in"]; !ok || val != "true" {
- t.Fatal("Got: " + val)
+ if il.Preferences != nil {
+ t.Fatal("should be valid")
+ }
+
+ if len(il.Teams) != 0 {
+ t.Fatal("should be valid")
+ }
+
+ if len(il.TeamMembers) != 0 {
+ t.Fatal("should be valid")
+ }
+
+ if len(il.ClientCfg) == 0 {
+ t.Fatal("should be valid")
+ }
+
+ if len(il.LicenseCfg) == 0 {
+ t.Fatal("should be valid")
}
}
+
}
func TestGenerateMfaQrCode(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser, _ := Client.CreateUser(&user, "")
+ LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team))
store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id))
Client.Logout()
@@ -1438,13 +1448,26 @@ func TestGenerateMfaQrCode(t *testing.T) {
}
func TestUpdateMfa(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
+
+ if utils.License.Features.MFA == nil {
+ utils.License.Features.MFA = new(bool)
+ }
+
+ enableMfa := *utils.Cfg.ServiceSettings.EnableMultifactorAuthentication
+ defer func() {
+ utils.IsLicensed = false
+ *utils.License.Features.MFA = false
+ *utils.Cfg.ServiceSettings.EnableMultifactorAuthentication = enableMfa
+ }()
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser, _ := Client.CreateUser(&user, "")
+ LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team))
store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id))
Client.Logout()
@@ -1463,20 +1486,30 @@ func TestUpdateMfa(t *testing.T) {
t.Fatal("should have failed - not licensed")
}
- // need to add more test cases when license and config can be configured for tests
+ utils.IsLicensed = true
+ *utils.License.Features.MFA = true
+ *utils.Cfg.ServiceSettings.EnableMultifactorAuthentication = true
+
+ if _, err := Client.UpdateMfa(true, "123456"); err == nil {
+ t.Fatal("should have failed - bad token")
+ }
+
+ // need to add more test cases when enterprise bits can be loaded into tests
}
func TestCheckMfa(t *testing.T) {
- Setup()
+ th := Setup()
+ Client := th.CreateClient()
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser, _ := Client.CreateUser(&user, "")
+ LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team))
store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id))
- if result, err := Client.CheckMfa(model.USER_AUTH_SERVICE_EMAIL, team.Name, user.Email); err != nil {
+ if result, err := Client.CheckMfa(model.USER_AUTH_SERVICE_EMAIL, user.Email); err != nil {
t.Fatal(err)
} else {
resp := result.Data.(map[string]string)
@@ -1485,5 +1518,5 @@ func TestCheckMfa(t *testing.T) {
}
}
- // need to add more test cases when license and config can be configured for tests
+ // need to add more test cases when enterprise bits can be loaded into tests
}
diff --git a/api/web_conn.go b/api/web_conn.go
index 515a8ab31..397af0696 100644
--- a/api/web_conn.go
+++ b/api/web_conn.go
@@ -7,7 +7,6 @@ import (
l4g "github.com/alecthomas/log4go"
"github.com/gorilla/websocket"
"github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
"time"
)
@@ -21,14 +20,15 @@ const (
)
type WebConn struct {
- WebSocket *websocket.Conn
- Send chan *model.Message
- TeamId string
- UserId string
- ChannelAccessCache map[string]bool
+ WebSocket *websocket.Conn
+ Send chan *model.Message
+ SessionId string
+ UserId string
+ hasPermissionsToChannel map[string]bool
+ hasPermissionsToTeam map[string]bool
}
-func NewWebConn(ws *websocket.Conn, teamId string, userId string, sessionId string) *WebConn {
+func NewWebConn(ws *websocket.Conn, userId string, sessionId string) *WebConn {
go func() {
achan := Srv.Store.User().UpdateUserAndSessionActivity(userId, sessionId, model.GetMillis())
pchan := Srv.Store.User().UpdateLastPingAt(userId, model.GetMillis())
@@ -42,7 +42,14 @@ func NewWebConn(ws *websocket.Conn, teamId string, userId string, sessionId stri
}
}()
- return &WebConn{Send: make(chan *model.Message, 64), WebSocket: ws, UserId: userId, TeamId: teamId, ChannelAccessCache: make(map[string]bool)}
+ return &WebConn{
+ Send: make(chan *model.Message, 64),
+ WebSocket: ws,
+ UserId: userId,
+ SessionId: sessionId,
+ hasPermissionsToChannel: make(map[string]bool),
+ hasPermissionsToTeam: make(map[string]bool),
+ }
}
func (c *WebConn) readPump() {
@@ -69,7 +76,6 @@ func (c *WebConn) readPump() {
if err := c.WebSocket.ReadJSON(&msg); err != nil {
return
} else {
- msg.TeamId = c.TeamId
msg.UserId = c.UserId
PublishAndForget(&msg)
}
@@ -107,19 +113,53 @@ func (c *WebConn) writePump() {
}
}
-func (c *WebConn) updateChannelAccessCache(channelId string) bool {
- allowed := hasPermissionsToChannel(Srv.Store.Channel().CheckPermissionsTo(c.TeamId, channelId, c.UserId))
- c.ChannelAccessCache[channelId] = allowed
+func (c *WebConn) InvalidateCache() {
+ c.hasPermissionsToChannel = make(map[string]bool)
+ c.hasPermissionsToTeam = make(map[string]bool)
+}
+
+func (c *WebConn) HasPermissionsToTeam(teamId string) bool {
+ perm, ok := c.hasPermissionsToTeam[teamId]
+ if !ok {
+ session := GetSession(c.SessionId)
+ if session == nil {
+ perm = false
+ c.hasPermissionsToTeam[teamId] = perm
+ } else {
+ member := session.GetTeamByTeamId(teamId)
+
+ if member != nil {
+ perm = true
+ c.hasPermissionsToTeam[teamId] = perm
+ } else {
+ perm = true
+ c.hasPermissionsToTeam[teamId] = perm
+ }
- return allowed
+ }
+ }
+
+ return perm
}
-func hasPermissionsToChannel(sc store.StoreChannel) bool {
- if cresult := <-sc; cresult.Err != nil {
- return false
- } else if cresult.Data.(int64) != 1 {
- return false
+func (c *WebConn) HasPermissionsToChannel(channelId string) bool {
+ perm, ok := c.hasPermissionsToChannel[channelId]
+ if !ok {
+ if cresult := <-Srv.Store.Channel().CheckPermissionsToNoTeam(channelId, c.UserId); cresult.Err != nil {
+ perm = false
+ c.hasPermissionsToChannel[channelId] = perm
+ } else {
+ count := cresult.Data.(int64)
+
+ if count == 1 {
+ perm = true
+ c.hasPermissionsToChannel[channelId] = perm
+ } else {
+ perm = false
+ c.hasPermissionsToChannel[channelId] = perm
+ }
+ }
}
- return true
+ return perm
}
diff --git a/api/web_hub.go b/api/web_hub.go
index 5fe9d6ae8..241ebcef0 100644
--- a/api/web_hub.go
+++ b/api/web_hub.go
@@ -10,19 +10,21 @@ import (
)
type Hub struct {
- teamHubs map[string]*TeamHub
- register chan *WebConn
- unregister chan *WebConn
- broadcast chan *model.Message
- stop chan string
+ connections map[*WebConn]bool
+ register chan *WebConn
+ unregister chan *WebConn
+ broadcast chan *model.Message
+ stop chan string
+ invalidateUser chan string
}
var hub = &Hub{
- register: make(chan *WebConn),
- unregister: make(chan *WebConn),
- teamHubs: make(map[string]*TeamHub),
- broadcast: make(chan *model.Message),
- stop: make(chan string),
+ register: make(chan *WebConn),
+ unregister: make(chan *WebConn),
+ connections: make(map[*WebConn]bool),
+ broadcast: make(chan *model.Message),
+ stop: make(chan string),
+ invalidateUser: make(chan string),
}
func PublishAndForget(message *model.Message) {
@@ -31,16 +33,8 @@ func PublishAndForget(message *model.Message) {
}()
}
-func UpdateChannelAccessCache(teamId, userId, channelId string) {
- if nh, ok := hub.teamHubs[teamId]; ok {
- nh.UpdateChannelAccessCache(userId, channelId)
- }
-}
-
-func UpdateChannelAccessCacheAndForget(teamId, userId, channelId string) {
- go func() {
- UpdateChannelAccessCache(teamId, userId, channelId)
- }()
+func InvalidateCacheForUser(userId string) {
+ hub.invalidateUser <- userId
}
func (h *Hub) Register(webConn *WebConn) {
@@ -65,34 +59,92 @@ func (h *Hub) Start() {
go func() {
for {
select {
+ case webCon := <-h.register:
+ h.connections[webCon] = true
- case c := <-h.register:
- nh := h.teamHubs[c.TeamId]
-
- if nh == nil {
- nh = NewTeamHub(c.TeamId)
- h.teamHubs[c.TeamId] = nh
- nh.Start()
+ case webCon := <-h.unregister:
+ if _, ok := h.connections[webCon]; ok {
+ delete(h.connections, webCon)
+ close(webCon.Send)
}
-
- nh.Register(c)
-
- case c := <-h.unregister:
- if nh, ok := h.teamHubs[c.TeamId]; ok {
- nh.Unregister(c)
+ case userId := <-h.invalidateUser:
+ for webCon := range h.connections {
+ if webCon.UserId == userId {
+ webCon.InvalidateCache()
+ }
}
+
case msg := <-h.broadcast:
- nh := h.teamHubs[msg.TeamId]
- if nh != nil {
- nh.broadcast <- msg
+ for webCon := range h.connections {
+ if shouldSendEvent(webCon, msg) {
+ select {
+ case webCon.Send <- msg:
+ default:
+ close(webCon.Send)
+ delete(h.connections, webCon)
+ }
+ }
}
+
case s := <-h.stop:
l4g.Debug(utils.T("api.web_hub.start.stopping.debug"), s)
- for _, v := range h.teamHubs {
- v.Stop()
+
+ for webCon := range h.connections {
+ webCon.WebSocket.Close()
}
+
return
}
}
}()
}
+
+func shouldSendEvent(webCon *WebConn, msg *model.Message) bool {
+
+ if webCon.UserId == msg.UserId {
+ // Don't need to tell the user they are typing
+ if msg.Action == model.ACTION_TYPING {
+ return false
+ }
+
+ // We have to make sure the user is in the channel. Otherwise system messages that
+ // post about users in channels they are not in trigger warnings.
+ if len(msg.ChannelId) > 0 {
+ allowed := webCon.HasPermissionsToChannel(msg.ChannelId)
+
+ if !allowed {
+ return false
+ }
+ }
+ } else {
+ // Don't share a user's view or preference events with other users
+ if msg.Action == model.ACTION_CHANNEL_VIEWED {
+ return false
+ } else if msg.Action == model.ACTION_PREFERENCE_CHANGED {
+ return false
+ } else if msg.Action == model.ACTION_EPHEMERAL_MESSAGE {
+ // For now, ephemeral messages are sent directly to individual users
+ return false
+ }
+
+ // Only report events to users who are in the team for the event
+ if len(msg.TeamId) > 0 {
+ allowed := webCon.HasPermissionsToTeam(msg.TeamId)
+
+ if !allowed {
+ return false
+ }
+ }
+
+ // Only report events to users who are in the channel for the event
+ if len(msg.ChannelId) > 0 {
+ allowed := webCon.HasPermissionsToChannel(msg.ChannelId)
+
+ if !allowed {
+ return false
+ }
+ }
+ }
+
+ return true
+}
diff --git a/api/web_socket.go b/api/web_socket.go
index e15732f43..72a9c61a6 100644
--- a/api/web_socket.go
+++ b/api/web_socket.go
@@ -5,16 +5,15 @@ package api
import (
l4g "github.com/alecthomas/log4go"
- "github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"net/http"
)
-func InitWebSocket(r *mux.Router) {
+func InitWebSocket() {
l4g.Debug(utils.T("api.web_socket.init.debug"))
- r.Handle("/websocket", ApiUserRequiredTrustRequester(connect)).Methods("GET")
+ BaseRoutes.Users.Handle("/websocket", ApiUserRequiredTrustRequester(connect)).Methods("GET")
hub.Start()
}
@@ -34,7 +33,7 @@ func connect(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- wc := NewWebConn(ws, c.Session.TeamId, c.Session.UserId, c.Session.Id)
+ wc := NewWebConn(ws, c.Session.UserId, c.Session.Id)
hub.Register(wc)
go wc.writePump()
wc.readPump()
diff --git a/api/web_socket_test.go b/api/web_socket_test.go
index 2c0ac61eb..7cb04e93e 100644
--- a/api/web_socket_test.go
+++ b/api/web_socket_test.go
@@ -6,7 +6,6 @@ package api
import (
"github.com/gorilla/websocket"
"github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
"net/http"
"testing"
@@ -14,22 +13,14 @@ import (
)
func TestSocket(t *testing.T) {
- Setup()
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+ channel1 := th.BasicChannel
+ channel2 := th.CreateChannel(Client, team)
+ Client.Must(Client.AddChannelMember(channel1.Id, th.BasicUser2.Id))
- url := "ws://localhost" + utils.Cfg.ServiceSettings.ListenAddress + "/api/v1/websocket"
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user1.Id))
- Client.LoginByEmail(team.Name, user1.Email, "pwd")
-
- channel1 := &model.Channel{DisplayName: "Test Web Scoket 1", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
-
- channel2 := &model.Channel{DisplayName: "Test Web Scoket 2", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
+ url := "ws://localhost" + utils.Cfg.ServiceSettings.ListenAddress + model.API_URL_SUFFIX + "/users/websocket"
header1 := http.Header{}
header1.Set(model.HEADER_AUTH, "BEARER "+Client.AuthToken)
@@ -39,10 +30,7 @@ func TestSocket(t *testing.T) {
t.Fatal(err)
}
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user2.Id))
- Client.LoginByEmail(team.Name, user2.Email, "pwd")
+ th.LoginBasic2()
header2 := http.Header{}
header2.Set(model.HEADER_AUTH, "BEARER "+Client.AuthToken)
@@ -53,21 +41,11 @@ func TestSocket(t *testing.T) {
}
time.Sleep(300 * time.Millisecond)
- Client.Must(Client.JoinChannel(channel1.Id))
- // Read the user_added message that gets generated
var rmsg model.Message
- if err := c2.ReadJSON(&rmsg); err != nil {
- t.Fatal(err)
- }
-
- // Read the second user_added message that gets generated
- if err := c2.ReadJSON(&rmsg); err != nil {
- t.Fatal(err)
- }
// Test sending message without a channelId
- m := model.NewMessage("", "", "", model.ACTION_TYPING)
+ m := model.NewMessage(team.Id, "", "", model.ACTION_TYPING)
m.Add("RootId", model.NewId())
m.Add("ParentId", model.NewId())
@@ -77,6 +55,8 @@ func TestSocket(t *testing.T) {
t.Fatal(err)
}
+ t.Log(rmsg.ToJson())
+
if team.Id != rmsg.TeamId {
t.Fatal("Ids do not match")
}
@@ -86,7 +66,7 @@ func TestSocket(t *testing.T) {
}
// Test sending messsage to Channel you have access to
- m = model.NewMessage("", channel1.Id, "", model.ACTION_TYPING)
+ m = model.NewMessage(team.Id, channel1.Id, "", model.ACTION_TYPING)
m.Add("RootId", model.NewId())
m.Add("ParentId", model.NewId())
diff --git a/api/web_team_hub.go b/api/web_team_hub.go
deleted file mode 100644
index 9d1c56f15..000000000
--- a/api/web_team_hub.go
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package api
-
-import (
- l4g "github.com/alecthomas/log4go"
- "github.com/mattermost/platform/model"
- "github.com/mattermost/platform/utils"
-)
-
-type TeamHub struct {
- connections map[*WebConn]bool
- broadcast chan *model.Message
- register chan *WebConn
- unregister chan *WebConn
- stop chan bool
- teamId string
-}
-
-func NewTeamHub(teamId string) *TeamHub {
- return &TeamHub{
- broadcast: make(chan *model.Message),
- register: make(chan *WebConn),
- unregister: make(chan *WebConn),
- connections: make(map[*WebConn]bool),
- stop: make(chan bool),
- teamId: teamId,
- }
-}
-
-func (h *TeamHub) Register(webConn *WebConn) {
- h.register <- webConn
-}
-
-func (h *TeamHub) Unregister(webConn *WebConn) {
- h.unregister <- webConn
-}
-
-func (h *TeamHub) Stop() {
- h.stop <- true
-}
-
-func (h *TeamHub) Start() {
- go func() {
- for {
- select {
- case webCon := <-h.register:
- h.connections[webCon] = true
- case webCon := <-h.unregister:
- if _, ok := h.connections[webCon]; ok {
- delete(h.connections, webCon)
- close(webCon.Send)
- }
- case msg := <-h.broadcast:
- for webCon := range h.connections {
- if ShouldSendEvent(webCon, msg) {
- select {
- case webCon.Send <- msg:
- default:
- close(webCon.Send)
- delete(h.connections, webCon)
- }
- }
- }
- case s := <-h.stop:
- if s {
-
- l4g.Debug(utils.T("api.web_team_hun.start.debug"), h.teamId)
-
- for webCon := range h.connections {
- webCon.WebSocket.Close()
- }
-
- return
- }
- }
- }
- }()
-}
-
-func (h *TeamHub) UpdateChannelAccessCache(userId string, channelId string) {
- for webCon := range h.connections {
- if webCon.UserId == userId {
- webCon.updateChannelAccessCache(channelId)
- break
- }
- }
-}
-
-func ShouldSendEvent(webCon *WebConn, msg *model.Message) bool {
-
- if webCon.UserId == msg.UserId {
- // Don't need to tell the user they are typing
- if msg.Action == model.ACTION_TYPING {
- return false
- }
- } else {
- // Don't share a user's view or preference events with other users
- if msg.Action == model.ACTION_CHANNEL_VIEWED {
- return false
- } else if msg.Action == model.ACTION_PREFERENCE_CHANGED {
- return false
- } else if msg.Action == model.ACTION_EPHEMERAL_MESSAGE {
- // For now, ephemeral messages are sent directly to individual users
- return false
- }
-
- // Only report events to a user who is the subject of the event, or is in the channel of the event
- if len(msg.ChannelId) > 0 {
- allowed, ok := webCon.ChannelAccessCache[msg.ChannelId]
- if !ok {
- allowed = webCon.updateChannelAccessCache(msg.ChannelId)
- }
-
- if !allowed {
- return false
- }
- }
- }
-
- return true
-}
diff --git a/api/webhook.go b/api/webhook.go
index fe1aa1175..ea628e39c 100644
--- a/api/webhook.go
+++ b/api/webhook.go
@@ -14,20 +14,19 @@ import (
"github.com/mattermost/platform/utils"
)
-func InitWebhook(r *mux.Router) {
+func InitWebhook() {
l4g.Debug(utils.T("api.webhook.init.debug"))
- sr := r.PathPrefix("/hooks").Subrouter()
- sr.Handle("/incoming/create", ApiUserRequired(createIncomingHook)).Methods("POST")
- sr.Handle("/incoming/delete", ApiUserRequired(deleteIncomingHook)).Methods("POST")
- sr.Handle("/incoming/list", ApiUserRequired(getIncomingHooks)).Methods("GET")
+ BaseRoutes.Hooks.Handle("/incoming/create", ApiUserRequired(createIncomingHook)).Methods("POST")
+ BaseRoutes.Hooks.Handle("/incoming/delete", ApiUserRequired(deleteIncomingHook)).Methods("POST")
+ BaseRoutes.Hooks.Handle("/incoming/list", ApiUserRequired(getIncomingHooks)).Methods("GET")
- sr.Handle("/outgoing/create", ApiUserRequired(createOutgoingHook)).Methods("POST")
- sr.Handle("/outgoing/regen_token", ApiUserRequired(regenOutgoingHookToken)).Methods("POST")
- sr.Handle("/outgoing/delete", ApiUserRequired(deleteOutgoingHook)).Methods("POST")
- sr.Handle("/outgoing/list", ApiUserRequired(getOutgoingHooks)).Methods("GET")
+ BaseRoutes.Hooks.Handle("/outgoing/create", ApiUserRequired(createOutgoingHook)).Methods("POST")
+ BaseRoutes.Hooks.Handle("/outgoing/regen_token", ApiUserRequired(regenOutgoingHookToken)).Methods("POST")
+ BaseRoutes.Hooks.Handle("/outgoing/delete", ApiUserRequired(deleteOutgoingHook)).Methods("POST")
+ BaseRoutes.Hooks.Handle("/outgoing/list", ApiUserRequired(getOutgoingHooks)).Methods("GET")
- sr.Handle("/{id:[A-Za-z0-9]+}", ApiAppHandler(incomingWebhook)).Methods("POST")
+ BaseRoutes.Hooks.Handle("/{id:[A-Za-z0-9]+}", ApiAppHandler(incomingWebhook)).Methods("POST")
// Old route. Remove eventually.
mr := Srv.Router
@@ -59,10 +58,10 @@ func createIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
}
cchan := Srv.Store.Channel().Get(hook.ChannelId)
- pchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, hook.ChannelId, c.Session.UserId)
+ pchan := Srv.Store.Channel().CheckPermissionsTo(c.TeamId, hook.ChannelId, c.Session.UserId)
hook.UserId = c.Session.UserId
- hook.TeamId = c.Session.TeamId
+ hook.TeamId = c.TeamId
var channel *model.Channel
if result := <-cchan; result.Err != nil {
@@ -73,7 +72,7 @@ func createIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
}
if !c.HasPermissionsToChannel(pchan, "createIncomingHook") {
- if channel.Type != model.CHANNEL_OPEN || channel.TeamId != c.Session.TeamId {
+ if channel.Type != model.CHANNEL_OPEN || channel.TeamId != c.TeamId {
c.LogAudit("fail - bad channel permissions")
return
}
@@ -149,7 +148,7 @@ func getIncomingHooks(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
- if result := <-Srv.Store.Webhook().GetIncomingByTeam(c.Session.TeamId); result.Err != nil {
+ if result := <-Srv.Store.Webhook().GetIncomingByTeam(c.TeamId); result.Err != nil {
c.Err = result.Err
return
} else {
@@ -183,11 +182,11 @@ func createOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) {
}
hook.CreatorId = c.Session.UserId
- hook.TeamId = c.Session.TeamId
+ hook.TeamId = c.TeamId
if len(hook.ChannelId) != 0 {
cchan := Srv.Store.Channel().Get(hook.ChannelId)
- pchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, hook.ChannelId, c.Session.UserId)
+ pchan := Srv.Store.Channel().CheckPermissionsTo(c.TeamId, hook.ChannelId, c.Session.UserId)
var channel *model.Channel
if result := <-cchan; result.Err != nil {
@@ -199,11 +198,14 @@ func createOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) {
if channel.Type != model.CHANNEL_OPEN {
c.LogAudit("fail - not open channel")
+ c.Err = model.NewLocAppError("createOutgoingHook", "api.webhook.create_outgoing.not_open.app_error", nil, "")
+ return
}
if !c.HasPermissionsToChannel(pchan, "createOutgoingHook") {
- if channel.Type != model.CHANNEL_OPEN || channel.TeamId != c.Session.TeamId {
+ if channel.Type != model.CHANNEL_OPEN || channel.TeamId != c.TeamId {
c.LogAudit("fail - bad channel permissions")
+ c.Err = model.NewLocAppError("createOutgoingHook", "api.webhook.create_outgoing.permissions.app_error", nil, "")
return
}
}
@@ -237,7 +239,7 @@ func getOutgoingHooks(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
- if result := <-Srv.Store.Webhook().GetOutgoingByTeam(c.Session.TeamId); result.Err != nil {
+ if result := <-Srv.Store.Webhook().GetOutgoingByTeam(c.TeamId); result.Err != nil {
c.Err = result.Err
return
} else {
@@ -292,7 +294,7 @@ func deleteOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) {
}
func regenOutgoingHookToken(c *Context, w http.ResponseWriter, r *http.Request) {
- if !utils.Cfg.ServiceSettings.EnableIncomingWebhooks {
+ if !utils.Cfg.ServiceSettings.EnableOutgoingWebhooks {
c.Err = model.NewLocAppError("regenOutgoingHookToken", "api.webhook.regen_outgoing_token.disabled.app_error", nil, "")
c.Err.StatusCode = http.StatusNotImplemented
return
@@ -323,7 +325,7 @@ func regenOutgoingHookToken(c *Context, w http.ResponseWriter, r *http.Request)
} else {
hook = result.Data.(*model.OutgoingWebhook)
- if c.Session.TeamId != hook.TeamId && c.Session.UserId != hook.CreatorId && !c.IsTeamAdmin() {
+ if c.TeamId != hook.TeamId && c.Session.UserId != hook.CreatorId && !c.IsTeamAdmin() {
c.LogAudit("fail - inappropriate permissions")
c.Err = model.NewLocAppError("regenOutgoingHookToken", "api.webhook.regen_outgoing_token.permissions.app_error", nil, "user_id="+c.Session.UserId)
return
@@ -398,7 +400,7 @@ func incomingWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
if len(channelName) != 0 {
if channelName[0] == '@' {
- if result := <-Srv.Store.User().GetByUsername(hook.TeamId, channelName[1:]); result.Err != nil {
+ if result := <-Srv.Store.User().GetByUsername(channelName[1:]); result.Err != nil {
c.Err = model.NewLocAppError("incomingWebhook", "web.incoming_webhook.user.app_error", nil, "err="+result.Err.Message)
return
} else {
@@ -426,7 +428,11 @@ func incomingWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
pchan := Srv.Store.Channel().CheckPermissionsTo(hook.TeamId, channel.Id, hook.UserId)
// create a mock session
- c.Session = model.Session{UserId: hook.UserId, TeamId: hook.TeamId, IsOAuth: false}
+ c.Session = model.Session{
+ UserId: hook.UserId,
+ TeamMembers: []*model.TeamMember{{TeamId: hook.TeamId, UserId: hook.UserId}},
+ IsOAuth: false,
+ }
if !c.HasPermissionsToChannel(pchan, "createIncomingHook") && channel.Type != model.CHANNEL_OPEN {
c.Err = model.NewLocAppError("incomingWebhook", "web.incoming_webhook.permissions.app_error", nil, "")
diff --git a/api/webhook_test.go b/api/webhook_test.go
index 4f85d178d..5198056cc 100644
--- a/api/webhook_test.go
+++ b/api/webhook_test.go
@@ -4,416 +4,599 @@
package api
import (
+ "fmt"
"github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
"testing"
"time"
)
func TestCreateIncomingHook(t *testing.T) {
- Setup()
+ th := Setup().InitSystemAdmin()
+ Client := th.SystemAdminClient
+ user := th.SystemAdminUser
+ team := th.SystemAdminTeam
+ channel1 := th.CreateChannel(Client, team)
+ channel2 := th.CreatePrivateChannel(Client, team)
+ user2 := th.CreateUser(Client)
+ LinkUserToTeam(user2, team)
+
enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks
- enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks
+ enableAdminOnlyHooks := utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations
defer func() {
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks
- utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks
+ utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = enableAdminOnlyHooks
}()
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true
- utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true
+ *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
+ hook := &model.IncomingWebhook{ChannelId: channel1.Id}
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
+ var rhook *model.IncomingWebhook
+ if result, err := Client.CreateIncomingWebhook(hook); err != nil {
+ t.Fatal(err)
+ } else {
+ rhook = result.Data.(*model.IncomingWebhook)
+ }
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ if hook.ChannelId != rhook.ChannelId {
+ t.Fatal("channel ids didn't match")
+ }
- channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ if rhook.UserId != user.Id {
+ t.Fatal("user ids didn't match")
+ }
- channel2 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
+ if rhook.TeamId != team.Id {
+ t.Fatal("team ids didn't match")
+ }
- hook := &model.IncomingWebhook{ChannelId: channel1.Id}
+ hook = &model.IncomingWebhook{ChannelId: "junk"}
+ if _, err := Client.CreateIncomingWebhook(hook); err == nil {
+ t.Fatal("should have failed - bad channel id")
+ }
- if utils.Cfg.ServiceSettings.EnableIncomingWebhooks {
- var rhook *model.IncomingWebhook
- if result, err := Client.CreateIncomingWebhook(hook); err != nil {
- t.Fatal(err)
- } else {
- rhook = result.Data.(*model.IncomingWebhook)
+ hook = &model.IncomingWebhook{ChannelId: channel2.Id, UserId: "123", TeamId: "456"}
+ if result, err := Client.CreateIncomingWebhook(hook); err != nil {
+ t.Fatal(err)
+ } else {
+ if result.Data.(*model.IncomingWebhook).UserId != user.Id {
+ t.Fatal("bad user id wasn't overwritten")
}
-
- if hook.ChannelId != rhook.ChannelId {
- t.Fatal("channel ids didn't match")
+ if result.Data.(*model.IncomingWebhook).TeamId != team.Id {
+ t.Fatal("bad team id wasn't overwritten")
}
+ }
- if rhook.UserId != user.Id {
- t.Fatal("user ids didn't match")
- }
+ Client.Logout()
+ Client.Must(Client.LoginById(user2.Id, user2.Password))
+ Client.SetTeamId(team.Id)
- if rhook.TeamId != team.Id {
- t.Fatal("team ids didn't match")
- }
+ hook = &model.IncomingWebhook{ChannelId: channel1.Id}
- hook = &model.IncomingWebhook{ChannelId: "junk"}
- if _, err := Client.CreateIncomingWebhook(hook); err == nil {
- t.Fatal("should have failed - bad channel id")
- }
+ if _, err := Client.CreateIncomingWebhook(hook); err == nil {
+ t.Fatal("should have failed - not system/team admin")
+ }
- hook = &model.IncomingWebhook{ChannelId: channel2.Id, UserId: "123", TeamId: "456"}
- if result, err := Client.CreateIncomingWebhook(hook); err != nil {
- t.Fatal(err)
- } else {
- if result.Data.(*model.IncomingWebhook).UserId != user.Id {
- t.Fatal("bad user id wasn't overwritten")
- }
- if result.Data.(*model.IncomingWebhook).TeamId != team.Id {
- t.Fatal("bad team id wasn't overwritten")
- }
- }
- } else {
- if _, err := Client.CreateIncomingWebhook(hook); err == nil {
- t.Fatal("should have errored - webhooks turned off")
- }
+ *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false
+
+ if _, err := Client.CreateIncomingWebhook(hook); err != nil {
+ t.Fatal(err)
+ }
+
+ hook = &model.IncomingWebhook{ChannelId: channel2.Id}
+
+ if _, err := Client.CreateIncomingWebhook(hook); err == nil {
+ t.Fatal("should have failed - channel is private and not a member")
+ }
+
+ utils.Cfg.ServiceSettings.EnableIncomingWebhooks = false
+
+ if _, err := Client.CreateIncomingWebhook(hook); err == nil {
+ t.Fatal("should have errored - webhooks turned off")
}
}
func TestListIncomingHooks(t *testing.T) {
- Setup()
+ th := Setup().InitSystemAdmin()
+ Client := th.SystemAdminClient
+ team := th.SystemAdminTeam
+ channel1 := th.CreateChannel(Client, team)
+ user2 := th.CreateUser(Client)
+ LinkUserToTeam(user2, team)
+
enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks
- enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks
+ enableAdminOnlyHooks := utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations
defer func() {
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks
- utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks
+ utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = enableAdminOnlyHooks
}()
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true
- utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true
+ *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
+ hook1 := &model.IncomingWebhook{ChannelId: channel1.Id}
+ hook1 = Client.Must(Client.CreateIncomingWebhook(hook1)).Data.(*model.IncomingWebhook)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
+ hook2 := &model.IncomingWebhook{ChannelId: channel1.Id}
+ hook2 = Client.Must(Client.CreateIncomingWebhook(hook2)).Data.(*model.IncomingWebhook)
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ if result, err := Client.ListIncomingWebhooks(); err != nil {
+ t.Fatal(err)
+ } else {
+ hooks := result.Data.([]*model.IncomingWebhook)
- channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ if len(hooks) != 2 {
+ t.Fatal("incorrect number of hooks")
+ }
+ }
- if utils.Cfg.ServiceSettings.EnableIncomingWebhooks {
- hook1 := &model.IncomingWebhook{ChannelId: channel1.Id}
- hook1 = Client.Must(Client.CreateIncomingWebhook(hook1)).Data.(*model.IncomingWebhook)
+ Client.Logout()
+ Client.Must(Client.LoginById(user2.Id, user2.Password))
+ Client.SetTeamId(team.Id)
- hook2 := &model.IncomingWebhook{ChannelId: channel1.Id}
- hook2 = Client.Must(Client.CreateIncomingWebhook(hook2)).Data.(*model.IncomingWebhook)
+ if _, err := Client.ListIncomingWebhooks(); err == nil {
+ t.Fatal("should have errored - not system/team admin")
+ }
- if result, err := Client.ListIncomingWebhooks(); err != nil {
- t.Fatal(err)
- } else {
- hooks := result.Data.([]*model.IncomingWebhook)
+ *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false
- if len(hooks) != 2 {
- t.Fatal("incorrect number of hooks")
- }
- }
- } else {
- if _, err := Client.ListIncomingWebhooks(); err == nil {
- t.Fatal("should have errored - webhooks turned off")
- }
+ if _, err := Client.ListIncomingWebhooks(); err != nil {
+ t.Fatal(err)
+ }
+
+ utils.Cfg.ServiceSettings.EnableIncomingWebhooks = false
+
+ if _, err := Client.ListIncomingWebhooks(); err == nil {
+ t.Fatal("should have errored - webhooks turned off")
}
}
func TestDeleteIncomingHook(t *testing.T) {
- Setup()
+ th := Setup().InitSystemAdmin()
+ Client := th.SystemAdminClient
+ team := th.SystemAdminTeam
+ channel1 := th.CreateChannel(Client, team)
+ user2 := th.CreateUser(Client)
+ LinkUserToTeam(user2, team)
+
enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks
- enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks
+ enableAdminOnlyHooks := utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations
defer func() {
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks
- utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks
+ utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = enableAdminOnlyHooks
}()
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true
- utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true
+ *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
+ hook := &model.IncomingWebhook{ChannelId: channel1.Id}
+ hook = Client.Must(Client.CreateIncomingWebhook(hook)).Data.(*model.IncomingWebhook)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
+ if _, err := Client.DeleteIncomingWebhook(hook.Id); err != nil {
+ t.Fatal(err)
+ }
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ if _, err := Client.DeleteIncomingWebhook("junk"); err == nil {
+ t.Fatal("should have failed - bad id")
+ }
- channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ if _, err := Client.DeleteIncomingWebhook(""); err == nil {
+ t.Fatal("should have failed - empty id")
+ }
- if utils.Cfg.ServiceSettings.EnableIncomingWebhooks {
- hook := &model.IncomingWebhook{ChannelId: channel1.Id}
- hook = Client.Must(Client.CreateIncomingWebhook(hook)).Data.(*model.IncomingWebhook)
+ hooks := Client.Must(Client.ListIncomingWebhooks()).Data.([]*model.IncomingWebhook)
+ if len(hooks) != 0 {
+ t.Fatal("delete didn't work properly")
+ }
- data := make(map[string]string)
- data["id"] = hook.Id
+ hook = &model.IncomingWebhook{ChannelId: channel1.Id}
+ hook = Client.Must(Client.CreateIncomingWebhook(hook)).Data.(*model.IncomingWebhook)
- if _, err := Client.DeleteIncomingWebhook(data); err != nil {
- t.Fatal(err)
- }
+ Client.Logout()
+ Client.Must(Client.LoginById(user2.Id, user2.Password))
+ Client.SetTeamId(team.Id)
- hooks := Client.Must(Client.ListIncomingWebhooks()).Data.([]*model.IncomingWebhook)
- if len(hooks) != 0 {
- t.Fatal("delete didn't work properly")
- }
- } else {
- data := make(map[string]string)
- data["id"] = "123"
+ if _, err := Client.DeleteIncomingWebhook(hook.Id); err == nil {
+ t.Fatal("should have failed - not system/team admin")
+ }
- if _, err := Client.DeleteIncomingWebhook(data); err == nil {
- t.Fatal("should have errored - webhooks turned off")
- }
+ *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false
+
+ if _, err := Client.DeleteIncomingWebhook(hook.Id); err == nil {
+ t.Fatal("should have failed - not creator or team admin")
+ }
+
+ hook = &model.IncomingWebhook{ChannelId: channel1.Id}
+ hook = Client.Must(Client.CreateIncomingWebhook(hook)).Data.(*model.IncomingWebhook)
+
+ if _, err := Client.DeleteIncomingWebhook(hook.Id); err != nil {
+ t.Fatal(err)
+ }
+
+ utils.Cfg.ServiceSettings.EnableIncomingWebhooks = false
+
+ if _, err := Client.DeleteIncomingWebhook(hook.Id); err == nil {
+ t.Fatal("should have errored - webhooks turned off")
}
}
func TestCreateOutgoingHook(t *testing.T) {
- Setup()
- enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks
+ th := Setup().InitSystemAdmin()
+ Client := th.SystemAdminClient
+ user := th.SystemAdminUser
+ team := th.SystemAdminTeam
+ team2 := th.CreateTeam(Client)
+ channel1 := th.CreateChannel(Client, team)
+ channel2 := th.CreatePrivateChannel(Client, team)
+ user2 := th.CreateUser(Client)
+ LinkUserToTeam(user2, team)
+ user3 := th.CreateUser(Client)
+ LinkUserToTeam(user3, team2)
+
enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks
+ enableAdminOnlyHooks := utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations
defer func() {
- utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks
+ utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = enableAdminOnlyHooks
}()
- utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true
+ *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
+ hook := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}}
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
+ var rhook *model.OutgoingWebhook
+ if result, err := Client.CreateOutgoingWebhook(hook); err != nil {
+ t.Fatal(err)
+ } else {
+ rhook = result.Data.(*model.OutgoingWebhook)
+ }
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ if hook.ChannelId != rhook.ChannelId {
+ t.Fatal("channel ids didn't match")
+ }
- channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ if rhook.CreatorId != user.Id {
+ t.Fatal("user ids didn't match")
+ }
- channel2 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
+ if rhook.TeamId != team.Id {
+ t.Fatal("team ids didn't match")
+ }
- hook := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}}
+ hook = &model.OutgoingWebhook{ChannelId: "junk", CallbackURLs: []string{"http://nowhere.com"}}
+ if _, err := Client.CreateOutgoingWebhook(hook); err == nil {
+ t.Fatal("should have failed - bad channel id")
+ }
- if utils.Cfg.ServiceSettings.EnableOutgoingWebhooks {
- var rhook *model.OutgoingWebhook
- if result, err := Client.CreateOutgoingWebhook(hook); err != nil {
- t.Fatal(err)
- } else {
- rhook = result.Data.(*model.OutgoingWebhook)
+ hook = &model.OutgoingWebhook{ChannelId: channel1.Id, CreatorId: "123", TeamId: "456", CallbackURLs: []string{"http://nowhere.com"}}
+ if result, err := Client.CreateOutgoingWebhook(hook); err != nil {
+ t.Fatal(err)
+ } else {
+ if result.Data.(*model.OutgoingWebhook).CreatorId != user.Id {
+ t.Fatal("bad user id wasn't overwritten")
}
-
- if hook.ChannelId != rhook.ChannelId {
- t.Fatal("channel ids didn't match")
+ if result.Data.(*model.OutgoingWebhook).TeamId != team.Id {
+ t.Fatal("bad team id wasn't overwritten")
}
+ }
- if rhook.CreatorId != user.Id {
- t.Fatal("user ids didn't match")
- }
+ hook = &model.OutgoingWebhook{ChannelId: channel2.Id, CallbackURLs: []string{"http://nowhere.com"}}
+ if _, err := Client.CreateOutgoingWebhook(hook); err == nil {
+ t.Fatal("should have failed - private channel")
+ }
- if rhook.TeamId != team.Id {
- t.Fatal("team ids didn't match")
- }
+ hook = &model.OutgoingWebhook{CallbackURLs: []string{"http://nowhere.com"}}
+ if _, err := Client.CreateOutgoingWebhook(hook); err == nil {
+ t.Fatal("should have failed - blank channel and trigger words")
+ }
- hook = &model.OutgoingWebhook{ChannelId: "junk", CallbackURLs: []string{"http://nowhere.com"}}
- if _, err := Client.CreateOutgoingWebhook(hook); err == nil {
- t.Fatal("should have failed - bad channel id")
- }
+ Client.Logout()
+ Client.Must(Client.LoginById(user2.Id, user2.Password))
+ Client.SetTeamId(team.Id)
- hook = &model.OutgoingWebhook{ChannelId: channel2.Id, CreatorId: "123", TeamId: "456", CallbackURLs: []string{"http://nowhere.com"}}
- if result, err := Client.CreateOutgoingWebhook(hook); err != nil {
- t.Fatal(err)
- } else {
- if result.Data.(*model.OutgoingWebhook).CreatorId != user.Id {
- t.Fatal("bad user id wasn't overwritten")
- }
- if result.Data.(*model.OutgoingWebhook).TeamId != team.Id {
- t.Fatal("bad team id wasn't overwritten")
- }
- }
- } else {
- if _, err := Client.CreateOutgoingWebhook(hook); err == nil {
- t.Fatal("should have errored - webhooks turned off")
- }
+ hook = &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}}
+ if _, err := Client.CreateOutgoingWebhook(hook); err == nil {
+ t.Fatal("should have failed - not system/team admin")
+ }
+
+ *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false
+
+ if _, err := Client.CreateOutgoingWebhook(hook); err != nil {
+ t.Fatal(err)
+ }
+
+ Client.Logout()
+ Client.Must(Client.LoginById(user3.Id, user3.Password))
+ Client.SetTeamId(team2.Id)
+
+ if _, err := Client.CreateOutgoingWebhook(hook); err == nil {
+ t.Fatal("should have failed - wrong team")
+ }
+
+ utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = false
+
+ if _, err := Client.CreateOutgoingWebhook(hook); err == nil {
+ t.Fatal("should have errored - webhooks turned off")
}
}
func TestListOutgoingHooks(t *testing.T) {
- Setup()
- enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks
+ th := Setup().InitSystemAdmin()
+ Client := th.SystemAdminClient
+ team := th.SystemAdminTeam
+ channel1 := th.CreateChannel(Client, team)
+ user2 := th.CreateUser(Client)
+ LinkUserToTeam(user2, team)
+
enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks
+ enableAdminOnlyHooks := utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations
defer func() {
- utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks
+ utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = enableAdminOnlyHooks
}()
- utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true
+ *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
+ hook1 := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}}
+ hook1 = Client.Must(Client.CreateOutgoingWebhook(hook1)).Data.(*model.OutgoingWebhook)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
+ hook2 := &model.OutgoingWebhook{TriggerWords: []string{"trigger"}, CallbackURLs: []string{"http://nowhere.com"}}
+ hook2 = Client.Must(Client.CreateOutgoingWebhook(hook2)).Data.(*model.OutgoingWebhook)
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ if result, err := Client.ListOutgoingWebhooks(); err != nil {
+ t.Fatal(err)
+ } else {
+ hooks := result.Data.([]*model.OutgoingWebhook)
- channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ if len(hooks) != 2 {
+ t.Fatal("incorrect number of hooks")
+ }
+ }
- if utils.Cfg.ServiceSettings.EnableOutgoingWebhooks {
- hook1 := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}}
- hook1 = Client.Must(Client.CreateOutgoingWebhook(hook1)).Data.(*model.OutgoingWebhook)
+ Client.Logout()
+ Client.Must(Client.LoginById(user2.Id, user2.Password))
+ Client.SetTeamId(team.Id)
- hook2 := &model.OutgoingWebhook{TriggerWords: []string{"trigger"}, CallbackURLs: []string{"http://nowhere.com"}}
- hook2 = Client.Must(Client.CreateOutgoingWebhook(hook2)).Data.(*model.OutgoingWebhook)
+ if _, err := Client.ListOutgoingWebhooks(); err == nil {
+ t.Fatal("should have failed - not system/team admin")
+ }
- if result, err := Client.ListOutgoingWebhooks(); err != nil {
- t.Fatal(err)
- } else {
- hooks := result.Data.([]*model.OutgoingWebhook)
+ *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false
- if len(hooks) != 2 {
- t.Fatal("incorrect number of hooks")
- }
- }
- } else {
- if _, err := Client.ListOutgoingWebhooks(); err == nil {
- t.Fatal("should have errored - webhooks turned off")
- }
+ if _, err := Client.ListOutgoingWebhooks(); err != nil {
+ t.Fatal(err)
+ }
+
+ utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = false
+
+ if _, err := Client.ListOutgoingWebhooks(); err == nil {
+ t.Fatal("should have errored - webhooks turned off")
}
}
func TestDeleteOutgoingHook(t *testing.T) {
- Setup()
- enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks
+ th := Setup().InitSystemAdmin()
+ Client := th.SystemAdminClient
+ team := th.SystemAdminTeam
+ channel1 := th.CreateChannel(Client, team)
+ user2 := th.CreateUser(Client)
+ LinkUserToTeam(user2, team)
+
enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks
+ enableAdminOnlyHooks := utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations
defer func() {
- utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks
+ utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = enableAdminOnlyHooks
}()
- utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true
+ *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
+ hook := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}}
+ hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
+ if _, err := Client.DeleteOutgoingWebhook("junk"); err == nil {
+ t.Fatal("should have failed - bad hook id")
+ }
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ if _, err := Client.DeleteOutgoingWebhook(""); err == nil {
+ t.Fatal("should have failed - empty hook id")
+ }
- channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ if _, err := Client.DeleteOutgoingWebhook(hook.Id); err != nil {
+ t.Fatal(err)
+ }
- if utils.Cfg.ServiceSettings.EnableOutgoingWebhooks {
- hook := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}}
- hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook)
+ hooks := Client.Must(Client.ListOutgoingWebhooks()).Data.([]*model.OutgoingWebhook)
+ if len(hooks) != 0 {
+ t.Fatal("delete didn't work properly")
+ }
- data := make(map[string]string)
- data["id"] = hook.Id
+ hook = &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}}
+ hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook)
- if _, err := Client.DeleteOutgoingWebhook(data); err != nil {
- t.Fatal(err)
- }
+ Client.Logout()
+ Client.Must(Client.LoginById(user2.Id, user2.Password))
+ Client.SetTeamId(team.Id)
- hooks := Client.Must(Client.ListOutgoingWebhooks()).Data.([]*model.OutgoingWebhook)
- if len(hooks) != 0 {
- t.Fatal("delete didn't work properly")
- }
- } else {
- data := make(map[string]string)
- data["id"] = "123"
+ if _, err := Client.DeleteOutgoingWebhook(hook.Id); err == nil {
+ t.Fatal("should have failed - not system/team admin")
+ }
- if _, err := Client.DeleteOutgoingWebhook(data); err == nil {
- t.Fatal("should have errored - webhooks turned off")
- }
+ *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false
+
+ if _, err := Client.DeleteOutgoingWebhook(hook.Id); err == nil {
+ t.Fatal("should have failed - not creator or team admin")
+ }
+
+ hook = &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}}
+ hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook)
+
+ if _, err := Client.DeleteOutgoingWebhook(hook.Id); err != nil {
+ t.Fatal(err)
+ }
+
+ utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = false
+
+ if _, err := Client.DeleteOutgoingWebhook(hook.Id); err == nil {
+ t.Fatal("should have errored - webhooks turned off")
}
}
func TestRegenOutgoingHookToken(t *testing.T) {
- Setup()
- enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks
+ th := Setup().InitSystemAdmin()
+ Client := th.SystemAdminClient
+ team := th.SystemAdminTeam
+ team2 := th.CreateTeam(Client)
+ channel1 := th.CreateChannel(Client, team)
+ user2 := th.CreateUser(Client)
+ LinkUserToTeam(user2, team)
+ user3 := th.CreateUser(Client)
+ LinkUserToTeam(user3, team2)
+
enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks
+ enableAdminOnlyHooks := utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations
defer func() {
- utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks
+ utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = enableAdminOnlyHooks
}()
- utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true
+ *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = true
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
+ hook := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}}
+ hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
+ if _, err := Client.RegenOutgoingWebhookToken("junk"); err == nil {
+ t.Fatal("should have failed - bad id")
+ }
- c := &Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
- UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
- Client.LoginByEmail(team.Name, user.Email, "pwd")
+ if _, err := Client.RegenOutgoingWebhookToken(""); err == nil {
+ t.Fatal("should have failed - empty id")
+ }
- channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
- channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+ if result, err := Client.RegenOutgoingWebhookToken(hook.Id); err != nil {
+ t.Fatal(err)
+ } else {
+ if result.Data.(*model.OutgoingWebhook).Token == hook.Token {
+ t.Fatal("regen didn't work properly")
+ }
+ }
- if utils.Cfg.ServiceSettings.EnableOutgoingWebhooks {
- hook := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}}
- hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook)
+ Client.Logout()
+ Client.Must(Client.LoginById(user2.Id, user2.Password))
+ Client.SetTeamId(team.Id)
- data := make(map[string]string)
- data["id"] = hook.Id
+ if _, err := Client.RegenOutgoingWebhookToken(hook.Id); err == nil {
+ t.Fatal("should have failed - not system/team admin")
+ }
- if result, err := Client.RegenOutgoingWebhookToken(data); err != nil {
- t.Fatal(err)
- } else {
- if result.Data.(*model.OutgoingWebhook).Token == hook.Token {
- t.Fatal("regen didn't work properly")
- }
- }
+ *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false
- } else {
- data := make(map[string]string)
- data["id"] = "123"
+ hook = &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}}
+ hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook)
- if _, err := Client.RegenOutgoingWebhookToken(data); err == nil {
- t.Fatal("should have errored - webhooks turned off")
- }
+ if _, err := Client.RegenOutgoingWebhookToken(hook.Id); err != nil {
+ t.Fatal(err)
+ }
+
+ Client.Logout()
+ Client.Must(Client.LoginById(user3.Id, user3.Password))
+ Client.SetTeamId(team2.Id)
+
+ if _, err := Client.RegenOutgoingWebhookToken(hook.Id); err == nil {
+ t.Fatal("should have failed - wrong team")
+ }
+
+ utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = false
+
+ if _, err := Client.RegenOutgoingWebhookToken(hook.Id); err == nil {
+ t.Fatal("should have errored - webhooks turned off")
+ }
+}
+func TestIncomingWebhooks(t *testing.T) {
+ th := Setup().InitSystemAdmin()
+ Client := th.SystemAdminClient
+ team := th.SystemAdminTeam
+ channel1 := th.CreateChannel(Client, team)
+ user2 := th.CreateUser(Client)
+ LinkUserToTeam(user2, team)
+
+ enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks
+ defer func() {
+ utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks
+ }()
+ utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true
+
+ hook := &model.IncomingWebhook{ChannelId: channel1.Id}
+ hook = Client.Must(Client.CreateIncomingWebhook(hook)).Data.(*model.IncomingWebhook)
+
+ url := "/hooks/" + hook.Id
+
+ if _, err := Client.DoPost(url, "{\"text\":\"this is a test\"}", "application/json"); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := Client.DoPost(url, fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", channel1.Name), "application/json"); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := Client.DoPost(url, fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"#%s\"}", channel1.Name), "application/json"); err != nil {
+ t.Fatal(err)
+ }
+
+ Client.Must(Client.CreateDirectChannel(user2.Id))
+
+ if _, err := Client.DoPost(url, fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"@%s\"}", user2.Username), "application/json"); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := Client.DoPost(url, "payload={\"text\":\"this is a test\"}", "application/x-www-form-urlencoded"); err != nil {
+ t.Fatal(err)
+ }
+
+ attachmentPayload := `{
+ "text": "this is a test",
+ "attachments": [
+ {
+ "fallback": "Required plain-text summary of the attachment.",
+
+ "color": "#36a64f",
+
+ "pretext": "Optional text that appears above the attachment block",
+
+ "author_name": "Bobby Tables",
+ "author_link": "http://flickr.com/bobby/",
+ "author_icon": "http://flickr.com/icons/bobby.jpg",
+
+ "title": "Slack API Documentation",
+ "title_link": "https://api.slack.com/",
+
+ "text": "Optional text that appears within the attachment",
+
+ "fields": [
+ {
+ "title": "Priority",
+ "value": "High",
+ "short": false
+ }
+ ],
+
+ "image_url": "http://my-website.com/path/to/image.jpg",
+ "thumb_url": "http://example.com/path/to/thumb.png"
+ }
+ ]
+ }`
+
+ if _, err := Client.DoPost(url, attachmentPayload, "application/json"); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := Client.DoPost(url, "{\"text\":\"\"}", "application/json"); err == nil {
+ t.Fatal("should have failed - no text")
+ }
+
+ utils.Cfg.ServiceSettings.EnableIncomingWebhooks = false
+
+ if _, err := Client.DoPost(url, "{\"text\":\"this is a test\"}", "application/json"); err == nil {
+ t.Fatal("should have failed - webhooks turned off")
}
}
diff --git a/config/config.json b/config/config.json
index 7f3085950..3fe938fe2 100644
--- a/config/config.json
+++ b/config/config.json
@@ -30,9 +30,9 @@
"MaxUsersPerTeam": 50,
"EnableTeamCreation": true,
"EnableUserCreation": true,
+ "EnableOpenServer": false,
"RestrictCreationToDomains": "",
"RestrictTeamNames": true,
- "EnableTeamListing": false,
"EnableCustomBrand": false,
"CustomBrandText": ""
},
@@ -143,6 +143,7 @@
"LastNameAttribute": "",
"EmailAttribute": "",
"UsernameAttribute": "",
+ "NicknameAttribute": "",
"IdAttribute": "",
"SkipCertificateVerification": false,
"QueryTimeout": 60,
@@ -154,4 +155,4 @@
"Directory": "./data/",
"EnableDaily": false
}
-}
+} \ No newline at end of file
diff --git a/einterfaces/ldap.go b/einterfaces/ldap.go
index 3917b42f8..25d591ce2 100644
--- a/einterfaces/ldap.go
+++ b/einterfaces/ldap.go
@@ -8,10 +8,10 @@ import (
)
type LdapInterface interface {
- DoLogin(team *model.Team, id string, password string) (*model.User, *model.AppError)
+ DoLogin(id string, password string) (*model.User, *model.AppError)
GetUser(id string) (*model.User, *model.AppError)
CheckPassword(id string, password string) *model.AppError
- SwitchToEmail(userId, ldapId, ldapPassword string) *model.AppError
+ SwitchToLdap(userId, ldapId, ldapPassword string) *model.AppError
ValidateFilter(filter string) *model.AppError
}
diff --git a/einterfaces/mfa.go b/einterfaces/mfa.go
index 0703fb766..25f3ed913 100644
--- a/einterfaces/mfa.go
+++ b/einterfaces/mfa.go
@@ -8,7 +8,7 @@ import (
)
type MfaInterface interface {
- GenerateQrCode(team *model.Team, user *model.User) ([]byte, *model.AppError)
+ GenerateQrCode(user *model.User) ([]byte, *model.AppError)
Activate(user *model.User, token string) *model.AppError
Deactivate(userId string) *model.AppError
ValidateToken(secret, token string) (bool, *model.AppError)
diff --git a/i18n/en.json b/i18n/en.json
index a135e04be..990db5277 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -1313,11 +1313,11 @@
},
{
"id": "api.templates.singin_change_email.body.info",
- "translation": "You updated your sign-in method for {{.TeamDisplayName}} on {{ .TeamURL }} to {{.Method}}.<br>If this change wasn't initiated by you, please contact your system administrator."
+ "translation": "You updated your sign-in method for on {{ .SiteName }} to {{.Method}}.<br>If this change wasn't initiated by you, please contact your system administrator."
},
{
"id": "api.templates.singin_change_email.subject",
- "translation": "You updated your sign-in method for {{.TeamDisplayName}} on {{ .SiteName }}"
+ "translation": "You updated your sign-in method on {{ .SiteName }}"
},
{
"id": "api.templates.verify_body.button",
@@ -1425,11 +1425,11 @@
},
{
"id": "api.user.create_oauth_user.already_attached.app_error",
- "translation": "Team {{.DisplayName}} already has a user with the email address attached to your {{.Service}} account"
+ "translation": "An existing user is already attached to your {{.Service}} account"
},
{
"id": "api.user.create_oauth_user.already_used.app_error",
- "translation": "This {{.Service}} account has already been used to sign up for team {{.DisplayName}}"
+ "translation": "This {{.Service}} account has already been used to sign up"
},
{
"id": "api.user.create_oauth_user.create.app_error",
@@ -1452,6 +1452,10 @@
"translation": "Could not add user initial to default profile picture"
},
{
+ "id": "api.user.create_user.no_open_server",
+ "translation": "This server does not allow open signups. Please speak with your Administrator to receive an invitation."
+ },
+ {
"id": "api.user.create_user.accepted_domain.app_error",
"translation": "The email you provided does not belong to an accepted domain. Please contact your administrator or sign up with a different email."
},
@@ -1760,6 +1764,14 @@
"translation": "Incoming webhooks have been disabled by the system admin."
},
{
+ "id": "api.webhook.create_outgoing.not_open.app_error",
+ "translation": "Outgoing webhooks can only be created for public channels."
+ },
+ {
+ "id": "api.webhook.create_outgoing.permissions.app_error",
+ "translation": "Inappropriate permissions to create outcoming webhook."
+ },
+ {
"id": "api.webhook.create_outgoing.disabled.app_error",
"translation": "Outgoing webhooks have been disabled by the system admin."
},
@@ -2112,6 +2124,18 @@
"translation": "Invalid type"
},
{
+ "id": "model.team_member.is_valid.team_id.app_error",
+ "translation": "Invalid team id"
+ },
+ {
+ "id": "model.team_member.is_valid.user_id.app_error",
+ "translation": "Invalid user id"
+ },
+ {
+ "id": "model.team_member.is_valid.role.app_error",
+ "translation": "Invalid role"
+ },
+ {
"id": "model.channel.is_valid.update_at.app_error",
"translation": "Update at must be a valid time"
},
@@ -2944,6 +2968,22 @@
"translation": "We encountered an error updating the channel member"
},
{
+ "id": "store.sql_team.save_member.exists.app_error",
+ "translation": "A team member with that id already exists"
+ },
+ {
+ "id": "store.sql_team.save_member.save.app_error",
+ "translation": "We couldn't save the team member"
+ },
+ {
+ "id": "store.sql_team.get_members.app_error",
+ "translation": "We couldn't get the team members"
+ },
+ {
+ "id": "store.sql_team.remove_member.app_error",
+ "translation": "We couldn't remove the team member"
+ },
+ {
"id": "store.sql_command.analytics_command_count.app_error",
"translation": "We couldn't count the commands"
},
@@ -3237,7 +3277,7 @@
},
{
"id": "store.sql_session.remove_all_sessions_for_team.app_error",
- "translation": "We couldn't remove all the sessions for the team"
+ "translation": "We couldn't remove all the sessions"
},
{
"id": "store.sql_session.save.app_error",
diff --git a/i18n/es.json b/i18n/es.json
index 7686e2b88..26378b148 100644
--- a/i18n/es.json
+++ b/i18n/es.json
@@ -1312,14 +1312,6 @@
"translation": "Configuración del equipo en {{ .SiteName }}"
},
{
- "id": "api.templates.singin_change_email.body.info",
- "translation": "Haz actualizado el método con el que inicias sesión en {{.TeamURL}} para el equipo {{.TeamDisplayName}} por {{.Method}}.<br>Si este cambio no fue realizado por ti, por favor contacta a un administrador del sistema."
- },
- {
- "id": "api.templates.singin_change_email.subject",
- "translation": "Cambio del método de inicio de sesión para {{.TeamDisplayName}} en {{ .SiteName }}"
- },
- {
"id": "api.templates.verify_body.button",
"translation": "Confirmar Correo"
},
@@ -1424,14 +1416,6 @@
"translation": "OAuth para {{.Service}} no está disponible en este servidor"
},
{
- "id": "api.user.create_oauth_user.already_attached.app_error",
- "translation": "El Equipo {{.DisplayName}} ya tiene un usuario con esta dirección de correo asociada a tu cuenta de {{.Service}}"
- },
- {
- "id": "api.user.create_oauth_user.already_used.app_error",
- "translation": "Esta cuenta de {{.Service}} ya fue utilizada para registrarse en el equipo {{.DisplayName}}"
- },
- {
"id": "api.user.create_oauth_user.create.app_error",
"translation": "No se pudo crear el usuario basandose en el objeto de {{.Service}}"
},
@@ -3236,10 +3220,6 @@
"translation": "No pudimos remover la sesión"
},
{
- "id": "store.sql_session.remove_all_sessions_for_team.app_error",
- "translation": "No pudimos remover todas las sesiones para el equipo"
- },
- {
"id": "store.sql_session.save.app_error",
"translation": "No pudimos guardar la sesión"
},
diff --git a/i18n/fr.json b/i18n/fr.json
index eaf16fbdd..488b49b69 100644
--- a/i18n/fr.json
+++ b/i18n/fr.json
@@ -1236,14 +1236,6 @@
"translation": "Paramétrage Équipe {{ .SiteName }}"
},
{
- "id": "api.templates.singin_change_email.body.info",
- "translation": "Vous avez mis à jour la méthode de connexion de {{.TeamDisplayName}} sur {{.TeamURL}} pour {{.Method}}.<br>Si cette modification n'a pas été effectuée par vous, veuillez prendre contact avec votre administrateur système."
- },
- {
- "id": "api.templates.singin_change_email.subject",
- "translation": "Vous avez mis à jour la méthode de connexion de {{.TeamDisplayName}} sur {{ .SiteName }}"
- },
- {
"id": "api.templates.verify_body.button",
"translation": "Vérifier l'adresse électronique"
},
@@ -1340,14 +1332,6 @@
"translation": "{{.Service}} oauth non disponible sur ce serveur"
},
{
- "id": "api.user.create_oauth_user.already_attached.app_error",
- "translation": "L'équipe {{.DisplayName}} dispose déjà d'un utilisateur ayant la même adresse électronique que celle attachée à votre compte {{.Service}}"
- },
- {
- "id": "api.user.create_oauth_user.already_used.app_error",
- "translation": "Ce compte {{.Service}} a déjà été utilisé pour pour se connecter à l'équipe {{.DisplayName}}"
- },
- {
"id": "api.user.create_oauth_user.create.app_error",
"translation": "Impossible de créer un utilisateur à partir du user object {{.Service}}"
},
diff --git a/i18n/pt.json b/i18n/pt.json
index b9e0c49c9..635e254d1 100644
--- a/i18n/pt.json
+++ b/i18n/pt.json
@@ -1312,14 +1312,6 @@
"translation": "{{ .SiteName }} Configuração da Equipe"
},
{
- "id": "api.templates.singin_change_email.body.info",
- "translation": "Você atualizou seu método de login para {{.TeamDisplayName}} no {{ .TeamURL }} para {{.Method}}.<br>Se esta mudança não foi iniciada por você, por favor entre em contato com o administrador do sistema."
- },
- {
- "id": "api.templates.singin_change_email.subject",
- "translation": "Você atualizou seu método de login para {{.TeamDisplayName}} em {{ .SiteName }}"
- },
- {
"id": "api.templates.verify_body.button",
"translation": "Verificar Email"
},
@@ -1424,14 +1416,6 @@
"translation": "{{.Service}} oauth não disponível neste servidor"
},
{
- "id": "api.user.create_oauth_user.already_attached.app_error",
- "translation": "Equipe {{.DisplayName}} já tem um usuário com o endereço de email anexado a sua conta {{.Service}}"
- },
- {
- "id": "api.user.create_oauth_user.already_used.app_error",
- "translation": "Está conta {{.Service}} já foi utilizada para se inscrever na equipe {{.DisplayName}}"
- },
- {
"id": "api.user.create_oauth_user.create.app_error",
"translation": "Não foi possível criar o usuário fora do {{.Service}} do objeto de usuário"
},
@@ -3236,10 +3220,6 @@
"translation": "Não foi possível remover a sessão"
},
{
- "id": "store.sql_session.remove_all_sessions_for_team.app_error",
- "translation": "Não foi possível remover todas as sessões para a equipe"
- },
- {
"id": "store.sql_session.save.app_error",
"translation": "Não foi possível salvar a sessão"
},
diff --git a/manualtesting/manual_testing.go b/manualtesting/manual_testing.go
index 2f1096fd5..77a5b89c8 100644
--- a/manualtesting/manual_testing.go
+++ b/manualtesting/manual_testing.go
@@ -66,7 +66,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) {
team := &model.Team{
DisplayName: teamDisplayName[0],
Name: utils.RandomName(utils.Range{20, 20}, utils.LOWERCASE),
- Email: utils.RandomEmail(utils.Range{20, 20}, utils.LOWERCASE),
+ Email: "success+" + model.NewId() + "simulator.amazonses.com",
Type: model.TEAM_OPEN,
}
@@ -88,8 +88,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) {
// Create user for testing
user := &model.User{
- TeamId: teamID,
- Email: utils.RandomEmail(utils.Range{20, 20}, utils.LOWERCASE),
+ Email: "success+" + model.NewId() + "simulator.amazonses.com",
Nickname: username[0],
Password: api.USER_PASSWORD}
@@ -98,7 +97,10 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) {
c.Err = err
return
}
- api.Srv.Store.User().VerifyEmail(result.Data.(*model.User).Id)
+
+ <-api.Srv.Store.User().VerifyEmail(result.Data.(*model.User).Id)
+ <-api.Srv.Store.Team().SaveMember(&model.TeamMember{TeamId: teamID, UserId: result.Data.(*model.User).Id})
+
newuser := result.Data.(*model.User)
userID = newuser.Id
diff --git a/mattermost.go b/mattermost.go
index a417fb3ec..a16bf53a7 100644
--- a/mattermost.go
+++ b/mattermost.go
@@ -22,6 +22,7 @@ import (
"github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/manualtesting"
"github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
"github.com/mattermost/platform/web"
@@ -36,13 +37,18 @@ import (
//ENTERPRISE_IMPORTS
+var flagCmdUpdateDb30 bool
var flagCmdCreateTeam bool
var flagCmdCreateUser bool
var flagCmdAssignRole bool
var flagCmdVersion bool
var flagCmdResetPassword bool
+var flagCmdResetMfa bool
var flagCmdPermanentDeleteUser bool
var flagCmdPermanentDeleteTeam bool
+var flagCmdPermanentDeleteAllUsers bool
+var flagCmdResetDatabase bool
+var flagUsername string
var flagCmdUploadLicense bool
var flagConfigFile string
var flagLicenseFile string
@@ -69,6 +75,10 @@ func main() {
l4g.Info(utils.T("mattermost.working_dir"), pwd)
l4g.Info(utils.T("mattermost.config_file"), utils.FindConfigFile(flagConfigFile))
+ // Speical case for upgrading the db to 3.0
+ // ADDED for 3.0 REMOVE for 3.4
+ cmdUpdateDb30()
+
api.NewServer()
api.InitApi()
web.InitWeb()
@@ -223,19 +233,24 @@ func parseCmds() {
}
flag.StringVar(&flagConfigFile, "config", "config.json", "")
+ flag.StringVar(&flagUsername, "username", "", "")
flag.StringVar(&flagLicenseFile, "license", "", "")
flag.StringVar(&flagEmail, "email", "", "")
flag.StringVar(&flagPassword, "password", "", "")
flag.StringVar(&flagTeamName, "team_name", "", "")
flag.StringVar(&flagRole, "role", "", "")
+ flag.BoolVar(&flagCmdUpdateDb30, "upgrade_db_30", false, "")
flag.BoolVar(&flagCmdCreateTeam, "create_team", false, "")
flag.BoolVar(&flagCmdCreateUser, "create_user", false, "")
flag.BoolVar(&flagCmdAssignRole, "assign_role", false, "")
flag.BoolVar(&flagCmdVersion, "version", false, "")
flag.BoolVar(&flagCmdResetPassword, "reset_password", false, "")
+ flag.BoolVar(&flagCmdResetMfa, "reset_mfa", false, "")
flag.BoolVar(&flagCmdPermanentDeleteUser, "permanent_delete_user", false, "")
flag.BoolVar(&flagCmdPermanentDeleteTeam, "permanent_delete_team", false, "")
+ flag.BoolVar(&flagCmdPermanentDeleteAllUsers, "permanent_delete_all_users", false, "")
+ flag.BoolVar(&flagCmdResetDatabase, "reset_database", false, "")
flag.BoolVar(&flagCmdUploadLicense, "upload_license", false, "")
flag.Parse()
@@ -244,9 +259,12 @@ func parseCmds() {
flagCmdCreateUser ||
flagCmdAssignRole ||
flagCmdResetPassword ||
+ flagCmdResetMfa ||
flagCmdVersion ||
flagCmdPermanentDeleteUser ||
flagCmdPermanentDeleteTeam ||
+ flagCmdPermanentDeleteAllUsers ||
+ flagCmdResetDatabase ||
flagCmdUploadLicense)
}
@@ -256,11 +274,258 @@ func runCmds() {
cmdCreateUser()
cmdAssignRole()
cmdResetPassword()
+ cmdResetMfa()
cmdPermDeleteUser()
cmdPermDeleteTeam()
+ cmdPermDeleteAllUsers()
+ cmdResetDatabase()
cmdUploadLicense()
}
+type TeamForUpgrade struct {
+ Id string
+ Name string
+}
+
+// ADDED for 3.0 REMOVE for 3.4
+func cmdUpdateDb30() {
+ if flagCmdUpdateDb30 {
+ api.Srv = &api.Server{}
+ api.Srv.Store = store.NewSqlStoreForUpgrade30()
+ store := api.Srv.Store.(*store.SqlStore)
+
+ l4g.Info("Attempting to run speical upgrade of the database schema to version 3.0 for user model changes")
+ time.Sleep(time.Second)
+
+ if !store.DoesColumnExist("Users", "TeamId") {
+ fmt.Println("**WARNING** the database schema appears to be upgraded to 3.0")
+ flushLogAndExit(1)
+ }
+
+ if !(store.SchemaVersion == "2.2.0" ||
+ store.SchemaVersion == "2.1.0" ||
+ store.SchemaVersion == "2.0.0") {
+ fmt.Println("**WARNING** the database schema needs to be version 2.2.0, 2.1.0 or 2.0.0 to upgrade")
+ flushLogAndExit(1)
+ }
+
+ var confirmBackup string
+ fmt.Println("\nPlease see http://www.mattermost.org/upgrade-to-3-0/")
+ fmt.Println("**WARNING** This upgrade process will be irreversible.")
+ fmt.Print("Have you performed a database backup? (YES/NO): ")
+ fmt.Scanln(&confirmBackup)
+ if confirmBackup != "YES" {
+ fmt.Fprintln(os.Stderr, "ABORTED: You did not answer YES exactly, in all capitals.")
+ flushLogAndExit(1)
+ }
+
+ var flagTeamName string
+ var teams []*TeamForUpgrade
+
+ if _, err := store.GetMaster().Select(&teams, "SELECT Id, Name FROM Teams"); err != nil {
+ l4g.Error("Failed to load all teams details=%v", err)
+ flushLogAndExit(1)
+ }
+
+ fmt.Println(fmt.Sprintf("We found %v teams.", len(teams)))
+
+ for _, team := range teams {
+ fmt.Println(team.Name)
+ }
+
+ fmt.Print("Please pick a primary team from the list above: ")
+ fmt.Scanln(&flagTeamName)
+
+ var team *TeamForUpgrade
+ for _, t := range teams {
+ if t.Name == flagTeamName {
+ team = t
+ break
+ }
+ }
+
+ if team == nil {
+ l4g.Error("Failed to find primary team details")
+ flushLogAndExit(1)
+ }
+
+ l4g.Info("Starting speical 3.0 database upgrade with performed_backup=YES team_name=%v", team.Name)
+ l4g.Info("Primary team %v will be left unchanged", team.Name)
+ l4g.Info("Upgrading primary team %v", team.Name)
+
+ uniqueEmails := make(map[string]bool)
+ uniqueUsernames := make(map[string]bool)
+ primaryUsers := convertTeamTo30(team, uniqueEmails, uniqueUsernames)
+
+ l4g.Info("Upgraded %v users", len(primaryUsers))
+
+ for _, otherTeam := range teams {
+ if otherTeam.Id != team.Id {
+ l4g.Info("Upgrading team %v", otherTeam.Name)
+ users := convertTeamTo30(otherTeam, uniqueEmails, uniqueUsernames)
+ l4g.Info("Upgraded %v users", len(users))
+
+ }
+ }
+
+ l4g.Info("Altering other scheme changes needed 3.0 for user model changes")
+
+ if _, err := store.GetMaster().Exec(`
+ UPDATE Channels
+ SET
+ TeamId = ''
+ WHERE
+ Type = 'D'
+ `,
+ ); err != nil {
+ l4g.Error("Failed to update direct channel types details=%v", err)
+ flushLogAndExit(1)
+ }
+
+ extraLength := store.GetMaxLengthOfColumnIfExists("Audits", "ExtraInfo")
+ if len(extraLength) > 0 && extraLength != "1024" {
+ store.AlterColumnTypeIfExists("Audits", "ExtraInfo", "VARCHAR(1024)", "VARCHAR(1024)")
+ }
+
+ actionLength := store.GetMaxLengthOfColumnIfExists("Audits", "Action")
+ if len(actionLength) > 0 && actionLength != "512" {
+ store.AlterColumnTypeIfExists("Audits", "Action", "VARCHAR(512)", "VARCHAR(512)")
+ }
+
+ if store.DoesColumnExist("Sessions", "TeamId") {
+ store.RemoveColumnIfExists("Sessions", "TeamId")
+ store.GetMaster().Exec(`TRUNCATE Sessions`)
+ }
+
+ // ADDED for 2.2 REMOVE for 2.6
+ store.CreateColumnIfNotExists("Users", "MfaActive", "tinyint(1)", "boolean", "0")
+ store.CreateColumnIfNotExists("Users", "MfaSecret", "varchar(128)", "character varying(128)", "")
+
+ // ADDED for 2.2 REMOVE for 2.6
+ if store.DoesColumnExist("Users", "TeamId") {
+ store.RemoveIndexIfExists("idx_users_team_id", "Users")
+ store.CreateUniqueIndexIfNotExists("idx_users_email_unique", "Users", "Email")
+ store.CreateUniqueIndexIfNotExists("idx_users_username_unique", "Users", "Username")
+ store.RemoveColumnIfExists("Teams", "AllowTeamListing")
+ store.RemoveColumnIfExists("Users", "TeamId")
+ }
+
+ l4g.Info("Finished running speical upgrade of the database schema to version 3.0 for user model changes")
+
+ if result := <-store.System().Update(&model.System{Name: "Version", Value: model.CurrentVersion}); result.Err != nil {
+ l4g.Error("Failed to update system schema version details=%v", result.Err)
+ flushLogAndExit(1)
+ }
+
+ l4g.Info(utils.T("store.sql.upgraded.warn"), model.CurrentVersion)
+ fmt.Println("**SUCCESS** with upgrade")
+
+ flushLogAndExit(0)
+ }
+}
+
+type UserForUpgrade struct {
+ Id string
+ Username string
+ Email string
+ Roles string
+ TeamId string
+}
+
+func convertTeamTo30(team *TeamForUpgrade, uniqueEmails map[string]bool, uniqueUsernames map[string]bool) []*UserForUpgrade {
+ store := api.Srv.Store.(*store.SqlStore)
+ var users []*UserForUpgrade
+ if _, err := store.GetMaster().Select(&users, "SELECT Users.Id, Users.Username, Users.Email, Users.Roles, Users.TeamId FROM Users WHERE Users.TeamId = :TeamId", map[string]interface{}{"TeamId": team.Id}); err != nil {
+ l4g.Error("Failed to load profiles for team details=%v", err)
+ flushLogAndExit(1)
+ }
+
+ var members []*model.TeamMember
+ if result := <-api.Srv.Store.Team().GetMembers(team.Id); result.Err != nil {
+ l4g.Error("Failed to load team membership details=%v", result.Err)
+ flushLogAndExit(1)
+ } else {
+ members = result.Data.([]*model.TeamMember)
+ }
+
+ for _, user := range users {
+ shouldUpdateUser := false
+ previousRole := user.Roles
+ previousEmail := user.Email
+ previousUsername := user.Username
+
+ member := &model.TeamMember{
+ TeamId: team.Id,
+ UserId: user.Id,
+ }
+
+ if model.IsInRole(user.Roles, model.ROLE_TEAM_ADMIN) {
+ member.Roles = model.ROLE_TEAM_ADMIN
+ user.Roles = ""
+ shouldUpdateUser = true
+ }
+
+ exists := false
+ for _, member := range members {
+ if member.UserId == user.Id {
+ exists = true
+ break
+ }
+ }
+
+ if !exists {
+ if result := <-api.Srv.Store.Team().SaveMember(member); result.Err != nil {
+ l4g.Error("Failed to save membership for %v details=%v", user.Email, result.Err)
+ flushLogAndExit(1)
+ }
+ }
+
+ if uniqueEmails[user.Email] {
+ shouldUpdateUser = true
+ emailParts := strings.Split(user.Email, "@")
+ if len(emailParts) == 2 {
+ user.Email = emailParts[0] + "+" + team.Name + "@" + emailParts[1]
+ } else {
+ user.Email = user.Email + "." + team.Name
+ }
+ }
+
+ if uniqueUsernames[user.Username] {
+ shouldUpdateUser = true
+ user.Username = user.Username + "." + team.Name
+ }
+
+ if shouldUpdateUser {
+ if _, err := store.GetMaster().Exec(`
+ UPDATE Users
+ SET
+ Email = :Email,
+ Username = :Username,
+ Roles = :Roles
+ WHERE
+ Id = :Id
+ `,
+ map[string]interface{}{
+ "Email": user.Email,
+ "Username": user.Username,
+ "Roles": user.Roles,
+ "Id": user.Id,
+ },
+ ); err != nil {
+ l4g.Error("Failed to update user %v details=%v", user.Email, err)
+ flushLogAndExit(1)
+ }
+
+ l4g.Info("modified user_id=%v, changed email from=%v to=%v, changed username from=%v to %v changed roles from=%v to=%v", user.Id, previousEmail, user.Email, previousUsername, user.Username, previousRole, user.Roles)
+ }
+
+ uniqueEmails[user.Email] = true
+ uniqueUsernames[user.Username] = true
+ }
+
+ return users
+}
+
func cmdCreateTeam() {
if flagCmdCreateTeam {
if len(flagTeamName) == 0 {
@@ -327,10 +592,9 @@ func cmdCreateUser() {
flushLogAndExit(1)
} else {
team = result.Data.(*model.Team)
- user.TeamId = team.Id
}
- _, err := api.CreateUser(team, user)
+ ruser, err := api.CreateUser(user)
if err != nil {
if err.Id != "store.sql_user.save.email_exists.app_error" {
l4g.Error("%v", err)
@@ -338,6 +602,12 @@ func cmdCreateUser() {
}
}
+ err = api.JoinUserToTeam(team, ruser)
+ if err != nil {
+ l4g.Error("%v", err)
+ flushLogAndExit(1)
+ }
+
os.Exit(0)
}
}
@@ -368,7 +638,7 @@ func cmdAssignRole() {
os.Exit(1)
}
- if !model.IsValidRoles(flagRole) {
+ if !model.IsValidUserRoles(flagRole) {
fmt.Fprintln(os.Stderr, "flag invalid argument: -role")
flag.Usage()
os.Exit(1)
@@ -376,16 +646,8 @@ func cmdAssignRole() {
c := getMockContext()
- var team *model.Team
- if result := <-api.Srv.Store.Team().GetByName(flagTeamName); result.Err != nil {
- l4g.Error("%v", result.Err)
- flushLogAndExit(1)
- } else {
- team = result.Data.(*model.Team)
- }
-
var user *model.User
- if result := <-api.Srv.Store.User().GetByEmail(team.Id, flagEmail); result.Err != nil {
+ if result := <-api.Srv.Store.User().GetByEmail(flagEmail); result.Err != nil {
l4g.Error("%v", result.Err)
flushLogAndExit(1)
} else {
@@ -426,16 +688,8 @@ func cmdResetPassword() {
os.Exit(1)
}
- var team *model.Team
- if result := <-api.Srv.Store.Team().GetByName(flagTeamName); result.Err != nil {
- l4g.Error("%v", result.Err)
- flushLogAndExit(1)
- } else {
- team = result.Data.(*model.Team)
- }
-
var user *model.User
- if result := <-api.Srv.Store.User().GetByEmail(team.Id, flagEmail); result.Err != nil {
+ if result := <-api.Srv.Store.User().GetByEmail(flagEmail); result.Err != nil {
l4g.Error("%v", result.Err)
flushLogAndExit(1)
} else {
@@ -451,14 +705,42 @@ func cmdResetPassword() {
}
}
-func cmdPermDeleteUser() {
- if flagCmdPermanentDeleteUser {
- if len(flagTeamName) == 0 {
- fmt.Fprintln(os.Stderr, "flag needs an argument: -team_name")
+func cmdResetMfa() {
+ if flagCmdResetMfa {
+ if len(flagEmail) == 0 && len(flagUsername) == 0 {
+ fmt.Fprintln(os.Stderr, "flag needs an argument: -email OR -username")
flag.Usage()
os.Exit(1)
}
+ var user *model.User
+ if len(flagEmail) > 0 {
+ if result := <-api.Srv.Store.User().GetByEmail(flagEmail); result.Err != nil {
+ l4g.Error("%v", result.Err)
+ flushLogAndExit(1)
+ } else {
+ user = result.Data.(*model.User)
+ }
+ } else {
+ if result := <-api.Srv.Store.User().GetByUsername(flagUsername); result.Err != nil {
+ l4g.Error("%v", result.Err)
+ flushLogAndExit(1)
+ } else {
+ user = result.Data.(*model.User)
+ }
+ }
+
+ if err := api.DeactivateMfa(user.Id); err != nil {
+ l4g.Error("%v", err)
+ flushLogAndExit(1)
+ }
+
+ os.Exit(0)
+ }
+}
+
+func cmdPermDeleteUser() {
+ if flagCmdPermanentDeleteUser {
if len(flagEmail) == 0 {
fmt.Fprintln(os.Stderr, "flag needs an argument: -email")
flag.Usage()
@@ -467,16 +749,8 @@ func cmdPermDeleteUser() {
c := getMockContext()
- var team *model.Team
- if result := <-api.Srv.Store.Team().GetByName(flagTeamName); result.Err != nil {
- l4g.Error("%v", result.Err)
- flushLogAndExit(1)
- } else {
- team = result.Data.(*model.Team)
- }
-
var user *model.User
- if result := <-api.Srv.Store.User().GetByEmail(team.Id, flagEmail); result.Err != nil {
+ if result := <-api.Srv.Store.User().GetByEmail(flagEmail); result.Err != nil {
l4g.Error("%v", result.Err)
flushLogAndExit(1)
} else {
@@ -487,6 +761,7 @@ func cmdPermDeleteUser() {
fmt.Print("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirmBackup)
if confirmBackup != "YES" {
+ fmt.Print("ABORTED: You did not answer YES exactly, in all capitals.")
flushLogAndExit(1)
}
@@ -494,6 +769,7 @@ func cmdPermDeleteUser() {
fmt.Printf("Are you sure you want to delete the user %v? All data will be permanently deleted? (YES/NO): ", user.Email)
fmt.Scanln(&confirm)
if confirm != "YES" {
+ fmt.Print("ABORTED: You did not answer YES exactly, in all capitals.")
flushLogAndExit(1)
}
@@ -501,6 +777,7 @@ func cmdPermDeleteUser() {
l4g.Error("%v", err)
flushLogAndExit(1)
} else {
+ fmt.Print("SUCCESS: User deleted.")
flushLogAndExit(0)
}
}
@@ -528,6 +805,7 @@ func cmdPermDeleteTeam() {
fmt.Print("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirmBackup)
if confirmBackup != "YES" {
+ fmt.Print("ABORTED: You did not answer YES exactly, in all capitals.")
flushLogAndExit(1)
}
@@ -535,6 +813,7 @@ func cmdPermDeleteTeam() {
fmt.Printf("Are you sure you want to delete the team %v? All data will be permanently deleted? (YES/NO): ", team.Name)
fmt.Scanln(&confirm)
if confirm != "YES" {
+ fmt.Print("ABORTED: You did not answer YES exactly, in all capitals.")
flushLogAndExit(1)
}
@@ -542,11 +821,67 @@ func cmdPermDeleteTeam() {
l4g.Error("%v", err)
flushLogAndExit(1)
} else {
+ fmt.Print("SUCCESS: Team deleted.")
flushLogAndExit(0)
}
}
}
+func cmdPermDeleteAllUsers() {
+ if flagCmdPermanentDeleteAllUsers {
+ c := getMockContext()
+
+ var confirmBackup string
+ fmt.Print("Have you performed a database backup? (YES/NO): ")
+ fmt.Scanln(&confirmBackup)
+ if confirmBackup != "YES" {
+ fmt.Print("ABORTED: You did not answer YES exactly, in all capitals.")
+ flushLogAndExit(1)
+ }
+
+ var confirm string
+ fmt.Printf("Are you sure you want to delete all the users? All data will be permanently deleted? (YES/NO): ")
+ fmt.Scanln(&confirm)
+ if confirm != "YES" {
+ fmt.Print("ABORTED: You did not answer YES exactly, in all capitals.")
+ flushLogAndExit(1)
+ }
+
+ if err := api.PermanentDeleteAllUsers(c); err != nil {
+ l4g.Error("%v", err)
+ flushLogAndExit(1)
+ } else {
+ fmt.Print("SUCCESS: All users deleted.")
+ flushLogAndExit(0)
+ }
+ }
+}
+
+func cmdResetDatabase() {
+ if flagCmdResetDatabase {
+ var confirmBackup string
+ fmt.Print("Have you performed a database backup? (YES/NO): ")
+ fmt.Scanln(&confirmBackup)
+ if confirmBackup != "YES" {
+ fmt.Print("ABORTED: You did not answer YES exactly, in all capitals.")
+ flushLogAndExit(1)
+ }
+
+ var confirm string
+ fmt.Printf("Are you sure you want to delete everything? ALL data will be permanently deleted? (YES/NO): ")
+ fmt.Scanln(&confirm)
+ if confirm != "YES" {
+ fmt.Print("ABORTED: You did not answer YES exactly, in all capitals.")
+ flushLogAndExit(1)
+ }
+
+ api.Srv.Store.DropAllTables()
+ fmt.Print("SUCCESS: Database reset.")
+ flushLogAndExit(0)
+ }
+
+}
+
func cmdUploadLicense() {
if flagCmdUploadLicense {
if model.BuildEnterpriseReady != "true" {
@@ -574,7 +909,7 @@ func cmdUploadLicense() {
flushLogAndExit(0)
}
- os.Exit(0)
+ flushLogAndExit(0)
}
}
@@ -604,6 +939,8 @@ USAGE:
FLAGS:
-config="config.json" Path to the config file
+ -username="someuser" Username used in other commands
+
-license="ex.mattermost-license" Path to your license file
-email="user@example.com" Email address used in other commands
@@ -644,14 +981,33 @@ COMMANDS:
Example:
platform -reset_password -team_name="name" -email="user@example.com" -password="newpassword"
+ -reset_mfa Turns off multi-factor authentication for a user. It requires the
+ -email or -username flag.
+ Example:
+ platform -reset_mfa -username="someuser"
+
+ -reset_database Completely erases the database causing the loss of all data. This
+ will reset Mattermost to it's initial state. (note this will not
+ erase your configuration.)
+
+ Example:
+ platform -reset_mfa -username="someuser"
+
-permanent_delete_user Permanently deletes a user and all related information
including posts from the database. It requires the
+ -email flag. You may need to restart the
+ server to invalidate the cache
+ Example:
+ platform -permanent_delete_user -email="user@example.com"
+
+ -permanent_delete_all_users Permanently deletes all users and all related information
+ including posts from the database. It requires the
-team_name, and -email flag. You may need to restart the
server to invalidate the cache
Example:
- platform -permanent_delete_user -team_name="name" -email="user@example.com"
+ platform -permanent_delete_all_users -team_name="name" -email="user@example.com"
- -permanent_delete_team Permanently deletes a team and all users along with
+ -permanent_delete_team Permanently deletes a team allong with
all related information including posts from the database.
It requires the -team_name flag. You may need to restart
the server to invalidate the cache.
diff --git a/model/client.go b/model/client.go
index 89b4d134f..4edb859e2 100644
--- a/model/client.go
+++ b/model/client.go
@@ -28,7 +28,10 @@ const (
HEADER_AUTH = "Authorization"
HEADER_REQUESTED_WITH = "X-Requested-With"
HEADER_REQUESTED_WITH_XML = "XMLHttpRequest"
- API_URL_SUFFIX = "/api/v1"
+
+ API_URL_SUFFIX_V1 = "/api/v1"
+ API_URL_SUFFIX_V3 = "/api/v3"
+ API_URL_SUFFIX = API_URL_SUFFIX_V3
)
type Result struct {
@@ -39,16 +42,52 @@ type Result struct {
type Client struct {
Url string // The location of the server like "http://localhost:8065"
- ApiUrl string // The api location of the server like "http://localhost:8065/api/v1"
+ ApiUrl string // The api location of the server like "http://localhost:8065/api/v3"
HttpClient *http.Client // The http client
AuthToken string
AuthType string
+ TeamId string
}
// NewClient constructs a new client with convienence methods for talking to
// the server.
func NewClient(url string) *Client {
- return &Client{url, url + API_URL_SUFFIX, &http.Client{}, "", ""}
+ return &Client{url, url + API_URL_SUFFIX, &http.Client{}, "", "", ""}
+}
+
+func (c *Client) SetOAuthToken(token string) {
+ c.AuthToken = token
+ c.AuthType = HEADER_TOKEN
+}
+
+func (c *Client) ClearOAuthToken() {
+ c.AuthToken = ""
+ c.AuthType = HEADER_BEARER
+}
+
+func (c *Client) SetTeamId(teamId string) {
+ c.TeamId = teamId
+}
+
+func (c *Client) GetTeamId() string {
+ if len(c.TeamId) == 0 {
+ println(`You are trying to use a route that requires a team_id,
+ but you have not called SetTeamId() in client.go`)
+ }
+
+ return c.TeamId
+}
+
+func (c *Client) ClearTeamId() {
+ c.TeamId = ""
+}
+
+func (c *Client) GetTeamRoute() string {
+ return fmt.Sprintf("/teams/%v", c.GetTeamId())
+}
+
+func (c *Client) GetChannelRoute(channelId string) string {
+ return fmt.Sprintf("/teams/%v/channels/%v", c.GetTeamId(), channelId)
}
func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppError) {
@@ -162,10 +201,19 @@ func (c *Client) GetAllTeams() (*Result, *AppError) {
}
}
-func (c *Client) FindTeamByName(name string, allServers bool) (*Result, *AppError) {
+func (c *Client) GetAllTeamListings() (*Result, *AppError) {
+ if r, err := c.DoApiGet("/teams/all_team_listings", "", ""); err != nil {
+ return nil, err
+ } else {
+
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), TeamMapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) FindTeamByName(name string) (*Result, *AppError) {
m := make(map[string]string)
m["name"] = name
- m["all"] = fmt.Sprintf("%v", allServers)
if r, err := c.DoApiPost("/teams/find_team_by_name", MapToJson(m)); err != nil {
return nil, err
} else {
@@ -179,8 +227,32 @@ func (c *Client) FindTeamByName(name string, allServers bool) (*Result, *AppErro
}
}
+func (c *Client) AddUserToTeam(userId string) (*Result, *AppError) {
+ data := make(map[string]string)
+ data["user_id"] = userId
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/add_user_to_team", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) AddUserToTeamFromInvite(hash, dataToHash, inviteId string) (*Result, *AppError) {
+ data := make(map[string]string)
+ data["hash"] = hash
+ data["data"] = dataToHash
+ data["invite_id"] = inviteId
+ if r, err := c.DoApiPost("/teams/add_user_to_team_from_invite", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil
+ }
+}
+
func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) {
- if r, err := c.DoApiPost("/teams/invite_members", invites.ToJson()); err != nil {
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/invite_members", invites.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -189,7 +261,7 @@ func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) {
}
func (c *Client) UpdateTeam(team *Team) (*Result, *AppError) {
- if r, err := c.DoApiPost("/teams/update", team.ToJson()); err != nil {
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/update", team.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -206,6 +278,18 @@ func (c *Client) CreateUser(user *User, hash string) (*Result, *AppError) {
}
}
+func (c *Client) CreateUserWithInvite(user *User, hash string, data string, inviteId string) (*Result, *AppError) {
+
+ url := "/users/create?d=" + url.QueryEscape(data) + "&h=" + url.QueryEscape(hash) + "&iid=" + url.QueryEscape(inviteId)
+
+ if r, err := c.DoApiPost(url, user.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
+ }
+}
+
func (c *Client) CreateUserFromSignup(user *User, data string, hash string) (*Result, *AppError) {
if r, err := c.DoApiPost("/users/create?d="+url.QueryEscape(data)+"&h="+hash, user.ToJson()); err != nil {
return nil, err
@@ -216,7 +300,7 @@ func (c *Client) CreateUserFromSignup(user *User, data string, hash string) (*Re
}
func (c *Client) GetUser(id string, etag string) (*Result, *AppError) {
- if r, err := c.DoApiGet("/users/"+id, "", etag); err != nil {
+ if r, err := c.DoApiGet("/users/"+id+"/get", "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -242,6 +326,24 @@ func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) {
}
}
+func (c *Client) GetProfilesForTeam(teamId string, etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/users/profiles/"+teamId+"?skip_direct=true", "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/users/direct_profiles", "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil
+ }
+}
+
func (c *Client) LoginById(id string, password string) (*Result, *AppError) {
m := make(map[string]string)
m["id"] = id
@@ -274,6 +376,26 @@ func (c *Client) LoginByEmailWithDevice(name string, email string, password stri
return c.login(m)
}
+func (c *Client) LoginByLdap(userid string, password string, mfatoken string) (*Result, *AppError) {
+ m := make(map[string]string)
+ m["id"] = userid
+ m["password"] = password
+ m["token"] = mfatoken
+ if r, err := c.DoApiPost("/users/login_ldap", MapToJson(m)); err != nil {
+ return nil, err
+ } else {
+ c.AuthToken = r.Header.Get(HEADER_TOKEN)
+ c.AuthType = HEADER_BEARER
+ sessionToken := getCookie(SESSION_COOKIE_TOKEN, r)
+
+ if c.AuthToken != sessionToken.Value {
+ NewLocAppError("/users/login_ldap", "model.client.login.app_error", nil, "")
+ }
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
func (c *Client) login(m map[string]string) (*Result, *AppError) {
if r, err := c.DoApiPost("/users/login", MapToJson(m)); err != nil {
return nil, err
@@ -297,16 +419,16 @@ func (c *Client) Logout() (*Result, *AppError) {
} else {
c.AuthToken = ""
c.AuthType = HEADER_BEARER
+ c.TeamId = ""
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
}
}
-func (c *Client) CheckMfa(method, teamName, loginId string) (*Result, *AppError) {
+func (c *Client) CheckMfa(method, loginId string) (*Result, *AppError) {
m := make(map[string]string)
m["method"] = method
- m["team_name"] = teamName
m["login_id"] = loginId
if r, err := c.DoApiPost("/users/mfa", MapToJson(m)); err != nil {
@@ -339,14 +461,16 @@ func (c *Client) UpdateMfa(activate bool, token string) (*Result, *AppError) {
}
}
-func (c *Client) SetOAuthToken(token string) {
- c.AuthToken = token
- c.AuthType = HEADER_TOKEN
-}
+func (c *Client) AdminResetMfa(userId string) (*Result, *AppError) {
+ m := make(map[string]string)
+ m["user_id"] = userId
-func (c *Client) ClearOAuthToken() {
- c.AuthToken = ""
- c.AuthType = HEADER_BEARER
+ if r, err := c.DoApiPost("/admin/reset_mfa", MapToJson(m)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
}
func (c *Client) RevokeSession(sessionAltId string) (*Result, *AppError) {
@@ -411,7 +535,7 @@ func (c *Client) Command(channelId string, command string, suggest bool) (*Resul
m["command"] = command
m["channelId"] = channelId
m["suggest"] = strconv.FormatBool(suggest)
- if r, err := c.DoApiPost("/commands/execute", MapToJson(m)); err != nil {
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/execute", MapToJson(m)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -420,7 +544,7 @@ func (c *Client) Command(channelId string, command string, suggest bool) (*Resul
}
func (c *Client) ListCommands() (*Result, *AppError) {
- if r, err := c.DoApiGet("/commands/list", "", ""); err != nil {
+ if r, err := c.DoApiGet(c.GetTeamRoute()+"/commands/list", "", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -429,7 +553,7 @@ func (c *Client) ListCommands() (*Result, *AppError) {
}
func (c *Client) ListTeamCommands() (*Result, *AppError) {
- if r, err := c.DoApiGet("/commands/list_team_commands", "", ""); err != nil {
+ if r, err := c.DoApiGet(c.GetTeamRoute()+"/commands/list_team_commands", "", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -438,7 +562,7 @@ func (c *Client) ListTeamCommands() (*Result, *AppError) {
}
func (c *Client) CreateCommand(cmd *Command) (*Result, *AppError) {
- if r, err := c.DoApiPost("/commands/create", cmd.ToJson()); err != nil {
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/create", cmd.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -447,7 +571,7 @@ func (c *Client) CreateCommand(cmd *Command) (*Result, *AppError) {
}
func (c *Client) RegenCommandToken(data map[string]string) (*Result, *AppError) {
- if r, err := c.DoApiPost("/commands/regen_token", MapToJson(data)); err != nil {
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/regen_token", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -456,7 +580,7 @@ func (c *Client) RegenCommandToken(data map[string]string) (*Result, *AppError)
}
func (c *Client) DeleteCommand(data map[string]string) (*Result, *AppError) {
- if r, err := c.DoApiPost("/commands/delete", MapToJson(data)); err != nil {
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/delete", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -582,7 +706,7 @@ func (c *Client) GetSystemAnalytics(name string) (*Result, *AppError) {
}
func (c *Client) CreateChannel(channel *Channel) (*Result, *AppError) {
- if r, err := c.DoApiPost("/channels/create", channel.ToJson()); err != nil {
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/create", channel.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -590,8 +714,10 @@ func (c *Client) CreateChannel(channel *Channel) (*Result, *AppError) {
}
}
-func (c *Client) CreateDirectChannel(data map[string]string) (*Result, *AppError) {
- if r, err := c.DoApiPost("/channels/create_direct", MapToJson(data)); err != nil {
+func (c *Client) CreateDirectChannel(userId string) (*Result, *AppError) {
+ data := make(map[string]string)
+ data["user_id"] = userId
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/create_direct", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -600,7 +726,7 @@ func (c *Client) CreateDirectChannel(data map[string]string) (*Result, *AppError
}
func (c *Client) UpdateChannel(channel *Channel) (*Result, *AppError) {
- if r, err := c.DoApiPost("/channels/update", channel.ToJson()); err != nil {
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update", channel.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -609,7 +735,7 @@ func (c *Client) UpdateChannel(channel *Channel) (*Result, *AppError) {
}
func (c *Client) UpdateChannelHeader(data map[string]string) (*Result, *AppError) {
- if r, err := c.DoApiPost("/channels/update_header", MapToJson(data)); err != nil {
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update_header", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -618,7 +744,7 @@ func (c *Client) UpdateChannelHeader(data map[string]string) (*Result, *AppError
}
func (c *Client) UpdateChannelPurpose(data map[string]string) (*Result, *AppError) {
- if r, err := c.DoApiPost("/channels/update_purpose", MapToJson(data)); err != nil {
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update_purpose", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -627,7 +753,7 @@ func (c *Client) UpdateChannelPurpose(data map[string]string) (*Result, *AppErro
}
func (c *Client) UpdateNotifyProps(data map[string]string) (*Result, *AppError) {
- if r, err := c.DoApiPost("/channels/update_notify_props", MapToJson(data)); err != nil {
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update_notify_props", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -636,7 +762,7 @@ func (c *Client) UpdateNotifyProps(data map[string]string) (*Result, *AppError)
}
func (c *Client) GetChannels(etag string) (*Result, *AppError) {
- if r, err := c.DoApiGet("/channels/", "", etag); err != nil {
+ if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/", "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -645,7 +771,7 @@ func (c *Client) GetChannels(etag string) (*Result, *AppError) {
}
func (c *Client) GetChannel(id, etag string) (*Result, *AppError) {
- if r, err := c.DoApiGet("/channels/"+id+"/", "", etag); err != nil {
+ if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/", "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -654,7 +780,7 @@ func (c *Client) GetChannel(id, etag string) (*Result, *AppError) {
}
func (c *Client) GetMoreChannels(etag string) (*Result, *AppError) {
- if r, err := c.DoApiGet("/channels/more", "", etag); err != nil {
+ if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/more", "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -663,7 +789,7 @@ func (c *Client) GetMoreChannels(etag string) (*Result, *AppError) {
}
func (c *Client) GetChannelCounts(etag string) (*Result, *AppError) {
- if r, err := c.DoApiGet("/channels/counts", "", etag); err != nil {
+ if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/counts", "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -672,7 +798,7 @@ func (c *Client) GetChannelCounts(etag string) (*Result, *AppError) {
}
func (c *Client) JoinChannel(id string) (*Result, *AppError) {
- if r, err := c.DoApiPost("/channels/"+id+"/join", ""); err != nil {
+ if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/join", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -681,7 +807,7 @@ func (c *Client) JoinChannel(id string) (*Result, *AppError) {
}
func (c *Client) LeaveChannel(id string) (*Result, *AppError) {
- if r, err := c.DoApiPost("/channels/"+id+"/leave", ""); err != nil {
+ if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/leave", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -690,7 +816,7 @@ func (c *Client) LeaveChannel(id string) (*Result, *AppError) {
}
func (c *Client) DeleteChannel(id string) (*Result, *AppError) {
- if r, err := c.DoApiPost("/channels/"+id+"/delete", ""); err != nil {
+ if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/delete", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -701,7 +827,7 @@ func (c *Client) DeleteChannel(id string) (*Result, *AppError) {
func (c *Client) AddChannelMember(id, user_id string) (*Result, *AppError) {
data := make(map[string]string)
data["user_id"] = user_id
- if r, err := c.DoApiPost("/channels/"+id+"/add", MapToJson(data)); err != nil {
+ if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/add", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -712,7 +838,7 @@ func (c *Client) AddChannelMember(id, user_id string) (*Result, *AppError) {
func (c *Client) RemoveChannelMember(id, user_id string) (*Result, *AppError) {
data := make(map[string]string)
data["user_id"] = user_id
- if r, err := c.DoApiPost("/channels/"+id+"/remove", MapToJson(data)); err != nil {
+ if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/remove", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -721,7 +847,7 @@ func (c *Client) RemoveChannelMember(id, user_id string) (*Result, *AppError) {
}
func (c *Client) UpdateLastViewedAt(channelId string) (*Result, *AppError) {
- if r, err := c.DoApiPost("/channels/"+channelId+"/update_last_viewed_at", ""); err != nil {
+ if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/update_last_viewed_at", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -730,7 +856,7 @@ func (c *Client) UpdateLastViewedAt(channelId string) (*Result, *AppError) {
}
func (c *Client) GetChannelExtraInfo(id string, memberLimit int, etag string) (*Result, *AppError) {
- if r, err := c.DoApiGet("/channels/"+id+"/extra_info/"+strconv.FormatInt(int64(memberLimit), 10), "", etag); err != nil {
+ if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/extra_info/"+strconv.FormatInt(int64(memberLimit), 10), "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -739,7 +865,7 @@ func (c *Client) GetChannelExtraInfo(id string, memberLimit int, etag string) (*
}
func (c *Client) CreatePost(post *Post) (*Result, *AppError) {
- if r, err := c.DoApiPost("/channels/"+post.ChannelId+"/create", post.ToJson()); err != nil {
+ if r, err := c.DoApiPost(c.GetChannelRoute(post.ChannelId)+"/posts/create", post.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -748,7 +874,7 @@ func (c *Client) CreatePost(post *Post) (*Result, *AppError) {
}
func (c *Client) UpdatePost(post *Post) (*Result, *AppError) {
- if r, err := c.DoApiPost("/channels/"+post.ChannelId+"/update", post.ToJson()); err != nil {
+ if r, err := c.DoApiPost(c.GetChannelRoute(post.ChannelId)+"/posts/update", post.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -757,7 +883,7 @@ func (c *Client) UpdatePost(post *Post) (*Result, *AppError) {
}
func (c *Client) GetPosts(channelId string, offset int, limit int, etag string) (*Result, *AppError) {
- if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/posts/%v/%v", channelId, offset, limit), "", etag); err != nil {
+ if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/page/%v/%v", offset, limit), "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -766,7 +892,7 @@ func (c *Client) GetPosts(channelId string, offset int, limit int, etag string)
}
func (c *Client) GetPostsSince(channelId string, time int64) (*Result, *AppError) {
- if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/posts/%v", channelId, time), "", ""); err != nil {
+ if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/since/%v", time), "", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -775,7 +901,7 @@ func (c *Client) GetPostsSince(channelId string, time int64) (*Result, *AppError
}
func (c *Client) GetPostsBefore(channelId string, postid string, offset int, limit int, etag string) (*Result, *AppError) {
- if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/post/%v/before/%v/%v", channelId, postid, offset, limit), "", etag); err != nil {
+ if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/before/%v/%v", postid, offset, limit), "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -784,7 +910,7 @@ func (c *Client) GetPostsBefore(channelId string, postid string, offset int, lim
}
func (c *Client) GetPostsAfter(channelId string, postid string, offset int, limit int, etag string) (*Result, *AppError) {
- if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/post/%v/after/%v/%v", channelId, postid, offset, limit), "", etag); err != nil {
+ if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/posts/%v/after/%v/%v", postid, offset, limit), "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -793,7 +919,7 @@ func (c *Client) GetPostsAfter(channelId string, postid string, offset int, limi
}
func (c *Client) GetPost(channelId string, postId string, etag string) (*Result, *AppError) {
- if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/post/%v", channelId, postId), "", etag); err != nil {
+ if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/get", postId), "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -802,7 +928,7 @@ func (c *Client) GetPost(channelId string, postId string, etag string) (*Result,
}
func (c *Client) DeletePost(channelId string, postId string) (*Result, *AppError) {
- if r, err := c.DoApiPost(fmt.Sprintf("/channels/%v/post/%v/delete", channelId, postId), ""); err != nil {
+ if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/delete", postId), ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -811,7 +937,7 @@ func (c *Client) DeletePost(channelId string, postId string) (*Result, *AppError
}
func (c *Client) SearchPosts(terms string) (*Result, *AppError) {
- if r, err := c.DoApiGet("/posts/search?terms="+url.QueryEscape(terms), "", ""); err != nil {
+ if r, err := c.DoApiGet(c.GetTeamRoute()+"/posts/search?terms="+url.QueryEscape(terms), "", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -819,8 +945,16 @@ func (c *Client) SearchPosts(terms string) (*Result, *AppError) {
}
}
-func (c *Client) UploadFile(url string, data []byte, contentType string) (*Result, *AppError) {
- rq, _ := http.NewRequest("POST", c.ApiUrl+url, bytes.NewReader(data))
+func (c *Client) UploadProfileFile(data []byte, contentType string) (*Result, *AppError) {
+ return c.uploadFile(c.ApiUrl+"/users/newimage", data, contentType)
+}
+
+func (c *Client) UploadPostAttachment(data []byte, contentType string) (*Result, *AppError) {
+ return c.uploadFile(c.ApiUrl+c.GetTeamRoute()+"/files/upload", data, contentType)
+}
+
+func (c *Client) uploadFile(url string, data []byte, contentType string) (*Result, *AppError) {
+ rq, _ := http.NewRequest("POST", url, bytes.NewReader(data))
rq.Header.Set("Content-Type", contentType)
if len(c.AuthToken) > 0 {
@@ -842,7 +976,7 @@ func (c *Client) GetFile(url string, isFullUrl bool) (*Result, *AppError) {
if isFullUrl {
rq, _ = http.NewRequest("GET", url, nil)
} else {
- rq, _ = http.NewRequest("GET", c.ApiUrl+"/files/get"+url, nil)
+ rq, _ = http.NewRequest("GET", c.ApiUrl+c.GetTeamRoute()+"/files/get"+url, nil)
}
if len(c.AuthToken) > 0 {
@@ -861,7 +995,7 @@ func (c *Client) GetFile(url string, isFullUrl bool) (*Result, *AppError) {
func (c *Client) GetFileInfo(url string) (*Result, *AppError) {
var rq *http.Request
- rq, _ = http.NewRequest("GET", c.ApiUrl+"/files/get_info"+url, nil)
+ rq, _ = http.NewRequest("GET", c.ApiUrl+c.GetTeamRoute()+"/files/get_info"+url, nil)
if len(c.AuthToken) > 0 {
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
@@ -878,7 +1012,7 @@ func (c *Client) GetFileInfo(url string) (*Result, *AppError) {
}
func (c *Client) GetPublicLink(data map[string]string) (*Result, *AppError) {
- if r, err := c.DoApiPost("/files/get_public_link", MapToJson(data)); err != nil {
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/files/get_public_link", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -950,7 +1084,9 @@ func (c *Client) UpdateUserPassword(userId, currentPassword, newPassword string)
}
}
-func (c *Client) SendPasswordReset(data map[string]string) (*Result, *AppError) {
+func (c *Client) SendPasswordReset(email string) (*Result, *AppError) {
+ data := map[string]string{}
+ data["email"] = email
if r, err := c.DoApiPost("/users/send_password_reset", MapToJson(data)); err != nil {
return nil, err
} else {
@@ -959,7 +1095,10 @@ func (c *Client) SendPasswordReset(data map[string]string) (*Result, *AppError)
}
}
-func (c *Client) ResetPassword(data map[string]string) (*Result, *AppError) {
+func (c *Client) ResetPassword(code, newPassword string) (*Result, *AppError) {
+ data := map[string]string{}
+ data["code"] = code
+ data["new_password"] = newPassword
if r, err := c.DoApiPost("/users/reset_password", MapToJson(data)); err != nil {
return nil, err
} else {
@@ -968,6 +1107,18 @@ func (c *Client) ResetPassword(data map[string]string) (*Result, *AppError) {
}
}
+func (c *Client) AdminResetPassword(userId, newPassword string) (*Result, *AppError) {
+ data := map[string]string{}
+ data["user_id"] = userId
+ data["new_password"] = newPassword
+ if r, err := c.DoApiPost("/admin/reset_password", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
func (c *Client) GetStatuses(data []string) (*Result, *AppError) {
if r, err := c.DoApiPost("/users/status", ArrayToJson(data)); err != nil {
return nil, err
@@ -978,7 +1129,7 @@ func (c *Client) GetStatuses(data []string) (*Result, *AppError) {
}
func (c *Client) GetMyTeam(etag string) (*Result, *AppError) {
- if r, err := c.DoApiGet("/teams/me", "", etag); err != nil {
+ if r, err := c.DoApiGet(c.GetTeamRoute()+"/me", "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -986,6 +1137,15 @@ func (c *Client) GetMyTeam(etag string) (*Result, *AppError) {
}
}
+func (c *Client) GetTeamMembers(teamId string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/teams/members/"+teamId, "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), TeamMembersFromJson(r.Body)}, nil
+ }
+}
+
func (c *Client) RegisterApp(app *OAuthApp) (*Result, *AppError) {
if r, err := c.DoApiPost("/oauth/register", app.ToJson()); err != nil {
return nil, err
@@ -1014,7 +1174,7 @@ func (c *Client) GetAccessToken(data url.Values) (*Result, *AppError) {
}
func (c *Client) CreateIncomingWebhook(hook *IncomingWebhook) (*Result, *AppError) {
- if r, err := c.DoApiPost("/hooks/incoming/create", hook.ToJson()); err != nil {
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/incoming/create", hook.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -1031,8 +1191,10 @@ func (c *Client) PostToWebhook(id, payload string) (*Result, *AppError) {
}
}
-func (c *Client) DeleteIncomingWebhook(data map[string]string) (*Result, *AppError) {
- if r, err := c.DoApiPost("/hooks/incoming/delete", MapToJson(data)); err != nil {
+func (c *Client) DeleteIncomingWebhook(id string) (*Result, *AppError) {
+ data := make(map[string]string)
+ data["id"] = id
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/incoming/delete", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -1041,7 +1203,7 @@ func (c *Client) DeleteIncomingWebhook(data map[string]string) (*Result, *AppErr
}
func (c *Client) ListIncomingWebhooks() (*Result, *AppError) {
- if r, err := c.DoApiGet("/hooks/incoming/list", "", ""); err != nil {
+ if r, err := c.DoApiGet(c.GetTeamRoute()+"/hooks/incoming/list", "", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -1085,7 +1247,7 @@ func (c *Client) GetPreferenceCategory(category string) (*Result, *AppError) {
}
func (c *Client) CreateOutgoingWebhook(hook *OutgoingWebhook) (*Result, *AppError) {
- if r, err := c.DoApiPost("/hooks/outgoing/create", hook.ToJson()); err != nil {
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/create", hook.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -1093,8 +1255,10 @@ func (c *Client) CreateOutgoingWebhook(hook *OutgoingWebhook) (*Result, *AppErro
}
}
-func (c *Client) DeleteOutgoingWebhook(data map[string]string) (*Result, *AppError) {
- if r, err := c.DoApiPost("/hooks/outgoing/delete", MapToJson(data)); err != nil {
+func (c *Client) DeleteOutgoingWebhook(id string) (*Result, *AppError) {
+ data := make(map[string]string)
+ data["id"] = id
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/delete", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -1103,7 +1267,7 @@ func (c *Client) DeleteOutgoingWebhook(data map[string]string) (*Result, *AppErr
}
func (c *Client) ListOutgoingWebhooks() (*Result, *AppError) {
- if r, err := c.DoApiGet("/hooks/outgoing/list", "", ""); err != nil {
+ if r, err := c.DoApiGet(c.GetTeamRoute()+"/hooks/outgoing/list", "", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -1111,8 +1275,10 @@ func (c *Client) ListOutgoingWebhooks() (*Result, *AppError) {
}
}
-func (c *Client) RegenOutgoingWebhookToken(data map[string]string) (*Result, *AppError) {
- if r, err := c.DoApiPost("/hooks/outgoing/regen_token", MapToJson(data)); err != nil {
+func (c *Client) RegenOutgoingWebhookToken(id string) (*Result, *AppError) {
+ data := make(map[string]string)
+ data["id"] = id
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/regen_token", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -1134,11 +1300,11 @@ func (c *Client) GetClientLicenceConfig(etag string) (*Result, *AppError) {
}
}
-func (c *Client) GetMeLoggedIn() (*Result, *AppError) {
- if r, err := c.DoApiGet("/users/me_logged_in", "", ""); err != nil {
+func (c *Client) GetInitialLoad() (*Result, *AppError) {
+ if r, err := c.DoApiGet("/users/initial_load", "", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
- r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ r.Header.Get(HEADER_ETAG_SERVER), InitialLoadFromJson(r.Body)}, nil
}
}
diff --git a/model/command.go b/model/command.go
index b854ae76a..4d5f7ace9 100644
--- a/model/command.go
+++ b/model/command.go
@@ -99,7 +99,7 @@ func (o *Command) IsValid() *AppError {
return NewLocAppError("Command.IsValid", "model.command.is_valid.team_id.app_error", nil, "")
}
- if len(o.Trigger) > 128 {
+ if len(o.Trigger) == 0 || len(o.Trigger) > 128 {
return NewLocAppError("Command.IsValid", "model.command.is_valid.trigger.app_error", nil, "")
}
diff --git a/model/command_test.go b/model/command_test.go
index d362d8f2c..2376e2ef7 100644
--- a/model/command_test.go
+++ b/model/command_test.go
@@ -19,63 +19,115 @@ func TestCommandJson(t *testing.T) {
}
func TestCommandIsValid(t *testing.T) {
- o := Command{}
+ o := Command{
+ Id: NewId(),
+ Token: NewId(),
+ CreateAt: GetMillis(),
+ UpdateAt: GetMillis(),
+ CreatorId: NewId(),
+ TeamId: NewId(),
+ Trigger: "trigger",
+ URL: "http://example.com",
+ Method: COMMAND_METHOD_GET,
+ DisplayName: "",
+ Description: "",
+ }
+ if err := o.IsValid(); err != nil {
+ t.Fatal(err)
+ }
+
+ o.Id = ""
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Id = NewId()
+ if err := o.IsValid(); err != nil {
+ t.Fatal(err)
+ }
+
+ o.Token = ""
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
- o.CreateAt = GetMillis()
+ o.Token = NewId()
+ if err := o.IsValid(); err != nil {
+ t.Fatal(err)
+ }
+
+ o.CreateAt = 0
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
- o.UpdateAt = GetMillis()
+ o.CreateAt = GetMillis()
+ if err := o.IsValid(); err != nil {
+ t.Fatal(err)
+ }
+
+ o.UpdateAt = 0
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
- o.CreatorId = "123"
+ o.UpdateAt = GetMillis()
+ if err := o.IsValid(); err != nil {
+ t.Fatal(err)
+ }
+
+ o.CreatorId = ""
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.CreatorId = NewId()
- if err := o.IsValid(); err == nil {
- t.Fatal("should be invalid")
+ if err := o.IsValid(); err != nil {
+ t.Fatal(err)
}
- o.Token = "123"
+ o.TeamId = ""
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
- o.Token = NewId()
+ o.TeamId = NewId()
+ if err := o.IsValid(); err != nil {
+ t.Fatal(err)
+ }
+
+ o.Trigger = ""
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
- o.TeamId = "123"
+ o.Trigger = strings.Repeat("1", 129)
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
- o.TeamId = NewId()
+ o.Trigger = strings.Repeat("1", 128)
+ if err := o.IsValid(); err != nil {
+ t.Fatal(err)
+ }
+
+ o.URL = ""
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
- o.URL = "nowhere.com/"
+ o.URL = "1234"
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
- o.URL = "http://nowhere.com/"
+ o.URL = "https://example.com"
+ if err := o.IsValid(); err != nil {
+ t.Fatal(err)
+ }
+
+ o.Method = "https://example.com"
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
@@ -85,6 +137,11 @@ func TestCommandIsValid(t *testing.T) {
t.Fatal(err)
}
+ o.Method = COMMAND_METHOD_POST
+ if err := o.IsValid(); err != nil {
+ t.Fatal(err)
+ }
+
o.DisplayName = strings.Repeat("1", 65)
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
diff --git a/model/config.go b/model/config.go
index 6803fe069..fd033c7cf 100644
--- a/model/config.go
+++ b/model/config.go
@@ -155,9 +155,9 @@ type TeamSettings struct {
MaxUsersPerTeam int
EnableTeamCreation bool
EnableUserCreation bool
+ EnableOpenServer *bool
RestrictCreationToDomains string
RestrictTeamNames *bool
- EnableTeamListing *bool
EnableCustomBrand *bool
CustomBrandText *string
}
@@ -180,6 +180,7 @@ type LdapSettings struct {
LastNameAttribute *string
EmailAttribute *string
UsernameAttribute *string
+ NicknameAttribute *string
IdAttribute *string
// Advanced
@@ -297,11 +298,6 @@ func (o *Config) SetDefaults() {
*o.TeamSettings.RestrictTeamNames = true
}
- if o.TeamSettings.EnableTeamListing == nil {
- o.TeamSettings.EnableTeamListing = new(bool)
- *o.TeamSettings.EnableTeamListing = false
- }
-
if o.TeamSettings.EnableCustomBrand == nil {
o.TeamSettings.EnableCustomBrand = new(bool)
*o.TeamSettings.EnableCustomBrand = false
@@ -312,6 +308,11 @@ func (o *Config) SetDefaults() {
*o.TeamSettings.CustomBrandText = ""
}
+ if o.TeamSettings.EnableOpenServer == nil {
+ o.TeamSettings.EnableOpenServer = new(bool)
+ *o.TeamSettings.EnableOpenServer = false
+ }
+
if o.EmailSettings.EnableSignInWithEmail == nil {
o.EmailSettings.EnableSignInWithEmail = new(bool)
@@ -476,6 +477,11 @@ func (o *Config) SetDefaults() {
o.LdapSettings.SkipCertificateVerification = new(bool)
*o.LdapSettings.SkipCertificateVerification = false
}
+
+ if o.LdapSettings.NicknameAttribute == nil {
+ o.LdapSettings.NicknameAttribute = new(string)
+ *o.LdapSettings.NicknameAttribute = ""
+ }
}
func (o *Config) IsValid() *AppError {
diff --git a/model/gitlab.go b/model/gitlab.go
new file mode 100644
index 000000000..3dfb1016a
--- /dev/null
+++ b/model/gitlab.go
@@ -0,0 +1,8 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+const (
+ USER_AUTH_SERVICE_GITLAB = "gitlab"
+)
diff --git a/model/gitlab/gitlab.go b/model/gitlab/gitlab.go
index 3ca499976..7df29c139 100644
--- a/model/gitlab/gitlab.go
+++ b/model/gitlab/gitlab.go
@@ -12,10 +12,6 @@ import (
"strings"
)
-const (
- USER_AUTH_SERVICE_GITLAB = "gitlab"
-)
-
type GitLabProvider struct {
}
@@ -29,7 +25,7 @@ type GitLabUser struct {
func init() {
provider := &GitLabProvider{}
- einterfaces.RegisterOauthProvider(USER_AUTH_SERVICE_GITLAB, provider)
+ einterfaces.RegisterOauthProvider(model.USER_AUTH_SERVICE_GITLAB, provider)
}
func userFromGitLabUser(glu *GitLabUser) *model.User {
@@ -51,7 +47,7 @@ func userFromGitLabUser(glu *GitLabUser) *model.User {
}
user.Email = glu.Email
user.AuthData = strconv.FormatInt(glu.Id, 10)
- user.AuthService = USER_AUTH_SERVICE_GITLAB
+ user.AuthService = model.USER_AUTH_SERVICE_GITLAB
return user
}
@@ -84,7 +80,7 @@ func (glu *GitLabUser) getAuthData() string {
}
func (m *GitLabProvider) GetIdentifier() string {
- return USER_AUTH_SERVICE_GITLAB
+ return model.USER_AUTH_SERVICE_GITLAB
}
func (m *GitLabProvider) GetUserFromJson(data io.Reader) *model.User {
diff --git a/model/initial_load.go b/model/initial_load.go
new file mode 100644
index 000000000..d7587e6d4
--- /dev/null
+++ b/model/initial_load.go
@@ -0,0 +1,40 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+type InitialLoad struct {
+ User *User `json:"user"`
+ TeamMembers []*TeamMember `json:"team_members"`
+ Teams []*Team `json:"teams"`
+ DirectProfiles map[string]*User `json:"direct_profiles"`
+ Preferences Preferences `json:"preferences"`
+ ClientCfg map[string]string `json:"client_cfg"`
+ LicenseCfg map[string]string `json:"license_cfg"`
+ NoAccounts bool `json:"no_accounts"`
+}
+
+func (me *InitialLoad) ToJson() string {
+ b, err := json.Marshal(me)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func InitialLoadFromJson(data io.Reader) *InitialLoad {
+ decoder := json.NewDecoder(data)
+ var o InitialLoad
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
diff --git a/model/initial_load_test.go b/model/initial_load_test.go
new file mode 100644
index 000000000..24a07e412
--- /dev/null
+++ b/model/initial_load_test.go
@@ -0,0 +1,20 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestInitialLoadJson(t *testing.T) {
+ u := &User{Id: NewId()}
+ o := InitialLoad{User: u}
+ json := o.ToJson()
+ ro := InitialLoadFromJson(strings.NewReader(json))
+
+ if o.User.Id != ro.User.Id {
+ t.Fatal("Ids do not match")
+ }
+}
diff --git a/model/password_recovery.go b/model/password_recovery.go
new file mode 100644
index 000000000..303d4a12e
--- /dev/null
+++ b/model/password_recovery.go
@@ -0,0 +1,37 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+const (
+ PASSWORD_RECOVERY_CODE_SIZE = 128
+ PASSWORD_RECOVER_EXPIRY_TIME = 1000 * 60 * 60 // 1 hour
+)
+
+type PasswordRecovery struct {
+ UserId string
+ Code string
+ CreateAt int64
+}
+
+func (p *PasswordRecovery) IsValid() *AppError {
+
+ if len(p.UserId) != 26 {
+ return NewLocAppError("User.IsValid", "model.password_recovery.is_valid.user_id.app_error", nil, "")
+ }
+
+ if len(p.Code) != PASSWORD_RECOVERY_CODE_SIZE {
+ return NewLocAppError("User.IsValid", "model.password_recovery.is_valid.code.app_error", nil, "")
+ }
+
+ if p.CreateAt == 0 {
+ return NewLocAppError("User.IsValid", "model.password_recovery.is_valid.create_at.app_error", nil, "")
+ }
+
+ return nil
+}
+
+func (p *PasswordRecovery) PreSave() {
+ p.Code = NewRandomString(PASSWORD_RECOVERY_CODE_SIZE)
+ p.CreateAt = GetMillis()
+}
diff --git a/model/session.go b/model/session.go
index bf0d9531e..8a5eec74c 100644
--- a/model/session.go
+++ b/model/session.go
@@ -17,17 +17,17 @@ const (
)
type Session struct {
- Id string `json:"id"`
- Token string `json:"token"`
- CreateAt int64 `json:"create_at"`
- ExpiresAt int64 `json:"expires_at"`
- LastActivityAt int64 `json:"last_activity_at"`
- UserId string `json:"user_id"`
- TeamId string `json:"team_id"`
- DeviceId string `json:"device_id"`
- Roles string `json:"roles"`
- IsOAuth bool `json:"is_oauth"`
- Props StringMap `json:"props"`
+ Id string `json:"id"`
+ Token string `json:"token"`
+ CreateAt int64 `json:"create_at"`
+ ExpiresAt int64 `json:"expires_at"`
+ LastActivityAt int64 `json:"last_activity_at"`
+ UserId string `json:"user_id"`
+ DeviceId string `json:"device_id"`
+ Roles string `json:"roles"`
+ IsOAuth bool `json:"is_oauth"`
+ Props StringMap `json:"props"`
+ TeamMembers []*TeamMember `json:"team_members" db:"-"`
}
func (me *Session) ToJson() string {
@@ -95,6 +95,16 @@ func (me *Session) AddProp(key string, value string) {
me.Props[key] = value
}
+func (me *Session) GetTeamByTeamId(teamId string) *TeamMember {
+ for _, team := range me.TeamMembers {
+ if team.TeamId == teamId {
+ return team
+ }
+ }
+
+ return nil
+}
+
func SessionsToJson(o []*Session) string {
if b, err := json.Marshal(o); err != nil {
return "[]"
diff --git a/model/team.go b/model/team.go
index d95dea110..072e0a8c0 100644
--- a/model/team.go
+++ b/model/team.go
@@ -18,19 +18,18 @@ const (
)
type Team struct {
- Id string `json:"id"`
- CreateAt int64 `json:"create_at"`
- UpdateAt int64 `json:"update_at"`
- DeleteAt int64 `json:"delete_at"`
- DisplayName string `json:"display_name"`
- Name string `json:"name"`
- Email string `json:"email"`
- Type string `json:"type"`
- CompanyName string `json:"company_name"`
- AllowedDomains string `json:"allowed_domains"`
- InviteId string `json:"invite_id"`
- AllowOpenInvite bool `json:"allow_open_invite"`
- AllowTeamListing bool `json:"allow_team_listing"`
+ Id string `json:"id"`
+ CreateAt int64 `json:"create_at"`
+ UpdateAt int64 `json:"update_at"`
+ DeleteAt int64 `json:"delete_at"`
+ DisplayName string `json:"display_name"`
+ Name string `json:"name"`
+ Email string `json:"email"`
+ Type string `json:"type"`
+ CompanyName string `json:"company_name"`
+ AllowedDomains string `json:"allowed_domains"`
+ InviteId string `json:"invite_id"`
+ AllowOpenInvite bool `json:"allow_open_invite"`
}
type Invites struct {
diff --git a/model/team_member.go b/model/team_member.go
new file mode 100644
index 000000000..80ca9f2a3
--- /dev/null
+++ b/model/team_member.go
@@ -0,0 +1,78 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+ "strings"
+)
+
+const (
+ ROLE_TEAM_ADMIN = "admin"
+)
+
+type TeamMember struct {
+ TeamId string `json:"team_id"`
+ UserId string `json:"user_id"`
+ Roles string `json:"roles"`
+}
+
+func (o *TeamMember) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func TeamMemberFromJson(data io.Reader) *TeamMember {
+ decoder := json.NewDecoder(data)
+ var o TeamMember
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
+
+func TeamMembersToJson(o []*TeamMember) string {
+ if b, err := json.Marshal(o); err != nil {
+ return "[]"
+ } else {
+ return string(b)
+ }
+}
+
+func TeamMembersFromJson(data io.Reader) []*TeamMember {
+ decoder := json.NewDecoder(data)
+ var o []*TeamMember
+ err := decoder.Decode(&o)
+ if err == nil {
+ return o
+ } else {
+ return nil
+ }
+}
+
+func (o *TeamMember) IsValid() *AppError {
+
+ if len(o.TeamId) != 26 {
+ return NewLocAppError("TeamMember.IsValid", "model.team_member.is_valid.team_id.app_error", nil, "")
+ }
+
+ if len(o.UserId) != 26 {
+ return NewLocAppError("TeamMember.IsValid", "model.team_member.is_valid.user_id.app_error", nil, "")
+ }
+
+ for _, role := range strings.Split(o.Roles, " ") {
+ if !(role == "" || role == ROLE_TEAM_ADMIN) {
+ return NewLocAppError("TeamMember.IsValid", "model.team_member.is_valid.role.app_error", nil, "role="+role)
+ }
+ }
+
+ return nil
+}
diff --git a/model/team_member_test.go b/model/team_member_test.go
new file mode 100644
index 000000000..d5b2e3b79
--- /dev/null
+++ b/model/team_member_test.go
@@ -0,0 +1,43 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestTeamMemberJson(t *testing.T) {
+ o := TeamMember{TeamId: NewId(), UserId: NewId()}
+ json := o.ToJson()
+ ro := TeamMemberFromJson(strings.NewReader(json))
+
+ if o.TeamId != ro.TeamId {
+ t.Fatal("Ids do not match")
+ }
+}
+
+func TestTeamMemberIsValid(t *testing.T) {
+ o := TeamMember{}
+
+ if err := o.IsValid(); err == nil {
+ t.Fatal("should be invalid")
+ }
+
+ o.TeamId = NewId()
+ if err := o.IsValid(); err == nil {
+ t.Fatal("should be invalid")
+ }
+
+ o.UserId = NewId()
+ o.Roles = "blahblah"
+ if err := o.IsValid(); err == nil {
+ t.Fatal("should be invalid")
+ }
+
+ o.Roles = ""
+ if err := o.IsValid(); err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/model/user.go b/model/user.go
index 173fe2b4e..f43fc2089 100644
--- a/model/user.go
+++ b/model/user.go
@@ -15,7 +15,6 @@ import (
)
const (
- ROLE_TEAM_ADMIN = "admin"
ROLE_SYSTEM_ADMIN = "system_admin"
USER_AWAY_TIMEOUT = 5 * 60 * 1000 // 5 minutes
USER_OFFLINE_TIMEOUT = 1 * 60 * 1000 // 1 minute
@@ -28,6 +27,7 @@ const (
DEFAULT_LOCALE = "en"
USER_AUTH_SERVICE_EMAIL = "email"
USER_AUTH_SERVICE_USERNAME = "username"
+ MIN_PASSWORD_LENGTH = 5
)
type User struct {
@@ -35,7 +35,6 @@ type User struct {
CreateAt int64 `json:"create_at,omitempty"`
UpdateAt int64 `json:"update_at,omitempty"`
DeleteAt int64 `json:"delete_at"`
- TeamId string `json:"team_id"`
Username string `json:"username"`
Password string `json:"password,omitempty"`
AuthData string `json:"auth_data,omitempty"`
@@ -76,10 +75,6 @@ func (u *User) IsValid() *AppError {
return NewLocAppError("User.IsValid", "model.user.is_valid.update_at.app_error", nil, "user_id="+u.Id)
}
- if len(u.TeamId) != 26 {
- return NewLocAppError("User.IsValid", "model.user.is_valid.team_id.app_error", nil, "")
- }
-
if !IsValidUsername(u.Username) {
return NewLocAppError("User.IsValid", "model.user.is_valid.username.app_error", nil, "user_id="+u.Id)
}
@@ -228,6 +223,7 @@ func (u *User) IsAway() bool {
func (u *User) Sanitize(options map[string]bool) {
u.Password = ""
u.AuthData = ""
+ u.MfaSecret = ""
if len(options) != 0 && !options["email"] {
u.Email = ""
@@ -246,6 +242,8 @@ func (u *User) ClearNonProfileFields() {
u.Password = ""
u.AuthData = ""
u.AuthService = ""
+ u.MfaActive = false
+ u.MfaSecret = ""
u.EmailVerified = false
u.LastPingAt = 0
u.AllowMarketing = false
@@ -301,7 +299,7 @@ func (u *User) GetDisplayName() string {
}
}
-func IsValidRoles(userRoles string) bool {
+func IsValidUserRoles(userRoles string) bool {
roles := strings.Split(userRoles, " ")
@@ -319,10 +317,6 @@ func isValidRole(role string) bool {
return true
}
- if role == ROLE_TEAM_ADMIN {
- return true
- }
-
if role == ROLE_SYSTEM_ADMIN {
return true
}
@@ -351,8 +345,8 @@ func IsInRole(userRoles string, inRole string) bool {
return false
}
-func (u *User) IsSSOUser() bool {
- if len(u.AuthData) != 0 && len(u.AuthService) != 0 && u.AuthService != USER_AUTH_SERVICE_LDAP {
+func (u *User) IsOAuthUser() bool {
+ if u.AuthService == USER_AUTH_SERVICE_GITLAB {
return true
}
return false
diff --git a/model/user_test.go b/model/user_test.go
index 662ae35a6..286c92a66 100644
--- a/model/user_test.go
+++ b/model/user_test.go
@@ -63,11 +63,6 @@ func TestUserIsValid(t *testing.T) {
t.Fatal()
}
- user.TeamId = NewId()
- if err := user.IsValid(); err == nil {
- t.Fatal()
- }
-
user.Username = NewId() + "^hello#"
if err := user.IsValid(); err == nil {
t.Fatal()
@@ -195,11 +190,11 @@ func TestCleanUsername(t *testing.T) {
func TestRoles(t *testing.T) {
- if !IsValidRoles("admin") {
+ if IsValidUserRoles("admin") {
t.Fatal()
}
- if IsValidRoles("junk") {
+ if IsValidUserRoles("junk") {
t.Fatal()
}
diff --git a/model/utils.go b/model/utils.go
index bb02f345d..03215490d 100644
--- a/model/utils.go
+++ b/model/utils.go
@@ -41,12 +41,10 @@ func (er *AppError) Error() string {
}
func (er *AppError) Translate(T goi18n.TranslateFunc) {
- if len(er.Message) == 0 {
- if er.params == nil {
- er.Message = T(er.Id)
- } else {
- er.Message = T(er.Id, er.params)
- }
+ if er.params == nil {
+ er.Message = T(er.Id)
+ } else {
+ er.Message = T(er.Id, er.params)
}
}
@@ -83,6 +81,7 @@ func NewLocAppError(where string, id string, params map[string]interface{}, deta
ap := &AppError{}
ap.Id = id
ap.params = params
+ ap.Message = id
ap.Where = where
ap.DetailedError = details
ap.StatusCode = 500
diff --git a/model/version.go b/model/version.go
index e9a2c2bd0..4a47f06ef 100644
--- a/model/version.go
+++ b/model/version.go
@@ -13,6 +13,7 @@ import (
// It should be maitained in chronological order with most current
// release at the front of the list.
var versions = []string{
+ "3.0.0",
"2.2.0",
"2.1.0",
"2.0.0",
diff --git a/store/sql_audit_store.go b/store/sql_audit_store.go
index 7609ebc25..772a549a3 100644
--- a/store/sql_audit_store.go
+++ b/store/sql_audit_store.go
@@ -28,17 +28,6 @@ func NewSqlAuditStore(sqlStore *SqlStore) AuditStore {
}
func (s SqlAuditStore) UpgradeSchemaIfNeeded() {
- // ADDED for 2.2 REMOVE for 2.6
- extraLength := s.GetMaxLengthOfColumnIfExists("Audits", "ExtraInfo")
- if len(extraLength) > 0 && extraLength != "1024" {
- s.AlterColumnTypeIfExists("Audits", "ExtraInfo", "VARCHAR(1024)", "VARCHAR(1024)")
- }
-
- // ADDED for 2.2 REMOVE for 2.6
- actionLength := s.GetMaxLengthOfColumnIfExists("Audits", "Action")
- if len(actionLength) > 0 && actionLength != "512" {
- s.AlterColumnTypeIfExists("Audits", "Action", "VARCHAR(512)", "VARCHAR(512)")
- }
}
func (s SqlAuditStore) CreateIndexesIfNotExists() {
diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go
index c7ffddd56..46f56e7eb 100644
--- a/store/sql_channel_store.go
+++ b/store/sql_channel_store.go
@@ -95,6 +95,7 @@ func (s SqlChannelStore) SaveDirectChannel(directchannel *model.Channel, member1
if transaction, err := s.GetMaster().Begin(); err != nil {
result.Err = model.NewLocAppError("SqlChannelStore.SaveDirectChannel", "store.sql_channel.save_direct_channel.open_transaction.app_error", nil, err.Error())
} else {
+ directchannel.TeamId = ""
channelResult := s.saveChannelT(transaction, directchannel)
if channelResult.Err != nil {
@@ -330,7 +331,7 @@ func (s SqlChannelStore) GetChannels(teamId string, userId string) StoreChannel
result := StoreResult{}
var data []channelWithMember
- _, err := s.GetReplica().Select(&data, "SELECT * FROM Channels, ChannelMembers WHERE Id = ChannelId AND TeamId = :TeamId AND UserId = :UserId AND DeleteAt = 0 ORDER BY DisplayName", map[string]interface{}{"TeamId": teamId, "UserId": userId})
+ _, err := s.GetReplica().Select(&data, "SELECT * FROM Channels, ChannelMembers WHERE Id = ChannelId AND UserId = :UserId AND DeleteAt = 0 AND (TeamId = :TeamId OR TeamId = '') ORDER BY DisplayName", map[string]interface{}{"TeamId": teamId, "UserId": userId})
if err != nil {
result.Err = model.NewLocAppError("SqlChannelStore.GetChannels", "store.sql_channel.get_channels.get.app_error", nil, "teamId="+teamId+", userId="+userId+", err="+err.Error())
@@ -411,7 +412,7 @@ func (s SqlChannelStore) GetChannelCounts(teamId string, userId string) StoreCha
result := StoreResult{}
var data []channelIdWithCountAndUpdateAt
- _, err := s.GetReplica().Select(&data, "SELECT Id, TotalMsgCount, UpdateAt FROM Channels WHERE Id IN (SELECT ChannelId FROM ChannelMembers WHERE UserId = :UserId) AND TeamId = :TeamId AND DeleteAt = 0 ORDER BY DisplayName", map[string]interface{}{"TeamId": teamId, "UserId": userId})
+ _, err := s.GetReplica().Select(&data, "SELECT Id, TotalMsgCount, UpdateAt FROM Channels WHERE Id IN (SELECT ChannelId FROM ChannelMembers WHERE UserId = :UserId) AND (TeamId = :TeamId OR TeamId = '') AND DeleteAt = 0 ORDER BY DisplayName", map[string]interface{}{"TeamId": teamId, "UserId": userId})
if err != nil {
result.Err = model.NewLocAppError("SqlChannelStore.GetChannelCounts", "store.sql_channel.get_channel_counts.get.app_error", nil, "teamId="+teamId+", userId="+userId+", err="+err.Error())
@@ -441,7 +442,7 @@ func (s SqlChannelStore) GetByName(teamId string, name string) StoreChannel {
channel := model.Channel{}
- if err := s.GetReplica().SelectOne(&channel, "SELECT * FROM Channels WHERE TeamId = :TeamId AND Name= :Name AND DeleteAt = 0", map[string]interface{}{"TeamId": teamId, "Name": name}); err != nil {
+ if err := s.GetReplica().SelectOne(&channel, "SELECT * FROM Channels WHERE (TeamId = :TeamId OR TeamId = '') AND Name = :Name AND DeleteAt = 0", map[string]interface{}{"TeamId": teamId, "Name": name}); err != nil {
if err == sql.ErrNoRows {
result.Err = model.NewLocAppError("SqlChannelStore.GetByName", MISSING_CHANNEL_ERROR, nil, "teamId="+teamId+", "+"name="+name+", "+err.Error())
} else {
@@ -719,6 +720,37 @@ func (s SqlChannelStore) PermanentDeleteMembersByUser(userId string) StoreChanne
return storeChannel
}
+func (s SqlChannelStore) CheckPermissionsToNoTeam(channelId string, userId string) StoreChannel {
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ count, err := s.GetReplica().SelectInt(
+ `SELECT
+ COUNT(0)
+ FROM
+ Channels,
+ ChannelMembers
+ WHERE
+ Channels.Id = ChannelMembers.ChannelId
+ AND Channels.DeleteAt = 0
+ AND ChannelMembers.ChannelId = :ChannelId
+ AND ChannelMembers.UserId = :UserId`,
+ map[string]interface{}{"ChannelId": channelId, "UserId": userId})
+ if err != nil {
+ result.Err = model.NewLocAppError("SqlChannelStore.CheckPermissionsTo", "store.sql_channel.check_permissions.app_error", nil, "channel_id="+channelId+", user_id="+userId+", "+err.Error())
+ } else {
+ result.Data = count
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
func (s SqlChannelStore) CheckPermissionsTo(teamId string, channelId string, userId string) StoreChannel {
storeChannel := make(StoreChannel)
@@ -733,7 +765,7 @@ func (s SqlChannelStore) CheckPermissionsTo(teamId string, channelId string, use
ChannelMembers
WHERE
Channels.Id = ChannelMembers.ChannelId
- AND Channels.TeamId = :TeamId
+ AND (Channels.TeamId = :TeamId OR Channels.TeamId = '')
AND Channels.DeleteAt = 0
AND ChannelMembers.ChannelId = :ChannelId
AND ChannelMembers.UserId = :UserId`,
@@ -765,7 +797,7 @@ func (s SqlChannelStore) CheckPermissionsToByName(teamId string, channelName str
ChannelMembers
WHERE
Channels.Id = ChannelMembers.ChannelId
- AND Channels.TeamId = :TeamId
+ AND (Channels.TeamId = :TeamId OR Channels.TeamId = '')
AND Channels.Name = :Name
AND Channels.DeleteAt = 0
AND ChannelMembers.UserId = :UserId`,
diff --git a/store/sql_channel_store_test.go b/store/sql_channel_store_test.go
index 2213aa795..1b3ea6fe5 100644
--- a/store/sql_channel_store_test.go
+++ b/store/sql_channel_store_test.go
@@ -67,17 +67,17 @@ func TestChannelStoreSaveDirectChannel(t *testing.T) {
o1.Name = "a" + model.NewId() + "b"
o1.Type = model.CHANNEL_DIRECT
- u1 := model.User{}
- u1.TeamId = model.NewId()
+ u1 := &model.User{}
u1.Email = model.NewId()
u1.Nickname = model.NewId()
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}))
- u2 := model.User{}
- u2.TeamId = model.NewId()
+ u2 := &model.User{}
u2.Email = model.NewId()
u2.Nickname = model.NewId()
- Must(store.User().Save(&u2))
+ Must(store.User().Save(u2))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u2.Id}))
m1 := model.ChannelMember{}
m1.ChannelId = o1.Id
@@ -163,17 +163,17 @@ func TestChannelStoreGet(t *testing.T) {
t.Fatal("Missing id should have failed")
}
- u1 := model.User{}
- u1.TeamId = model.NewId()
+ u1 := &model.User{}
u1.Email = model.NewId()
u1.Nickname = model.NewId()
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}))
u2 := model.User{}
- u2.TeamId = model.NewId()
u2.Email = model.NewId()
u2.Nickname = model.NewId()
Must(store.User().Save(&u2))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u2.Id}))
o2 := model.Channel{}
o2.TeamId = model.NewId()
@@ -309,16 +309,16 @@ func TestChannelMemberStore(t *testing.T) {
t1 := c1t1.ExtraUpdateAt
u1 := model.User{}
- u1.TeamId = model.NewId()
u1.Email = model.NewId()
u1.Nickname = model.NewId()
Must(store.User().Save(&u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}))
u2 := model.User{}
- u2.TeamId = model.NewId()
u2.Email = model.NewId()
u2.Nickname = model.NewId()
Must(store.User().Save(&u2))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u2.Id}))
o1 := model.ChannelMember{}
o1.ChannelId = c1.Id
@@ -405,16 +405,16 @@ func TestChannelDeleteMemberStore(t *testing.T) {
t1 := c1t1.ExtraUpdateAt
u1 := model.User{}
- u1.TeamId = model.NewId()
u1.Email = model.NewId()
u1.Nickname = model.NewId()
Must(store.User().Save(&u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}))
u2 := model.User{}
- u2.TeamId = model.NewId()
u2.Email = model.NewId()
u2.Nickname = model.NewId()
Must(store.User().Save(&u2))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u2.Id}))
o1 := model.ChannelMember{}
o1.ChannelId = c1.Id
@@ -469,6 +469,11 @@ func TestChannelStorePermissionsTo(t *testing.T) {
t.Fatal("should have permissions")
}
+ count = (<-store.Channel().CheckPermissionsToNoTeam(o1.Id, m1.UserId)).Data.(int64)
+ if count != 1 {
+ t.Fatal("should have permissions")
+ }
+
count = (<-store.Channel().CheckPermissionsTo("junk", o1.Id, m1.UserId)).Data.(int64)
if count != 0 {
t.Fatal("shouldn't have permissions")
@@ -479,11 +484,21 @@ func TestChannelStorePermissionsTo(t *testing.T) {
t.Fatal("shouldn't have permissions")
}
+ count = (<-store.Channel().CheckPermissionsToNoTeam("junk", m1.UserId)).Data.(int64)
+ if count != 0 {
+ t.Fatal("shouldn't have permissions")
+ }
+
count = (<-store.Channel().CheckPermissionsTo(o1.TeamId, o1.Id, "junk")).Data.(int64)
if count != 0 {
t.Fatal("shouldn't have permissions")
}
+ count = (<-store.Channel().CheckPermissionsToNoTeam(o1.Id, "junk")).Data.(int64)
+ if count != 0 {
+ t.Fatal("shouldn't have permissions")
+ }
+
channelId := (<-store.Channel().CheckPermissionsToByName(o1.TeamId, o1.Name, m1.UserId)).Data.(string)
if channelId != o1.Id {
t.Fatal("should have permissions")
@@ -786,12 +801,12 @@ func TestGetMemberCount(t *testing.T) {
t.Logf("c1.Id = %v", c1.Id)
- u1 := model.User{
- TeamId: teamId,
+ u1 := &model.User{
Email: model.NewId(),
DeleteAt: 0,
}
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
m1 := model.ChannelMember{
ChannelId: c1.Id,
@@ -807,11 +822,11 @@ func TestGetMemberCount(t *testing.T) {
}
u2 := model.User{
- TeamId: teamId,
Email: model.NewId(),
DeleteAt: 0,
}
Must(store.User().Save(&u2))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
m2 := model.ChannelMember{
ChannelId: c1.Id,
@@ -828,11 +843,11 @@ func TestGetMemberCount(t *testing.T) {
// make sure members of other channels aren't counted
u3 := model.User{
- TeamId: teamId,
Email: model.NewId(),
DeleteAt: 0,
}
Must(store.User().Save(&u3))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}))
m3 := model.ChannelMember{
ChannelId: c2.Id,
@@ -848,12 +863,12 @@ func TestGetMemberCount(t *testing.T) {
}
// make sure inactive users aren't counted
- u4 := model.User{
- TeamId: teamId,
+ u4 := &model.User{
Email: model.NewId(),
DeleteAt: 10000,
}
- Must(store.User().Save(&u4))
+ Must(store.User().Save(u4))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u4.Id}))
m4 := model.ChannelMember{
ChannelId: c1.Id,
@@ -892,12 +907,12 @@ func TestUpdateExtrasByUser(t *testing.T) {
t.Logf("c1.Id = %v", c1.Id)
- u1 := model.User{
- TeamId: teamId,
+ u1 := &model.User{
Email: model.NewId(),
DeleteAt: 0,
}
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
m1 := model.ChannelMember{
ChannelId: c1.Id,
@@ -907,7 +922,7 @@ func TestUpdateExtrasByUser(t *testing.T) {
Must(store.Channel().SaveMember(&m1))
u1.DeleteAt = model.GetMillis()
- Must(store.User().Update(&u1, true))
+ Must(store.User().Update(u1, true))
if result := <-store.Channel().ExtraUpdateByUser(u1.Id, u1.DeleteAt); result.Err != nil {
t.Fatal("failed to update extras by user: %v", result.Err)
@@ -920,7 +935,7 @@ func TestUpdateExtrasByUser(t *testing.T) {
}
u1.DeleteAt = 0
- Must(store.User().Update(&u1, true))
+ Must(store.User().Update(u1, true))
if result := <-store.Channel().ExtraUpdateByUser(u1.Id, u1.DeleteAt); result.Err != nil {
t.Fatal("failed to update extras by user: %v", result.Err)
diff --git a/store/sql_command_store_test.go b/store/sql_command_store_test.go
index 644ebc9ae..ae1c61df3 100644
--- a/store/sql_command_store_test.go
+++ b/store/sql_command_store_test.go
@@ -16,6 +16,7 @@ func TestCommandStoreSave(t *testing.T) {
o1.Method = model.COMMAND_METHOD_POST
o1.TeamId = model.NewId()
o1.URL = "http://nowhere.com/"
+ o1.Trigger = "trigger"
if err := (<-store.Command().Save(&o1)).Err; err != nil {
t.Fatal("couldn't save item", err)
@@ -34,6 +35,7 @@ func TestCommandStoreGet(t *testing.T) {
o1.Method = model.COMMAND_METHOD_POST
o1.TeamId = model.NewId()
o1.URL = "http://nowhere.com/"
+ o1.Trigger = "trigger"
o1 = (<-store.Command().Save(o1)).Data.(*model.Command)
@@ -58,6 +60,7 @@ func TestCommandStoreGetByTeam(t *testing.T) {
o1.Method = model.COMMAND_METHOD_POST
o1.TeamId = model.NewId()
o1.URL = "http://nowhere.com/"
+ o1.Trigger = "trigger"
o1 = (<-store.Command().Save(o1)).Data.(*model.Command)
@@ -86,6 +89,7 @@ func TestCommandStoreDelete(t *testing.T) {
o1.Method = model.COMMAND_METHOD_POST
o1.TeamId = model.NewId()
o1.URL = "http://nowhere.com/"
+ o1.Trigger = "trigger"
o1 = (<-store.Command().Save(o1)).Data.(*model.Command)
@@ -115,6 +119,7 @@ func TestCommandStoreDeleteByUser(t *testing.T) {
o1.Method = model.COMMAND_METHOD_POST
o1.TeamId = model.NewId()
o1.URL = "http://nowhere.com/"
+ o1.Trigger = "trigger"
o1 = (<-store.Command().Save(o1)).Data.(*model.Command)
@@ -144,6 +149,7 @@ func TestCommandStoreUpdate(t *testing.T) {
o1.Method = model.COMMAND_METHOD_POST
o1.TeamId = model.NewId()
o1.URL = "http://nowhere.com/"
+ o1.Trigger = "trigger"
o1 = (<-store.Command().Save(o1)).Data.(*model.Command)
@@ -162,6 +168,7 @@ func TestCommandCount(t *testing.T) {
o1.Method = model.COMMAND_METHOD_POST
o1.TeamId = model.NewId()
o1.URL = "http://nowhere.com/"
+ o1.Trigger = "trigger"
o1 = (<-store.Command().Save(o1)).Data.(*model.Command)
diff --git a/store/sql_compliance_store_test.go b/store/sql_compliance_store_test.go
index 1a41fa389..b7b270a42 100644
--- a/store/sql_compliance_store_test.go
+++ b/store/sql_compliance_store_test.go
@@ -58,16 +58,16 @@ func TestComplianceExport(t *testing.T) {
t1 = Must(store.Team().Save(t1)).(*model.Team)
u1 := &model.User{}
- u1.TeamId = t1.Id
u1.Email = model.NewId()
u1.Username = model.NewId()
u1 = Must(store.User().Save(u1)).(*model.User)
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: t1.Id, UserId: u1.Id}))
u2 := &model.User{}
- u2.TeamId = t1.Id
u2.Email = model.NewId()
u2.Username = model.NewId()
u2 = Must(store.User().Save(u2)).(*model.User)
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: t1.Id, UserId: u2.Id}))
c1 := &model.Channel{}
c1.TeamId = t1.Id
diff --git a/store/sql_post_store.go b/store/sql_post_store.go
index 401306862..54b526191 100644
--- a/store/sql_post_store.go
+++ b/store/sql_post_store.go
@@ -652,7 +652,7 @@ func (s SqlPostStore) Search(teamId string, userId string, params *model.SearchP
ChannelMembers
WHERE
Id = ChannelId
- AND TeamId = :TeamId
+ AND (TeamId = :TeamId OR TeamId = '')
AND UserId = :UserId
AND DeleteAt = 0
CHANNEL_FILTER)
@@ -693,9 +693,11 @@ func (s SqlPostStore) Search(teamId string, userId string, params *model.SearchP
SELECT
Id
FROM
- Users
+ Users,
+ TeamMembers
WHERE
- TeamId = :TeamId
+ TeamMembers.TeamId = :TeamId
+ AND Users.Id = TeamMembers.UserId
AND Username IN (`+inClause+`))`, 1)
} else if len(params.FromUsers) == 1 {
queryParams["FromUser"] = params.FromUsers[0]
@@ -704,9 +706,11 @@ func (s SqlPostStore) Search(teamId string, userId string, params *model.SearchP
SELECT
Id
FROM
- Users
+ Users,
+ TeamMembers
WHERE
- TeamId = :TeamId
+ TeamMembers.TeamId = :TeamId
+ AND Users.Id = TeamMembers.UserId
AND Username = :FromUser)`, 1)
} else {
searchQuery = strings.Replace(searchQuery, "POST_FILTER", "", 1)
diff --git a/store/sql_recovery_store.go b/store/sql_recovery_store.go
new file mode 100644
index 000000000..17a444ebb
--- /dev/null
+++ b/store/sql_recovery_store.go
@@ -0,0 +1,124 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package store
+
+import (
+ "github.com/mattermost/platform/model"
+)
+
+type SqlPasswordRecoveryStore struct {
+ *SqlStore
+}
+
+func NewSqlPasswordRecoveryStore(sqlStore *SqlStore) PasswordRecoveryStore {
+ s := &SqlPasswordRecoveryStore{sqlStore}
+
+ for _, db := range sqlStore.GetAllConns() {
+ table := db.AddTableWithName(model.PasswordRecovery{}, "PasswordRecovery").SetKeys(false, "UserId")
+ table.ColMap("UserId").SetMaxSize(26)
+ table.ColMap("Code").SetMaxSize(128)
+ }
+
+ return s
+}
+
+func (s SqlPasswordRecoveryStore) UpgradeSchemaIfNeeded() {
+}
+
+func (s SqlPasswordRecoveryStore) CreateIndexesIfNotExists() {
+ s.CreateIndexIfNotExists("idx_password_recovery_code", "PasswordRecovery", "Code")
+}
+
+func (s SqlPasswordRecoveryStore) SaveOrUpdate(recovery *model.PasswordRecovery) StoreChannel {
+
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ recovery.PreSave()
+ if result.Err = recovery.IsValid(); result.Err != nil {
+ storeChannel <- result
+ close(storeChannel)
+ return
+ }
+
+ if err := s.GetReplica().SelectOne(&model.PasswordRecovery{}, "SELECT * FROM PasswordRecovery WHERE UserId = :UserId", map[string]interface{}{"UserId": recovery.UserId}); err == nil {
+ if _, err := s.GetMaster().Update(recovery); err != nil {
+ result.Err = model.NewLocAppError("SqlPasswordRecoveryStore.SaveOrUpdate", "store.sql_recover.update.app_error", nil, "")
+ }
+ } else {
+ if err := s.GetMaster().Insert(recovery); err != nil {
+ result.Err = model.NewLocAppError("SqlPasswordRecoveryStore.SaveOrUpdate", "store.sql_recover.save.app_error", nil, "")
+ }
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
+func (s SqlPasswordRecoveryStore) Delete(userId string) StoreChannel {
+
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ if _, err := s.GetMaster().Exec("DELETE FROM PasswordRecovery WHERE UserId = :UserId", map[string]interface{}{"UserId": userId}); err != nil {
+ result.Err = model.NewLocAppError("SqlPasswordRecoveryStore.Delete", "store.sql_recover.delete.app_error", nil, "")
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
+func (s SqlPasswordRecoveryStore) Get(userId string) StoreChannel {
+
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ recovery := model.PasswordRecovery{}
+
+ if err := s.GetReplica().SelectOne(&recovery, "SELECT * FROM PasswordRecovery WHERE UserId = :UserId", map[string]interface{}{"UserId": userId}); err != nil {
+ result.Err = model.NewLocAppError("SqlPasswordRecoveryStore.Get", "store.sql_recover.get.app_error", nil, "")
+ }
+
+ result.Data = &recovery
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
+func (s SqlPasswordRecoveryStore) GetByCode(code string) StoreChannel {
+
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ recovery := model.PasswordRecovery{}
+
+ if err := s.GetReplica().SelectOne(&recovery, "SELECT * FROM PasswordRecovery WHERE Code = :Code", map[string]interface{}{"Code": code}); err != nil {
+ result.Err = model.NewLocAppError("SqlPasswordRecoveryStore.GetByCode", "store.sql_recover.get_by_code.app_error", nil, "")
+ }
+
+ result.Data = &recovery
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
diff --git a/store/sql_recovery_store_test.go b/store/sql_recovery_store_test.go
new file mode 100644
index 000000000..cf1048482
--- /dev/null
+++ b/store/sql_recovery_store_test.go
@@ -0,0 +1,54 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package store
+
+import (
+ "github.com/mattermost/platform/model"
+ "testing"
+)
+
+func TestSqlPasswordRecoveryGet(t *testing.T) {
+ Setup()
+
+ recovery := &model.PasswordRecovery{UserId: "12345678901234567890123456"}
+ Must(store.PasswordRecovery().SaveOrUpdate(recovery))
+
+ result := <-store.PasswordRecovery().Get(recovery.UserId)
+ rrecovery := result.Data.(*model.PasswordRecovery)
+ if rrecovery.Code != recovery.Code {
+ t.Fatal("codes didn't match")
+ }
+
+ result2 := <-store.PasswordRecovery().GetByCode(recovery.Code)
+ rrecovery2 := result2.Data.(*model.PasswordRecovery)
+ if rrecovery2.Code != recovery.Code {
+ t.Fatal("codes didn't match")
+ }
+}
+
+func TestSqlPasswordRecoverySaveOrUpdate(t *testing.T) {
+ Setup()
+
+ recovery := &model.PasswordRecovery{UserId: "12345678901234567890123456"}
+
+ if err := (<-store.PasswordRecovery().SaveOrUpdate(recovery)).Err; err != nil {
+ t.Fatal(err)
+ }
+
+ // not duplicate, testing update
+ if err := (<-store.PasswordRecovery().SaveOrUpdate(recovery)).Err; err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestSqlPasswordRecoveryDelete(t *testing.T) {
+ Setup()
+
+ recovery := &model.PasswordRecovery{UserId: "12345678901234567890123456"}
+ Must(store.PasswordRecovery().SaveOrUpdate(recovery))
+
+ if err := (<-store.PasswordRecovery().Delete(recovery.UserId)).Err; err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/store/sql_session_store.go b/store/sql_session_store.go
index 337ad16e6..525d0e5b2 100644
--- a/store/sql_session_store.go
+++ b/store/sql_session_store.go
@@ -21,7 +21,6 @@ func NewSqlSessionStore(sqlStore *SqlStore) SessionStore {
table.ColMap("Id").SetMaxSize(26)
table.ColMap("Token").SetMaxSize(26)
table.ColMap("UserId").SetMaxSize(26)
- table.ColMap("TeamId").SetMaxSize(26)
table.ColMap("DeviceId").SetMaxSize(512)
table.ColMap("Roles").SetMaxSize(64)
table.ColMap("Props").SetMaxSize(1000)
@@ -63,12 +62,22 @@ func (me SqlSessionStore) Save(session *model.Session) StoreChannel {
l4g.Error(utils.T("store.sql_session.save.cleanup.error"), cur.Err)
}
+ tcs := me.Team().GetTeamsForUser(session.UserId)
+
if err := me.GetMaster().Insert(session); err != nil {
result.Err = model.NewLocAppError("SqlSessionStore.Save", "store.sql_session.save.app_error", nil, "id="+session.Id+", "+err.Error())
+ return
} else {
result.Data = session
}
+ if rtcs := <-tcs; rtcs.Err != nil {
+ result.Err = model.NewLocAppError("SqlSessionStore.Save", "store.sql_session.save.app_error", nil, "id="+session.Id+", "+rtcs.Err.Error())
+ return
+ } else {
+ session.TeamMembers = rtcs.Data.([]*model.TeamMember)
+ }
+
storeChannel <- result
close(storeChannel)
}()
@@ -91,6 +100,14 @@ func (me SqlSessionStore) Get(sessionIdOrToken string) StoreChannel {
result.Err = model.NewLocAppError("SqlSessionStore.Get", "store.sql_session.get.app_error", nil, "sessionIdOrToken="+sessionIdOrToken)
} else {
result.Data = sessions[0]
+
+ tcs := me.Team().GetTeamsForUser(sessions[0].UserId)
+ if rtcs := <-tcs; rtcs.Err != nil {
+ result.Err = model.NewLocAppError("SqlSessionStore.Get", "store.sql_session.get.app_error", nil, "sessionIdOrToken="+sessionIdOrToken+", "+rtcs.Err.Error())
+ return
+ } else {
+ sessions[0].TeamMembers = rtcs.Data.([]*model.TeamMember)
+ }
}
storeChannel <- result
@@ -111,9 +128,10 @@ func (me SqlSessionStore) GetSessions(userId string) StoreChannel {
}
result := StoreResult{}
-
var sessions []*model.Session
+ tcs := me.Team().GetTeamsForUser(userId)
+
if _, err := me.GetReplica().Select(&sessions, "SELECT * FROM Sessions WHERE UserId = :UserId ORDER BY LastActivityAt DESC", map[string]interface{}{"UserId": userId}); err != nil {
result.Err = model.NewLocAppError("SqlSessionStore.GetSessions", "store.sql_session.get_sessions.app_error", nil, err.Error())
} else {
@@ -121,6 +139,15 @@ func (me SqlSessionStore) GetSessions(userId string) StoreChannel {
result.Data = sessions
}
+ if rtcs := <-tcs; rtcs.Err != nil {
+ result.Err = model.NewLocAppError("SqlSessionStore.GetSessions", "store.sql_session.get_sessions.app_error", nil, rtcs.Err.Error())
+ return
+ } else {
+ for _, session := range sessions {
+ session.TeamMembers = rtcs.Data.([]*model.TeamMember)
+ }
+ }
+
storeChannel <- result
close(storeChannel)
}()
@@ -146,15 +173,15 @@ func (me SqlSessionStore) Remove(sessionIdOrToken string) StoreChannel {
return storeChannel
}
-func (me SqlSessionStore) RemoveAllSessionsForTeam(teamId string) StoreChannel {
+func (me SqlSessionStore) RemoveAllSessions() StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
- _, err := me.GetMaster().Exec("DELETE FROM Sessions WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": teamId})
+ _, err := me.GetMaster().Exec("DELETE FROM Sessions")
if err != nil {
- result.Err = model.NewLocAppError("SqlSessionStore.RemoveAllSessionsForTeam", "store.sql_session.remove_all_sessions_for_team.app_error", nil, "id="+teamId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlSessionStore.RemoveAllSessions", "store.sql_session.remove_all_sessions_for_team.app_error", nil, err.Error())
}
storeChannel <- result
@@ -256,7 +283,7 @@ func (me SqlSessionStore) UpdateDeviceId(id, deviceId string) StoreChannel {
return storeChannel
}
-func (me SqlSessionStore) AnalyticsSessionCount(teamId string) StoreChannel {
+func (me SqlSessionStore) AnalyticsSessionCount() StoreChannel {
storeChannel := make(StoreChannel)
go func() {
@@ -269,11 +296,7 @@ func (me SqlSessionStore) AnalyticsSessionCount(teamId string) StoreChannel {
Sessions
WHERE ExpiresAt > :Time`
- if len(teamId) > 0 {
- query += " AND TeamId = :TeamId"
- }
-
- if c, err := me.GetReplica().SelectInt(query, map[string]interface{}{"Time": model.GetMillis(), "TeamId": teamId}); err != nil {
+ if c, err := me.GetReplica().SelectInt(query, map[string]interface{}{"Time": model.GetMillis()}); err != nil {
result.Err = model.NewLocAppError("SqlSessionStore.AnalyticsSessionCount", "store.sql_session.analytics_session_count.app_error", nil, err.Error())
} else {
result.Data = c
diff --git a/store/sql_session_store_test.go b/store/sql_session_store_test.go
index 506695f0e..d7f07254d 100644
--- a/store/sql_session_store_test.go
+++ b/store/sql_session_store_test.go
@@ -13,7 +13,6 @@ func TestSessionStoreSave(t *testing.T) {
s1 := model.Session{}
s1.UserId = model.NewId()
- s1.TeamId = model.NewId()
if err := (<-store.Session().Save(&s1)).Err; err != nil {
t.Fatal(err)
@@ -25,17 +24,14 @@ func TestSessionGet(t *testing.T) {
s1 := model.Session{}
s1.UserId = model.NewId()
- s1.TeamId = model.NewId()
Must(store.Session().Save(&s1))
s2 := model.Session{}
s2.UserId = s1.UserId
- s2.TeamId = s1.TeamId
Must(store.Session().Save(&s2))
s3 := model.Session{}
s3.UserId = s1.UserId
- s3.TeamId = s1.TeamId
s3.ExpiresAt = 1
Must(store.Session().Save(&s3))
@@ -62,7 +58,6 @@ func TestSessionRemove(t *testing.T) {
s1 := model.Session{}
s1.UserId = model.NewId()
- s1.TeamId = model.NewId()
Must(store.Session().Save(&s1))
if rs1 := (<-store.Session().Get(s1.Id)); rs1.Err != nil {
@@ -85,7 +80,6 @@ func TestSessionRemoveAll(t *testing.T) {
s1 := model.Session{}
s1.UserId = model.NewId()
- s1.TeamId = model.NewId()
Must(store.Session().Save(&s1))
if rs1 := (<-store.Session().Get(s1.Id)); rs1.Err != nil {
@@ -96,7 +90,7 @@ func TestSessionRemoveAll(t *testing.T) {
}
}
- Must(store.Session().RemoveAllSessionsForTeam(s1.TeamId))
+ Must(store.Session().RemoveAllSessions())
if rs2 := (<-store.Session().Get(s1.Id)); rs2.Err == nil {
t.Fatal("should have been removed")
@@ -108,7 +102,6 @@ func TestSessionRemoveByUser(t *testing.T) {
s1 := model.Session{}
s1.UserId = model.NewId()
- s1.TeamId = model.NewId()
Must(store.Session().Save(&s1))
if rs1 := (<-store.Session().Get(s1.Id)); rs1.Err != nil {
@@ -131,7 +124,6 @@ func TestSessionRemoveToken(t *testing.T) {
s1 := model.Session{}
s1.UserId = model.NewId()
- s1.TeamId = model.NewId()
Must(store.Session().Save(&s1))
if rs1 := (<-store.Session().Get(s1.Id)); rs1.Err != nil {
@@ -162,7 +154,6 @@ func TestSessionUpdateDeviceId(t *testing.T) {
s1 := model.Session{}
s1.UserId = model.NewId()
- s1.TeamId = model.NewId()
Must(store.Session().Save(&s1))
if rs1 := (<-store.Session().UpdateDeviceId(s1.Id, model.PUSH_NOTIFY_APPLE+":1234567890")); rs1.Err != nil {
@@ -171,7 +162,6 @@ func TestSessionUpdateDeviceId(t *testing.T) {
s2 := model.Session{}
s2.UserId = model.NewId()
- s2.TeamId = model.NewId()
Must(store.Session().Save(&s2))
if rs2 := (<-store.Session().UpdateDeviceId(s2.Id, model.PUSH_NOTIFY_APPLE+":1234567890")); rs2.Err != nil {
@@ -184,7 +174,6 @@ func TestSessionStoreUpdateLastActivityAt(t *testing.T) {
s1 := model.Session{}
s1.UserId = model.NewId()
- s1.TeamId = model.NewId()
Must(store.Session().Save(&s1))
if err := (<-store.Session().UpdateLastActivityAt(s1.Id, 1234567890)).Err; err != nil {
@@ -206,23 +195,14 @@ func TestSessionCount(t *testing.T) {
s1 := model.Session{}
s1.UserId = model.NewId()
- s1.TeamId = model.NewId()
s1.ExpiresAt = model.GetMillis() + 100000
Must(store.Session().Save(&s1))
- if r1 := <-store.Session().AnalyticsSessionCount(""); r1.Err != nil {
+ if r1 := <-store.Session().AnalyticsSessionCount(); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(int64) == 0 {
t.Fatal("should have at least 1 session")
}
}
-
- if r2 := <-store.Session().AnalyticsSessionCount(s1.TeamId); r2.Err != nil {
- t.Fatal(r2.Err)
- } else {
- if r2.Data.(int64) != 1 {
- t.Fatal("should have 1 session")
- }
- }
}
diff --git a/store/sql_store.go b/store/sql_store.go
index 8ff5da6f7..688e1b116 100644
--- a/store/sql_store.go
+++ b/store/sql_store.go
@@ -36,25 +36,26 @@ const (
)
type SqlStore struct {
- master *gorp.DbMap
- replicas []*gorp.DbMap
- team TeamStore
- channel ChannelStore
- post PostStore
- user UserStore
- audit AuditStore
- compliance ComplianceStore
- session SessionStore
- oauth OAuthStore
- system SystemStore
- webhook WebhookStore
- command CommandStore
- preference PreferenceStore
- license LicenseStore
-}
-
-func NewSqlStore() Store {
-
+ master *gorp.DbMap
+ replicas []*gorp.DbMap
+ team TeamStore
+ channel ChannelStore
+ post PostStore
+ user UserStore
+ audit AuditStore
+ compliance ComplianceStore
+ session SessionStore
+ oauth OAuthStore
+ system SystemStore
+ webhook WebhookStore
+ command CommandStore
+ preference PreferenceStore
+ license LicenseStore
+ recovery PasswordRecoveryStore
+ SchemaVersion string
+}
+
+func initConnection() *SqlStore {
sqlStore := &SqlStore{}
sqlStore.master = setupConnection("master", utils.Cfg.SqlSettings.DriverName,
@@ -75,25 +76,43 @@ func NewSqlStore() Store {
}
}
- schemaVersion := sqlStore.GetCurrentSchemaVersion()
+ sqlStore.SchemaVersion = sqlStore.GetCurrentSchemaVersion()
+ return sqlStore
+}
+
+func NewSqlStore() Store {
+
+ sqlStore := initConnection()
// If the version is already set then we are potentially in an 'upgrade needed' state
- if schemaVersion != "" {
+ if sqlStore.SchemaVersion != "" {
// Check to see if it's the most current database schema version
- if !model.IsCurrentVersion(schemaVersion) {
+ if !model.IsCurrentVersion(sqlStore.SchemaVersion) {
// If we are upgrading from the previous version then print a warning and continue
- if model.IsPreviousVersionsSupported(schemaVersion) {
- l4g.Warn(utils.T("store.sql.schema_out_of_date.warn"), schemaVersion)
+ if model.IsPreviousVersionsSupported(sqlStore.SchemaVersion) {
+ l4g.Warn(utils.T("store.sql.schema_out_of_date.warn"), sqlStore.SchemaVersion)
l4g.Warn(utils.T("store.sql.schema_upgrade_attempt.warn"), model.CurrentVersion)
} else {
// If this is an 'upgrade needed' state but the user is attempting to skip a version then halt the world
- l4g.Critical(utils.T("store.sql.schema_version.critical"), schemaVersion)
+ l4g.Critical(utils.T("store.sql.schema_version.critical"), sqlStore.SchemaVersion)
time.Sleep(time.Second)
- panic(fmt.Sprintf(utils.T("store.sql.schema_version.critical"), schemaVersion))
+ panic(fmt.Sprintf(utils.T("store.sql.schema_version.critical"), sqlStore.SchemaVersion))
}
}
}
+ // This is a speical case for upgrading the schema to the 3.0 user model
+ // ADDED for 3.0 REMOVE for 3.4
+ if sqlStore.SchemaVersion == "2.2.0" ||
+ sqlStore.SchemaVersion == "2.1.0" ||
+ sqlStore.SchemaVersion == "2.0.0" {
+ l4g.Critical("The database version of %v cannot be automatically upgraded to 3.0 schema", sqlStore.SchemaVersion)
+ l4g.Critical("You will need to run the command line tool './platform -upgrade_db_30'")
+ l4g.Critical("Please see 'http://www.mattermost.org/upgrade-to-3-0/' for more information on how to upgrade.")
+ time.Sleep(time.Second)
+ os.Exit(1)
+ }
+
sqlStore.team = NewSqlTeamStore(sqlStore)
sqlStore.channel = NewSqlChannelStore(sqlStore)
sqlStore.post = NewSqlPostStore(sqlStore)
@@ -107,10 +126,13 @@ func NewSqlStore() Store {
sqlStore.command = NewSqlCommandStore(sqlStore)
sqlStore.preference = NewSqlPreferenceStore(sqlStore)
sqlStore.license = NewSqlLicenseStore(sqlStore)
+ sqlStore.recovery = NewSqlPasswordRecoveryStore(sqlStore)
err := sqlStore.master.CreateTablesIfNotExists()
if err != nil {
l4g.Critical(utils.T("store.sql.creating_tables.critical"), err)
+ time.Sleep(time.Second)
+ os.Exit(1)
}
sqlStore.team.(*SqlTeamStore).UpgradeSchemaIfNeeded()
@@ -126,6 +148,7 @@ func NewSqlStore() Store {
sqlStore.command.(*SqlCommandStore).UpgradeSchemaIfNeeded()
sqlStore.preference.(*SqlPreferenceStore).UpgradeSchemaIfNeeded()
sqlStore.license.(*SqlLicenseStore).UpgradeSchemaIfNeeded()
+ sqlStore.recovery.(*SqlPasswordRecoveryStore).UpgradeSchemaIfNeeded()
sqlStore.team.(*SqlTeamStore).CreateIndexesIfNotExists()
sqlStore.channel.(*SqlChannelStore).CreateIndexesIfNotExists()
@@ -140,22 +163,44 @@ func NewSqlStore() Store {
sqlStore.command.(*SqlCommandStore).CreateIndexesIfNotExists()
sqlStore.preference.(*SqlPreferenceStore).CreateIndexesIfNotExists()
sqlStore.license.(*SqlLicenseStore).CreateIndexesIfNotExists()
+ sqlStore.recovery.(*SqlPasswordRecoveryStore).CreateIndexesIfNotExists()
sqlStore.preference.(*SqlPreferenceStore).DeleteUnusedFeatures()
- if model.IsPreviousVersionsSupported(schemaVersion) && !model.IsCurrentVersion(schemaVersion) {
+ if model.IsPreviousVersionsSupported(sqlStore.SchemaVersion) && !model.IsCurrentVersion(sqlStore.SchemaVersion) {
sqlStore.system.Update(&model.System{Name: "Version", Value: model.CurrentVersion})
+ sqlStore.SchemaVersion = model.CurrentVersion
l4g.Warn(utils.T("store.sql.upgraded.warn"), model.CurrentVersion)
}
- if schemaVersion == "" {
+ if sqlStore.SchemaVersion == "" {
sqlStore.system.Save(&model.System{Name: "Version", Value: model.CurrentVersion})
+ sqlStore.SchemaVersion = model.CurrentVersion
l4g.Info(utils.T("store.sql.schema_set.info"), model.CurrentVersion)
}
return sqlStore
}
+// ADDED for 3.0 REMOVE for 3.4
+// This is a speical case for upgrading the schema to the 3.0 user model
+func NewSqlStoreForUpgrade30() *SqlStore {
+ sqlStore := initConnection()
+
+ sqlStore.team = NewSqlTeamStore(sqlStore)
+ sqlStore.user = NewSqlUserStore(sqlStore)
+ sqlStore.system = NewSqlSystemStore(sqlStore)
+
+ err := sqlStore.master.CreateTablesIfNotExists()
+ if err != nil {
+ l4g.Critical(utils.T("store.sql.creating_tables.critical"), err)
+ time.Sleep(time.Second)
+ os.Exit(1)
+ }
+
+ return sqlStore
+}
+
func setupConnection(con_type string, driver string, dataSource string, maxIdle int, maxOpen int, trace bool) *gorp.DbMap {
db, err := dbsql.Open(driver, dataSource)
@@ -426,15 +471,24 @@ func (ss SqlStore) AlterColumnTypeIfExists(tableName string, columnName string,
return true
}
+func (ss SqlStore) CreateUniqueIndexIfNotExists(indexName string, tableName string, columnName string) {
+ ss.createIndexIfNotExists(indexName, tableName, columnName, INDEX_TYPE_DEFAULT, true)
+}
+
func (ss SqlStore) CreateIndexIfNotExists(indexName string, tableName string, columnName string) {
- ss.createIndexIfNotExists(indexName, tableName, columnName, INDEX_TYPE_DEFAULT)
+ ss.createIndexIfNotExists(indexName, tableName, columnName, INDEX_TYPE_DEFAULT, false)
}
func (ss SqlStore) CreateFullTextIndexIfNotExists(indexName string, tableName string, columnName string) {
- ss.createIndexIfNotExists(indexName, tableName, columnName, INDEX_TYPE_FULL_TEXT)
+ ss.createIndexIfNotExists(indexName, tableName, columnName, INDEX_TYPE_FULL_TEXT, false)
}
-func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, columnName string, indexType string) {
+func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, columnName string, indexType string, unique bool) {
+
+ uniqueStr := ""
+ if unique {
+ uniqueStr = "UNIQUE "
+ }
if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
_, err := ss.GetMaster().SelectStr("SELECT $1::regclass", indexName)
@@ -447,7 +501,7 @@ func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, co
if indexType == INDEX_TYPE_FULL_TEXT {
query = "CREATE INDEX " + indexName + " ON " + tableName + " USING gin(to_tsvector('english', " + columnName + "))"
} else {
- query = "CREATE INDEX " + indexName + " ON " + tableName + " (" + columnName + ")"
+ query = "CREATE " + uniqueStr + "INDEX " + indexName + " ON " + tableName + " (" + columnName + ")"
}
_, err = ss.GetMaster().Exec(query)
@@ -474,7 +528,7 @@ func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, co
fullTextIndex = " FULLTEXT "
}
- _, err = ss.GetMaster().Exec("CREATE " + fullTextIndex + " INDEX " + indexName + " ON " + tableName + " (" + columnName + ")")
+ _, err = ss.GetMaster().Exec("CREATE " + uniqueStr + fullTextIndex + " INDEX " + indexName + " ON " + tableName + " (" + columnName + ")")
if err != nil {
l4g.Critical(utils.T("store.sql.create_index.critical"), err)
time.Sleep(time.Second)
@@ -623,6 +677,14 @@ func (ss SqlStore) License() LicenseStore {
return ss.license
}
+func (ss SqlStore) PasswordRecovery() PasswordRecoveryStore {
+ return ss.recovery
+}
+
+func (ss SqlStore) DropAllTables() {
+ ss.master.TruncateTables()
+}
+
type mattermConverter struct{}
func (me mattermConverter) ToDb(val interface{}) (interface{}, error) {
diff --git a/store/sql_team_store.go b/store/sql_team_store.go
index 8a345bfa0..c17a45d97 100644
--- a/store/sql_team_store.go
+++ b/store/sql_team_store.go
@@ -6,7 +6,6 @@ package store
import (
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
- "strings"
)
type SqlTeamStore struct {
@@ -25,6 +24,11 @@ func NewSqlTeamStore(sqlStore *SqlStore) TeamStore {
table.ColMap("CompanyName").SetMaxSize(64)
table.ColMap("AllowedDomains").SetMaxSize(500)
table.ColMap("InviteId").SetMaxSize(32)
+
+ tablem := db.AddTableWithName(model.TeamMember{}, "TeamMembers").SetKeys(false, "TeamId", "UserId")
+ tablem.ColMap("TeamId").SetMaxSize(26)
+ tablem.ColMap("UserId").SetMaxSize(26)
+ tablem.ColMap("Roles").SetMaxSize(64)
}
return s
@@ -36,6 +40,9 @@ func (s SqlTeamStore) UpgradeSchemaIfNeeded() {
func (s SqlTeamStore) CreateIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_teams_name", "Teams", "Name")
s.CreateIndexIfNotExists("idx_teams_invite_id", "Teams", "InviteId")
+
+ s.CreateIndexIfNotExists("idx_teammembers_team_id", "TeamMembers", "TeamId")
+ s.CreateIndexIfNotExists("idx_teammembers_user_id", "TeamMembers", "UserId")
}
func (s SqlTeamStore) Save(team *model.Team) StoreChannel {
@@ -218,17 +225,15 @@ func (s SqlTeamStore) GetByName(name string) StoreChannel {
return storeChannel
}
-func (s SqlTeamStore) GetTeamsForEmail(email string) StoreChannel {
+func (s SqlTeamStore) GetAll() StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
- email = strings.ToLower(email)
-
var data []*model.Team
- if _, err := s.GetReplica().Select(&data, "SELECT Teams.* FROM Teams, Users WHERE Teams.Id = Users.TeamId AND Users.Email = :Email", map[string]interface{}{"Email": email}); err != nil {
- result.Err = model.NewLocAppError("SqlTeamStore.GetTeamsForEmail", "store.sql_team.get_teams_for_email.app_error", nil, "email="+email+", "+err.Error())
+ if _, err := s.GetReplica().Select(&data, "SELECT * FROM Teams"); err != nil {
+ result.Err = model.NewLocAppError("SqlTeamStore.GetAllTeams", "store.sql_team.get_all.app_error", nil, err.Error())
}
for _, team := range data {
@@ -246,15 +251,15 @@ func (s SqlTeamStore) GetTeamsForEmail(email string) StoreChannel {
return storeChannel
}
-func (s SqlTeamStore) GetAll() StoreChannel {
+func (s SqlTeamStore) GetTeamsByUserId(userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
var data []*model.Team
- if _, err := s.GetReplica().Select(&data, "SELECT * FROM Teams"); err != nil {
- result.Err = model.NewLocAppError("SqlTeamStore.GetAllTeams", "store.sql_team.get_all.app_error", nil, err.Error())
+ if _, err := s.GetReplica().Select(&data, "SELECT Teams.* FROM Teams, TeamMembers WHERE TeamMembers.TeamId = Teams.Id AND TeamMembers.UserId = :UserId", map[string]interface{}{"UserId": userId}); err != nil {
+ result.Err = model.NewLocAppError("SqlTeamStore.GetTeamsByUserId", "store.sql_team.get_all.app_error", nil, err.Error())
}
for _, team := range data {
@@ -278,10 +283,10 @@ func (s SqlTeamStore) GetAllTeamListing() StoreChannel {
go func() {
result := StoreResult{}
- query := "SELECT * FROM Teams WHERE AllowTeamListing = 1"
+ query := "SELECT * FROM Teams WHERE AllowOpenInvite = 1"
if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
- query = "SELECT * FROM Teams WHERE AllowTeamListing = true"
+ query = "SELECT * FROM Teams WHERE AllowOpenInvite = true"
}
var data []*model.Team
@@ -339,3 +344,165 @@ func (s SqlTeamStore) AnalyticsTeamCount() StoreChannel {
return storeChannel
}
+
+func (s SqlTeamStore) SaveMember(member *model.TeamMember) StoreChannel {
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ if result.Err = member.IsValid(); result.Err != nil {
+ storeChannel <- result
+ close(storeChannel)
+ return
+ }
+
+ if count, err := s.GetMaster().SelectInt("SELECT COUNT(0) FROM TeamMembers WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": member.TeamId}); err != nil {
+ result.Err = model.NewLocAppError("SqlUserStore.Save", "store.sql_user.save.member_count.app_error", nil, "teamId="+member.TeamId+", "+err.Error())
+ storeChannel <- result
+ close(storeChannel)
+ return
+ } else if int(count) > utils.Cfg.TeamSettings.MaxUsersPerTeam {
+ result.Err = model.NewLocAppError("SqlUserStore.Save", "store.sql_user.save.max_accounts.app_error", nil, "teamId="+member.TeamId)
+ storeChannel <- result
+ close(storeChannel)
+ return
+ }
+
+ if err := s.GetMaster().Insert(member); err != nil {
+ if IsUniqueConstraintError(err.Error(), "TeamId", "teammembers_pkey") {
+ result.Err = model.NewLocAppError("SqlTeamStore.SaveMember", "store.sql_team.save_member.exists.app_error", nil, "team_id="+member.TeamId+", user_id="+member.UserId+", "+err.Error())
+ } else {
+ result.Err = model.NewLocAppError("SqlTeamStore.SaveMember", "store.sql_team.save_member.save.app_error", nil, "team_id="+member.TeamId+", user_id="+member.UserId+", "+err.Error())
+ }
+ } else {
+ result.Data = member
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
+func (s SqlTeamStore) UpdateMember(member *model.TeamMember) StoreChannel {
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ if result.Err = member.IsValid(); result.Err != nil {
+ storeChannel <- result
+ close(storeChannel)
+ return
+ }
+
+ if _, err := s.GetMaster().Update(member); err != nil {
+ result.Err = model.NewLocAppError("SqlTeamStore.UpdateMember", "store.sql_team.save_member.save.app_error", nil, err.Error())
+ } else {
+ result.Data = member
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
+func (s SqlTeamStore) GetMembers(teamId string) StoreChannel {
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ var members []*model.TeamMember
+ _, err := s.GetReplica().Select(&members, "SELECT * FROM TeamMembers WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": teamId})
+ if err != nil {
+ result.Err = model.NewLocAppError("SqlTeamStore.GetMembers", "store.sql_team.get_members.app_error", nil, "teamId="+teamId+" "+err.Error())
+ } else {
+ result.Data = members
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
+func (s SqlTeamStore) GetTeamsForUser(userId string) StoreChannel {
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ var members []*model.TeamMember
+ _, err := s.GetReplica().Select(&members, "SELECT * FROM TeamMembers WHERE UserId = :UserId", map[string]interface{}{"UserId": userId})
+ if err != nil {
+ result.Err = model.NewLocAppError("SqlTeamStore.GetMembers", "store.sql_team.get_members.app_error", nil, "userId="+userId+" "+err.Error())
+ } else {
+ result.Data = members
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
+func (s SqlTeamStore) RemoveMember(teamId string, userId string) StoreChannel {
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ _, err := s.GetMaster().Exec("DELETE FROM TeamMembers WHERE TeamId = :TeamId AND UserId = :UserId", map[string]interface{}{"TeamId": teamId, "UserId": userId})
+ if err != nil {
+ result.Err = model.NewLocAppError("SqlChannelStore.RemoveMember", "store.sql_team.remove_member.app_error", nil, "team_id="+teamId+", user_id="+userId+", "+err.Error())
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
+func (s SqlTeamStore) RemoveAllMembersByTeam(teamId string) StoreChannel {
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ _, err := s.GetMaster().Exec("DELETE FROM TeamMembers WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": teamId})
+ if err != nil {
+ result.Err = model.NewLocAppError("SqlChannelStore.RemoveMember", "store.sql_team.remove_member.app_error", nil, "team_id="+teamId+", "+err.Error())
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
+func (s SqlTeamStore) RemoveAllMembersByUser(userId string) StoreChannel {
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ _, err := s.GetMaster().Exec("DELETE FROM TeamMembers WHERE UserId = :UserId", map[string]interface{}{"UserId": userId})
+ if err != nil {
+ result.Err = model.NewLocAppError("SqlChannelStore.RemoveMember", "store.sql_team.remove_member.app_error", nil, "user_id="+userId+", "+err.Error())
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
diff --git a/store/sql_team_store_test.go b/store/sql_team_store_test.go
index 743ef053f..d5ee15bc6 100644
--- a/store/sql_team_store_test.go
+++ b/store/sql_team_store_test.go
@@ -14,7 +14,7 @@ func TestTeamStoreSave(t *testing.T) {
o1 := model.Team{}
o1.DisplayName = "DisplayName"
- o1.Name = "a" + model.NewId() + "b"
+ o1.Name = "z-z-z" + model.NewId() + "b"
o1.Email = model.NewId() + "@nowhere.com"
o1.Type = model.TEAM_OPEN
@@ -37,7 +37,7 @@ func TestTeamStoreUpdate(t *testing.T) {
o1 := model.Team{}
o1.DisplayName = "DisplayName"
- o1.Name = "a" + model.NewId() + "b"
+ o1.Name = "z-z-z" + model.NewId() + "b"
o1.Email = model.NewId() + "@nowhere.com"
o1.Type = model.TEAM_OPEN
if err := (<-store.Team().Save(&o1)).Err; err != nil {
@@ -66,7 +66,7 @@ func TestTeamStoreUpdateDisplayName(t *testing.T) {
o1 := &model.Team{}
o1.DisplayName = "Display Name"
- o1.Name = "a" + model.NewId() + "b"
+ o1.Name = "z-z-z" + model.NewId() + "b"
o1.Email = model.NewId() + "@nowhere.com"
o1.Type = model.TEAM_OPEN
o1 = (<-store.Team().Save(o1)).Data.(*model.Team)
@@ -88,7 +88,7 @@ func TestTeamStoreGet(t *testing.T) {
o1 := model.Team{}
o1.DisplayName = "DisplayName"
- o1.Name = "a" + model.NewId() + "b"
+ o1.Name = "z-z-z" + model.NewId() + "b"
o1.Email = model.NewId() + "@nowhere.com"
o1.Type = model.TEAM_OPEN
Must(store.Team().Save(&o1))
@@ -111,7 +111,7 @@ func TestTeamStoreGetByName(t *testing.T) {
o1 := model.Team{}
o1.DisplayName = "DisplayName"
- o1.Name = "a" + model.NewId() + "b"
+ o1.Name = "z-z-z" + model.NewId() + "b"
o1.Email = model.NewId() + "@nowhere.com"
o1.Type = model.TEAM_OPEN
@@ -137,7 +137,7 @@ func TestTeamStoreGetByIniviteId(t *testing.T) {
o1 := model.Team{}
o1.DisplayName = "DisplayName"
- o1.Name = "a" + model.NewId() + "b"
+ o1.Name = "z-z-z" + model.NewId() + "b"
o1.Email = model.NewId() + "@nowhere.com"
o1.Type = model.TEAM_OPEN
o1.InviteId = model.NewId()
@@ -180,33 +180,32 @@ func TestTeamStoreGetByIniviteId(t *testing.T) {
}
}
-func TestTeamStoreGetForEmail(t *testing.T) {
+func TestTeamStoreByUserId(t *testing.T) {
Setup()
- o1 := model.Team{}
+ o1 := &model.Team{}
o1.DisplayName = "DisplayName"
- o1.Name = "a" + model.NewId() + "b"
+ o1.Name = "z-z-z" + model.NewId() + "b"
o1.Email = model.NewId() + "@nowhere.com"
o1.Type = model.TEAM_OPEN
- Must(store.Team().Save(&o1))
+ o1.InviteId = model.NewId()
+ o1 = Must(store.Team().Save(o1)).(*model.Team)
- u1 := model.User{}
- u1.TeamId = o1.Id
- u1.Email = model.NewId()
- Must(store.User().Save(&u1))
+ m1 := &model.TeamMember{TeamId: o1.Id, UserId: model.NewId()}
+ Must(store.Team().SaveMember(m1))
- if r1 := <-store.Team().GetTeamsForEmail(u1.Email); r1.Err != nil {
+ if r1 := <-store.Team().GetTeamsByUserId(m1.UserId); r1.Err != nil {
t.Fatal(r1.Err)
} else {
teams := r1.Data.([]*model.Team)
+ if len(teams) == 0 {
+ t.Fatal("Should return a team")
+ }
if teams[0].Id != o1.Id {
- t.Fatal("failed to lookup by email")
+ t.Fatal("should be a member")
}
- }
- if r1 := <-store.Team().GetTeamsForEmail("missing"); r1.Err != nil {
- t.Fatal(r1.Err)
}
}
@@ -215,10 +214,10 @@ func TestAllTeamListing(t *testing.T) {
o1 := model.Team{}
o1.DisplayName = "DisplayName"
- o1.Name = "a" + model.NewId() + "b"
+ o1.Name = "z-z-z" + model.NewId() + "b"
o1.Email = model.NewId() + "@nowhere.com"
o1.Type = model.TEAM_OPEN
- o1.AllowTeamListing = true
+ o1.AllowOpenInvite = true
Must(store.Team().Save(&o1))
o2 := model.Team{}
@@ -244,10 +243,10 @@ func TestDelete(t *testing.T) {
o1 := model.Team{}
o1.DisplayName = "DisplayName"
- o1.Name = "a" + model.NewId() + "b"
+ o1.Name = "z-z-z" + model.NewId() + "b"
o1.Email = model.NewId() + "@nowhere.com"
o1.Type = model.TEAM_OPEN
- o1.AllowTeamListing = true
+ o1.AllowOpenInvite = true
Must(store.Team().Save(&o1))
o2 := model.Team{}
@@ -267,10 +266,10 @@ func TestTeamCount(t *testing.T) {
o1 := model.Team{}
o1.DisplayName = "DisplayName"
- o1.Name = "a" + model.NewId() + "b"
+ o1.Name = "z-z-z" + model.NewId() + "b"
o1.Email = model.NewId() + "@nowhere.com"
o1.Type = model.TEAM_OPEN
- o1.AllowTeamListing = true
+ o1.AllowOpenInvite = true
Must(store.Team().Save(&o1))
if r1 := <-store.Team().AnalyticsTeamCount(); r1.Err != nil {
@@ -281,3 +280,126 @@ func TestTeamCount(t *testing.T) {
}
}
}
+
+func TestTeamMembers(t *testing.T) {
+ Setup()
+
+ teamId1 := model.NewId()
+ teamId2 := model.NewId()
+
+ m1 := &model.TeamMember{TeamId: teamId1, UserId: model.NewId()}
+ m2 := &model.TeamMember{TeamId: teamId1, UserId: model.NewId()}
+ m3 := &model.TeamMember{TeamId: teamId2, UserId: model.NewId()}
+
+ if r1 := <-store.Team().SaveMember(m1); r1.Err != nil {
+ t.Fatal(r1.Err)
+ }
+
+ Must(store.Team().SaveMember(m2))
+ Must(store.Team().SaveMember(m3))
+
+ if r1 := <-store.Team().GetMembers(teamId1); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ ms := r1.Data.([]*model.TeamMember)
+
+ if len(ms) != 2 {
+ t.Fatal()
+ }
+ }
+
+ if r1 := <-store.Team().GetMembers(teamId2); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ ms := r1.Data.([]*model.TeamMember)
+
+ if len(ms) != 1 {
+ t.Fatal()
+ }
+
+ if ms[0].UserId != m3.UserId {
+ t.Fatal()
+
+ }
+ }
+
+ if r1 := <-store.Team().GetTeamsForUser(m1.UserId); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ ms := r1.Data.([]*model.TeamMember)
+
+ if len(ms) != 1 {
+ t.Fatal()
+ }
+
+ if ms[0].TeamId != m1.TeamId {
+ t.Fatal()
+
+ }
+ }
+
+ if r1 := <-store.Team().RemoveMember(teamId1, m1.UserId); r1.Err != nil {
+ t.Fatal(r1.Err)
+ }
+
+ if r1 := <-store.Team().GetMembers(teamId1); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ ms := r1.Data.([]*model.TeamMember)
+
+ if len(ms) != 1 {
+ t.Fatal()
+ }
+
+ if ms[0].UserId != m2.UserId {
+ t.Fatal()
+
+ }
+ }
+
+ Must(store.Team().SaveMember(m1))
+
+ if r1 := <-store.Team().RemoveAllMembersByTeam(teamId1); r1.Err != nil {
+ t.Fatal(r1.Err)
+ }
+
+ if r1 := <-store.Team().GetMembers(teamId1); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ ms := r1.Data.([]*model.TeamMember)
+
+ if len(ms) != 0 {
+ t.Fatal()
+ }
+ }
+
+ uid := model.NewId()
+ m4 := &model.TeamMember{TeamId: teamId1, UserId: uid}
+ m5 := &model.TeamMember{TeamId: teamId2, UserId: uid}
+ Must(store.Team().SaveMember(m4))
+ Must(store.Team().SaveMember(m5))
+
+ if r1 := <-store.Team().GetTeamsForUser(uid); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ ms := r1.Data.([]*model.TeamMember)
+
+ if len(ms) != 2 {
+ t.Fatal()
+ }
+ }
+
+ if r1 := <-store.Team().RemoveAllMembersByUser(uid); r1.Err != nil {
+ t.Fatal(r1.Err)
+ }
+
+ if r1 := <-store.Team().GetTeamsForUser(m1.UserId); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ ms := r1.Data.([]*model.TeamMember)
+
+ if len(ms) != 0 {
+ t.Fatal()
+ }
+ }
+}
diff --git a/store/sql_user_store.go b/store/sql_user_store.go
index 2b52dfbd7..ea83458e9 100644
--- a/store/sql_user_store.go
+++ b/store/sql_user_store.go
@@ -6,9 +6,10 @@ package store
import (
"database/sql"
"fmt"
- "github.com/mattermost/platform/model"
- "github.com/mattermost/platform/utils"
+ "strconv"
"strings"
+
+ "github.com/mattermost/platform/model"
)
const (
@@ -26,12 +27,11 @@ func NewSqlUserStore(sqlStore *SqlStore) UserStore {
for _, db := range sqlStore.GetAllConns() {
table := db.AddTableWithName(model.User{}, "Users").SetKeys(false, "Id")
table.ColMap("Id").SetMaxSize(26)
- table.ColMap("TeamId").SetMaxSize(26)
- table.ColMap("Username").SetMaxSize(64)
+ table.ColMap("Username").SetMaxSize(64).SetUnique(true)
table.ColMap("Password").SetMaxSize(128)
table.ColMap("AuthData").SetMaxSize(128)
table.ColMap("AuthService").SetMaxSize(32)
- table.ColMap("Email").SetMaxSize(128)
+ table.ColMap("Email").SetMaxSize(128).SetUnique(true)
table.ColMap("Nickname").SetMaxSize(64)
table.ColMap("FirstName").SetMaxSize(64)
table.ColMap("LastName").SetMaxSize(64)
@@ -41,8 +41,6 @@ func NewSqlUserStore(sqlStore *SqlStore) UserStore {
table.ColMap("ThemeProps").SetMaxSize(2000)
table.ColMap("Locale").SetMaxSize(5)
table.ColMap("MfaSecret").SetMaxSize(128)
- table.SetUniqueTogether("Email", "TeamId")
- table.SetUniqueTogether("Username", "TeamId")
}
return us
@@ -51,13 +49,9 @@ func NewSqlUserStore(sqlStore *SqlStore) UserStore {
func (us SqlUserStore) UpgradeSchemaIfNeeded() {
// ADDED for 2.0 REMOVE for 2.4
us.CreateColumnIfNotExists("Users", "Locale", "varchar(5)", "character varying(5)", model.DEFAULT_LOCALE)
- // ADDED for 2.2 REMOVE for 2.6
- us.CreateColumnIfNotExists("Users", "MfaActive", "tinyint(1)", "boolean", "0")
- us.CreateColumnIfNotExists("Users", "MfaSecret", "varchar(128)", "character varying(128)", "")
}
func (us SqlUserStore) CreateIndexesIfNotExists() {
- us.CreateIndexIfNotExists("idx_users_team_id", "Users", "TeamId")
us.CreateIndexIfNotExists("idx_users_email", "Users", "Email")
}
@@ -82,18 +76,6 @@ func (us SqlUserStore) Save(user *model.User) StoreChannel {
return
}
- if count, err := us.GetMaster().SelectInt("SELECT COUNT(0) FROM Users WHERE TeamId = :TeamId AND DeleteAt = 0", map[string]interface{}{"TeamId": user.TeamId}); err != nil {
- result.Err = model.NewLocAppError("SqlUserStore.Save", "store.sql_user.save.member_count.app_error", nil, "teamId="+user.TeamId+", "+err.Error())
- storeChannel <- result
- close(storeChannel)
- return
- } else if int(count) > utils.Cfg.TeamSettings.MaxUsersPerTeam {
- result.Err = model.NewLocAppError("SqlUserStore.Save", "store.sql_user.save.max_accounts.app_error", nil, "teamId="+user.TeamId)
- storeChannel <- result
- close(storeChannel)
- return
- }
-
if err := us.GetMaster().Insert(user); err != nil {
if IsUniqueConstraintError(err.Error(), "Email", "users_email_teamid_key") {
result.Err = model.NewLocAppError("SqlUserStore.Save", "store.sql_user.save.email_exists.app_error", nil, "user_id="+user.Id+", "+err.Error())
@@ -140,7 +122,6 @@ func (us SqlUserStore) Update(user *model.User, allowActiveUpdate bool) StoreCha
user.Password = oldUser.Password
user.LastPasswordUpdate = oldUser.LastPasswordUpdate
user.LastPictureUpdate = oldUser.LastPictureUpdate
- user.TeamId = oldUser.TeamId
user.LastActivityAt = oldUser.LastActivityAt
user.LastPingAt = oldUser.LastPingAt
user.EmailVerified = oldUser.EmailVerified
@@ -153,7 +134,7 @@ func (us SqlUserStore) Update(user *model.User, allowActiveUpdate bool) StoreCha
user.DeleteAt = oldUser.DeleteAt
}
- if user.IsSSOUser() {
+ if user.IsOAuthUser() {
user.Email = oldUser.Email
} else if !user.IsLDAPUser() && user.Email != oldUser.Email {
user.EmailVerified = false
@@ -421,13 +402,76 @@ func (us SqlUserStore) Get(id string) StoreChannel {
return storeChannel
}
+func (us SqlUserStore) GetAll() StoreChannel {
+
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ var data []*model.User
+ if _, err := us.GetReplica().Select(&data, "SELECT * FROM Users"); err != nil {
+ result.Err = model.NewLocAppError("SqlUserStore.GetAll", "store.sql_user.get.app_error", nil, err.Error())
+ }
+
+ result.Data = data
+
+ storeChannel <- result
+ close(storeChannel)
+
+ }()
+
+ return storeChannel
+}
+
+func (s SqlUserStore) GetEtagForDirectProfiles(userId string) StoreChannel {
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ updateAt, err := s.GetReplica().SelectInt(`
+ SELECT
+ UpdateAt
+ FROM
+ Users
+ WHERE
+ Id IN (SELECT DISTINCT
+ UserId
+ FROM
+ ChannelMembers
+ WHERE
+ ChannelMembers.UserId != :UserId
+ AND ChannelMembers.ChannelId IN (SELECT
+ Channels.Id
+ FROM
+ Channels,
+ ChannelMembers
+ WHERE
+ Channels.Type = 'D'
+ AND Channels.Id = ChannelMembers.ChannelId
+ AND ChannelMembers.UserId = :UserId))
+ `, map[string]interface{}{"UserId": userId})
+ if err != nil {
+ result.Data = fmt.Sprintf("%v.%v", model.CurrentVersion, model.GetMillis())
+ } else {
+ result.Data = fmt.Sprintf("%v.%v", model.CurrentVersion, updateAt)
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
func (s SqlUserStore) GetEtagForProfiles(teamId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
- updateAt, err := s.GetReplica().SelectInt("SELECT UpdateAt FROM Users WHERE TeamId = :TeamId ORDER BY UpdateAt DESC LIMIT 1", map[string]interface{}{"TeamId": teamId})
+ updateAt, err := s.GetReplica().SelectInt("SELECT UpdateAt FROM Users, TeamMembers WHERE TeamMembers.TeamId = :TeamId AND Users.Id = TeamMembers.UserId ORDER BY UpdateAt DESC LIMIT 1", map[string]interface{}{"TeamId": teamId})
if err != nil {
result.Data = fmt.Sprintf("%v.%v", model.CurrentVersion, model.GetMillis())
} else {
@@ -450,7 +494,7 @@ func (us SqlUserStore) GetProfiles(teamId string) StoreChannel {
var users []*model.User
- if _, err := us.GetReplica().Select(&users, "SELECT * FROM Users WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": teamId}); err != nil {
+ if _, err := us.GetReplica().Select(&users, "SELECT Users.* FROM Users, TeamMembers WHERE TeamMembers.TeamId = :TeamId AND Users.Id = TeamMembers.UserId", map[string]interface{}{"TeamId": teamId}); err != nil {
result.Err = model.NewLocAppError("SqlUserStore.GetProfiles", "store.sql_user.get_profiles.app_error", nil, err.Error())
} else {
@@ -472,6 +516,99 @@ func (us SqlUserStore) GetProfiles(teamId string) StoreChannel {
return storeChannel
}
+func (us SqlUserStore) GetDirectProfiles(userId string) StoreChannel {
+
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ var users []*model.User
+
+ if _, err := us.GetReplica().Select(&users, `
+ SELECT
+ Users.*
+ FROM
+ Users
+ WHERE
+ Id IN (SELECT DISTINCT
+ UserId
+ FROM
+ ChannelMembers
+ WHERE
+ ChannelMembers.UserId != :UserId
+ AND ChannelMembers.ChannelId IN (SELECT
+ Channels.Id
+ FROM
+ Channels,
+ ChannelMembers
+ WHERE
+ Channels.Type = 'D'
+ AND Channels.Id = ChannelMembers.ChannelId
+ AND ChannelMembers.UserId = :UserId))`, map[string]interface{}{"UserId": userId}); err != nil {
+ result.Err = model.NewLocAppError("SqlUserStore.GetDirectProfiles", "store.sql_user.get_profiles.app_error", nil, err.Error())
+ } else {
+
+ userMap := make(map[string]*model.User)
+
+ for _, u := range users {
+ u.Password = ""
+ u.AuthData = ""
+ userMap[u.Id] = u
+ }
+
+ result.Data = userMap
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
+func (us SqlUserStore) GetProfileByIds(userIds []string) StoreChannel {
+
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ var users []*model.User
+ props := make(map[string]interface{})
+ idQuery := ""
+
+ for index, userId := range userIds {
+ if len(idQuery) > 0 {
+ idQuery += ", "
+ }
+
+ props["userId"+strconv.Itoa(index)] = userId
+ idQuery += ":userId" + strconv.Itoa(index)
+ }
+
+ if _, err := us.GetReplica().Select(&users, "SELECT * FROM Users WHERE Users.Id IN ("+idQuery+")", props); err != nil {
+ result.Err = model.NewLocAppError("SqlUserStore.GetProfileByIds", "store.sql_user.get_profiles.app_error", nil, err.Error())
+ } else {
+
+ userMap := make(map[string]*model.User)
+
+ for _, u := range users {
+ u.Password = ""
+ u.AuthData = ""
+ userMap[u.Id] = u
+ }
+
+ result.Data = userMap
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
func (us SqlUserStore) GetSystemAdminProfiles() StoreChannel {
storeChannel := make(StoreChannel)
@@ -503,7 +640,7 @@ func (us SqlUserStore) GetSystemAdminProfiles() StoreChannel {
return storeChannel
}
-func (us SqlUserStore) GetByEmail(teamId string, email string) StoreChannel {
+func (us SqlUserStore) GetByEmail(email string) StoreChannel {
storeChannel := make(StoreChannel)
@@ -514,8 +651,8 @@ func (us SqlUserStore) GetByEmail(teamId string, email string) StoreChannel {
user := model.User{}
- if err := us.GetReplica().SelectOne(&user, "SELECT * FROM Users WHERE TeamId = :TeamId AND Email = :Email", map[string]interface{}{"TeamId": teamId, "Email": email}); err != nil {
- result.Err = model.NewLocAppError("SqlUserStore.GetByEmail", MISSING_ACCOUNT_ERROR, nil, "teamId="+teamId+", email="+email+", "+err.Error())
+ if err := us.GetReplica().SelectOne(&user, "SELECT * FROM Users WHERE Email = :Email", map[string]interface{}{"Email": email}); err != nil {
+ result.Err = model.NewLocAppError("SqlUserStore.GetByEmail", MISSING_ACCOUNT_ERROR, nil, "email="+email+", "+err.Error())
}
result.Data = &user
@@ -527,7 +664,7 @@ func (us SqlUserStore) GetByEmail(teamId string, email string) StoreChannel {
return storeChannel
}
-func (us SqlUserStore) GetByAuth(teamId string, authData string, authService string) StoreChannel {
+func (us SqlUserStore) GetByAuth(authData string, authService string) StoreChannel {
storeChannel := make(StoreChannel)
@@ -536,11 +673,11 @@ func (us SqlUserStore) GetByAuth(teamId string, authData string, authService str
user := model.User{}
- if err := us.GetReplica().SelectOne(&user, "SELECT * FROM Users WHERE TeamId = :TeamId AND AuthData = :AuthData AND AuthService = :AuthService", map[string]interface{}{"TeamId": teamId, "AuthData": authData, "AuthService": authService}); err != nil {
+ if err := us.GetReplica().SelectOne(&user, "SELECT * FROM Users WHERE AuthData = :AuthData AND AuthService = :AuthService", map[string]interface{}{"AuthData": authData, "AuthService": authService}); err != nil {
if err == sql.ErrNoRows {
- result.Err = model.NewLocAppError("SqlUserStore.GetByAuth", MISSING_AUTH_ACCOUNT_ERROR, nil, "teamId="+teamId+", authData="+authData+", authService="+authService+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.GetByAuth", MISSING_AUTH_ACCOUNT_ERROR, nil, "authData="+authData+", authService="+authService+", "+err.Error())
} else {
- result.Err = model.NewLocAppError("SqlUserStore.GetByAuth", "store.sql_user.get_by_auth.other.app_error", nil, "teamId="+teamId+", authData="+authData+", authService="+authService+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.GetByAuth", "store.sql_user.get_by_auth.other.app_error", nil, "authData="+authData+", authService="+authService+", "+err.Error())
}
}
@@ -553,7 +690,7 @@ func (us SqlUserStore) GetByAuth(teamId string, authData string, authService str
return storeChannel
}
-func (us SqlUserStore) GetByUsername(teamId string, username string) StoreChannel {
+func (us SqlUserStore) GetByUsername(username string) StoreChannel {
storeChannel := make(StoreChannel)
@@ -562,9 +699,9 @@ func (us SqlUserStore) GetByUsername(teamId string, username string) StoreChanne
user := model.User{}
- if err := us.GetReplica().SelectOne(&user, "SELECT * FROM Users WHERE TeamId = :TeamId AND Username = :Username", map[string]interface{}{"TeamId": teamId, "Username": username}); err != nil {
+ if err := us.GetReplica().SelectOne(&user, "SELECT * FROM Users WHERE Username = :Username", map[string]interface{}{"Username": username}); err != nil {
result.Err = model.NewLocAppError("SqlUserStore.GetByUsername", "store.sql_user.get_by_username.app_error",
- nil, "teamId="+teamId+", username="+username+", "+err.Error())
+ nil, err.Error())
}
result.Data = &user
@@ -604,8 +741,8 @@ func (us SqlUserStore) GetForExport(teamId string) StoreChannel {
var users []*model.User
- if _, err := us.GetReplica().Select(&users, "SELECT * FROM Users WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": teamId}); err != nil {
- result.Err = model.NewLocAppError("SqlUserStore.GetProfiles", "store.sql_user.get_for_export.app_error", nil, err.Error())
+ if _, err := us.GetReplica().Select(&users, "SELECT Users.* FROM Users, TeamMembers WHERE TeamMembers.TeamId = :TeamId AND Users.Id = TeamMembers.UserId", map[string]interface{}{"TeamId": teamId}); err != nil {
+ result.Err = model.NewLocAppError("SqlUserStore.GetForExport", "store.sql_user.get_for_export.app_error", nil, err.Error())
} else {
for _, u := range users {
u.Password = ""
@@ -690,7 +827,7 @@ func (us SqlUserStore) AnalyticsUniqueUserCount(teamId string) StoreChannel {
query := "SELECT COUNT(DISTINCT Email) FROM Users"
if len(teamId) > 0 {
- query += " WHERE TeamId = :TeamId"
+ query += ", TeamMembers WHERE TeamMembers.TeamId = :TeamId AND Users.Id = TeamMembers.UserId"
}
v, err := us.GetReplica().SelectInt(query, map[string]interface{}{"TeamId": teamId})
diff --git a/store/sql_user_store_test.go b/store/sql_user_store_test.go
index dcd2440ac..2d17c5888 100644
--- a/store/sql_user_store_test.go
+++ b/store/sql_user_store_test.go
@@ -13,15 +13,18 @@ import (
func TestUserStoreSave(t *testing.T) {
Setup()
+ teamId := model.NewId()
+
u1 := model.User{}
u1.Email = model.NewId()
u1.Username = model.NewId()
- u1.TeamId = model.NewId()
if err := (<-store.User().Save(&u1)).Err; err != nil {
t.Fatal("couldn't save user", err)
}
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
+
if err := (<-store.User().Save(&u1)).Err; err == nil {
t.Fatal("shouldn't be able to update user from save")
}
@@ -49,37 +52,44 @@ func TestUserStoreSave(t *testing.T) {
if err := (<-store.User().Save(&u1)).Err; err != nil {
t.Fatal("couldn't save item", err)
}
+
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
}
u1.Id = ""
u1.Email = model.NewId()
u1.Username = model.NewId()
- if err := (<-store.User().Save(&u1)).Err; err == nil {
- t.Fatal("should be the limit", err)
+ if err := (<-store.User().Save(&u1)).Err; err != nil {
+ t.Fatal("couldn't save item", err)
}
+
+ if err := (<-store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id})).Err; err == nil {
+ t.Fatal("should be the limit")
+ }
+
}
func TestUserStoreUpdate(t *testing.T) {
Setup()
- u1 := model.User{}
- u1.TeamId = model.NewId()
+ u1 := &model.User{}
u1.Email = model.NewId()
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}))
time.Sleep(100 * time.Millisecond)
- if err := (<-store.User().Update(&u1, false)).Err; err != nil {
+ if err := (<-store.User().Update(u1, false)).Err; err != nil {
t.Fatal(err)
}
u1.Id = "missing"
- if err := (<-store.User().Update(&u1, false)).Err; err == nil {
+ if err := (<-store.User().Update(u1, false)).Err; err == nil {
t.Fatal("Update should have failed because of missing key")
}
u1.Id = model.NewId()
- if err := (<-store.User().Update(&u1, false)).Err; err == nil {
+ if err := (<-store.User().Update(u1, false)).Err; err == nil {
t.Fatal("Update should have faile because id change")
}
}
@@ -87,10 +97,10 @@ func TestUserStoreUpdate(t *testing.T) {
func TestUserStoreUpdateLastPingAt(t *testing.T) {
Setup()
- u1 := model.User{}
- u1.TeamId = model.NewId()
+ u1 := &model.User{}
u1.Email = model.NewId()
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}))
if err := (<-store.User().UpdateLastPingAt(u1.Id, 1234567890)).Err; err != nil {
t.Fatal(err)
@@ -109,10 +119,10 @@ func TestUserStoreUpdateLastPingAt(t *testing.T) {
func TestUserStoreUpdateLastActivityAt(t *testing.T) {
Setup()
- u1 := model.User{}
- u1.TeamId = model.NewId()
+ u1 := &model.User{}
u1.Email = model.NewId()
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}))
if err := (<-store.User().UpdateLastActivityAt(u1.Id, 1234567890)).Err; err != nil {
t.Fatal(err)
@@ -131,10 +141,10 @@ func TestUserStoreUpdateLastActivityAt(t *testing.T) {
func TestUserStoreUpdateFailedPasswordAttempts(t *testing.T) {
Setup()
- u1 := model.User{}
- u1.TeamId = model.NewId()
+ u1 := &model.User{}
u1.Email = model.NewId()
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}))
if err := (<-store.User().UpdateFailedPasswordAttempts(u1.Id, 3)).Err; err != nil {
t.Fatal(err)
@@ -153,14 +163,13 @@ func TestUserStoreUpdateFailedPasswordAttempts(t *testing.T) {
func TestUserStoreUpdateUserAndSessionActivity(t *testing.T) {
Setup()
- u1 := model.User{}
- u1.TeamId = model.NewId()
+ u1 := &model.User{}
u1.Email = model.NewId()
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}))
s1 := model.Session{}
s1.UserId = u1.Id
- s1.TeamId = u1.TeamId
Must(store.Session().Save(&s1))
if err := (<-store.User().UpdateUserAndSessionActivity(u1.Id, s1.Id, 1234567890)).Err; err != nil {
@@ -188,10 +197,10 @@ func TestUserStoreUpdateUserAndSessionActivity(t *testing.T) {
func TestUserStoreGet(t *testing.T) {
Setup()
- u1 := model.User{}
- u1.TeamId = model.NewId()
+ u1 := &model.User{}
u1.Email = model.NewId()
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}))
if r1 := <-store.User().Get(u1.Id); r1.Err != nil {
t.Fatal(r1.Err)
@@ -209,10 +218,10 @@ func TestUserStoreGet(t *testing.T) {
func TestUserCount(t *testing.T) {
Setup()
- u1 := model.User{}
- u1.TeamId = model.NewId()
+ u1 := &model.User{}
u1.Email = model.NewId()
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}))
if result := <-store.User().GetTotalUsersCount(); result.Err != nil {
t.Fatal(result.Err)
@@ -227,10 +236,11 @@ func TestUserCount(t *testing.T) {
func TestActiveUserCount(t *testing.T) {
Setup()
- u1 := model.User{}
- u1.TeamId = model.NewId()
+ u1 := &model.User{}
u1.Email = model.NewId()
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}))
+ <-store.User().UpdateLastActivityAt(u1.Id, model.GetMillis())
if result := <-store.User().GetTotalActiveUsersCount(); result.Err != nil {
t.Fatal(result.Err)
@@ -245,17 +255,89 @@ func TestActiveUserCount(t *testing.T) {
func TestUserStoreGetProfiles(t *testing.T) {
Setup()
- u1 := model.User{}
- u1.TeamId = model.NewId()
+ teamId := model.NewId()
+
+ u1 := &model.User{}
u1.Email = model.NewId()
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
+
+ u2 := &model.User{}
+ u2.Email = model.NewId()
+ Must(store.User().Save(u2))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
+
+ if r1 := <-store.User().GetProfiles(teamId); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ users := r1.Data.(map[string]*model.User)
+ if len(users) != 2 {
+ t.Fatal("invalid returned users")
+ }
+
+ if users[u1.Id].Id != u1.Id {
+ t.Fatal("invalid returned user")
+ }
+ }
+
+ if r2 := <-store.User().GetProfiles("123"); r2.Err != nil {
+ t.Fatal(r2.Err)
+ } else {
+ if len(r2.Data.(map[string]*model.User)) != 0 {
+ t.Fatal("should have returned empty map")
+ }
+ }
+}
+
+func TestUserStoreGetDirectProfiles(t *testing.T) {
+ Setup()
+
+ teamId := model.NewId()
+
+ u1 := &model.User{}
+ u1.Email = model.NewId()
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
+
+ u2 := &model.User{}
+ u2.Email = model.NewId()
+ Must(store.User().Save(u2))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
+
+ if r1 := <-store.User().GetDirectProfiles(u1.Id); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ users := r1.Data.(map[string]*model.User)
+ if len(users) != 0 {
+ t.Fatal("invalid returned users")
+ }
+ }
+
+ if r2 := <-store.User().GetDirectProfiles("123"); r2.Err != nil {
+ t.Fatal(r2.Err)
+ } else {
+ if len(r2.Data.(map[string]*model.User)) != 0 {
+ t.Fatal("should have returned empty map")
+ }
+ }
+}
+
+func TestUserStoreGetProfilesByIds(t *testing.T) {
+ Setup()
+
+ teamId := model.NewId()
+
+ u1 := &model.User{}
+ u1.Email = model.NewId()
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
- u2 := model.User{}
- u2.TeamId = u1.TeamId
+ u2 := &model.User{}
u2.Email = model.NewId()
- Must(store.User().Save(&u2))
+ Must(store.User().Save(u2))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
- if r1 := <-store.User().GetProfiles(u1.TeamId); r1.Err != nil {
+ if r1 := <-store.User().GetProfileByIds([]string{u1.Id, u2.Id}); r1.Err != nil {
t.Fatal(r1.Err)
} else {
users := r1.Data.(map[string]*model.User)
@@ -268,6 +350,19 @@ func TestUserStoreGetProfiles(t *testing.T) {
}
}
+ if r1 := <-store.User().GetProfileByIds([]string{u1.Id}); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ users := r1.Data.(map[string]*model.User)
+ if len(users) != 1 {
+ t.Fatal("invalid returned users")
+ }
+
+ if users[u1.Id].Id != u1.Id {
+ t.Fatal("invalid returned user")
+ }
+ }
+
if r2 := <-store.User().GetProfiles("123"); r2.Err != nil {
t.Fatal(r2.Err)
} else {
@@ -280,15 +375,18 @@ func TestUserStoreGetProfiles(t *testing.T) {
func TestUserStoreGetSystemAdminProfiles(t *testing.T) {
Setup()
- u1 := model.User{}
- u1.TeamId = model.NewId()
+ teamId := model.NewId()
+
+ u1 := &model.User{}
u1.Email = model.NewId()
- Must(store.User().Save(&u1))
+ u1.Roles = model.ROLE_SYSTEM_ADMIN
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
- u2 := model.User{}
- u2.TeamId = u1.TeamId
+ u2 := &model.User{}
u2.Email = model.NewId()
- Must(store.User().Save(&u2))
+ Must(store.User().Save(u2))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
if r1 := <-store.User().GetSystemAdminProfiles(); r1.Err != nil {
t.Fatal(r1.Err)
@@ -303,16 +401,18 @@ func TestUserStoreGetSystemAdminProfiles(t *testing.T) {
func TestUserStoreGetByEmail(t *testing.T) {
Setup()
- u1 := model.User{}
- u1.TeamId = model.NewId()
+ teamid := model.NewId()
+
+ u1 := &model.User{}
u1.Email = model.NewId()
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamid, UserId: u1.Id}))
- if err := (<-store.User().GetByEmail(u1.TeamId, u1.Email)).Err; err != nil {
+ if err := (<-store.User().GetByEmail(u1.Email)).Err; err != nil {
t.Fatal(err)
}
- if err := (<-store.User().GetByEmail("", "")).Err; err == nil {
+ if err := (<-store.User().GetByEmail("")).Err; err == nil {
t.Fatal("Should have failed because of missing email")
}
}
@@ -320,18 +420,20 @@ func TestUserStoreGetByEmail(t *testing.T) {
func TestUserStoreGetByAuthData(t *testing.T) {
Setup()
- u1 := model.User{}
- u1.TeamId = model.NewId()
+ teamId := model.NewId()
+
+ u1 := &model.User{}
u1.Email = model.NewId()
- u1.AuthData = "123"
+ u1.AuthData = "123" + model.NewId()
u1.AuthService = "service"
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
- if err := (<-store.User().GetByAuth(u1.TeamId, u1.AuthData, u1.AuthService)).Err; err != nil {
+ if err := (<-store.User().GetByAuth(u1.AuthData, u1.AuthService)).Err; err != nil {
t.Fatal(err)
}
- if err := (<-store.User().GetByAuth("", "", "")).Err; err == nil {
+ if err := (<-store.User().GetByAuth("", "")).Err; err == nil {
t.Fatal("Should have failed because of missing auth data")
}
}
@@ -339,17 +441,19 @@ func TestUserStoreGetByAuthData(t *testing.T) {
func TestUserStoreGetByUsername(t *testing.T) {
Setup()
- u1 := model.User{}
- u1.TeamId = model.NewId()
+ teamId := model.NewId()
+
+ u1 := &model.User{}
u1.Email = model.NewId()
u1.Username = model.NewId()
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
- if err := (<-store.User().GetByUsername(u1.TeamId, u1.Username)).Err; err != nil {
+ if err := (<-store.User().GetByUsername(u1.Username)).Err; err != nil {
t.Fatal(err)
}
- if err := (<-store.User().GetByUsername("", "")).Err; err == nil {
+ if err := (<-store.User().GetByUsername("")).Err; err == nil {
t.Fatal("Should have failed because of missing username")
}
}
@@ -357,10 +461,12 @@ func TestUserStoreGetByUsername(t *testing.T) {
func TestUserStoreUpdatePassword(t *testing.T) {
Setup()
- u1 := model.User{}
- u1.TeamId = model.NewId()
+ teamId := model.NewId()
+
+ u1 := &model.User{}
u1.Email = model.NewId()
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
hashedPassword := model.HashPassword("newpwd")
@@ -368,7 +474,7 @@ func TestUserStoreUpdatePassword(t *testing.T) {
t.Fatal(err)
}
- if r1 := <-store.User().GetByEmail(u1.TeamId, u1.Email); r1.Err != nil {
+ if r1 := <-store.User().GetByEmail(u1.Email); r1.Err != nil {
t.Fatal(r1.Err)
} else {
user := r1.Data.(*model.User)
@@ -381,10 +487,10 @@ func TestUserStoreUpdatePassword(t *testing.T) {
func TestUserStoreDelete(t *testing.T) {
Setup()
- u1 := model.User{}
- u1.TeamId = model.NewId()
+ u1 := &model.User{}
u1.Email = model.NewId()
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}))
if err := (<-store.User().PermanentDelete(u1.Id)).Err; err != nil {
t.Fatal(err)
@@ -394,10 +500,12 @@ func TestUserStoreDelete(t *testing.T) {
func TestUserStoreUpdateAuthData(t *testing.T) {
Setup()
- u1 := model.User{}
- u1.TeamId = model.NewId()
+ teamId := model.NewId()
+
+ u1 := &model.User{}
u1.Email = model.NewId()
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
service := "someservice"
authData := "1"
@@ -406,7 +514,7 @@ func TestUserStoreUpdateAuthData(t *testing.T) {
t.Fatal(err)
}
- if r1 := <-store.User().GetByEmail(u1.TeamId, u1.Email); r1.Err != nil {
+ if r1 := <-store.User().GetByEmail(u1.Email); r1.Err != nil {
t.Fatal(r1.Err)
} else {
user := r1.Data.(*model.User)
@@ -430,26 +538,26 @@ func TestUserUnreadCount(t *testing.T) {
c1 := model.Channel{}
c1.TeamId = teamId
c1.DisplayName = "Unread Messages"
- c1.Name = "unread-messages"
+ c1.Name = "unread-messages-" + model.NewId()
c1.Type = model.CHANNEL_OPEN
c2 := model.Channel{}
c2.TeamId = teamId
c2.DisplayName = "Unread Direct"
- c2.Name = "unread-direct"
+ c2.Name = "unread-direct-" + model.NewId()
c2.Type = model.CHANNEL_DIRECT
- u1 := model.User{}
+ u1 := &model.User{}
+ u1.Username = "user1" + model.NewId()
u1.Email = model.NewId()
- u1.Username = "user1"
- u1.TeamId = teamId
- Must(store.User().Save(&u1))
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
- u2 := model.User{}
+ u2 := &model.User{}
u2.Email = model.NewId()
- u2.Username = "user2"
- u2.TeamId = teamId
- Must(store.User().Save(&u2))
+ u2.Username = "user2" + model.NewId()
+ Must(store.User().Save(u2))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
if err := (<-store.Channel().Save(&c1)).Err; err != nil {
t.Fatal("couldn't save item", err)
@@ -507,7 +615,6 @@ func TestUserStoreUpdateMfaSecret(t *testing.T) {
Setup()
u1 := model.User{}
- u1.TeamId = model.NewId()
u1.Email = model.NewId()
Must(store.User().Save(&u1))
@@ -527,7 +634,6 @@ func TestUserStoreUpdateMfaActive(t *testing.T) {
Setup()
u1 := model.User{}
- u1.TeamId = model.NewId()
u1.Email = model.NewId()
Must(store.User().Save(&u1))
diff --git a/store/store.go b/store/store.go
index 4a4fa1481..f5c440721 100644
--- a/store/store.go
+++ b/store/store.go
@@ -41,8 +41,10 @@ type Store interface {
Command() CommandStore
Preference() PreferenceStore
License() LicenseStore
+ PasswordRecovery() PasswordRecoveryStore
MarkSystemRanUnitTests()
Close()
+ DropAllTables()
}
type TeamStore interface {
@@ -51,12 +53,19 @@ type TeamStore interface {
UpdateDisplayName(name string, teamId string) StoreChannel
Get(id string) StoreChannel
GetByName(name string) StoreChannel
- GetTeamsForEmail(domain string) StoreChannel
GetAll() StoreChannel
GetAllTeamListing() StoreChannel
+ GetTeamsByUserId(userId string) StoreChannel
GetByInviteId(inviteId string) StoreChannel
PermanentDelete(teamId string) StoreChannel
AnalyticsTeamCount() StoreChannel
+ SaveMember(member *model.TeamMember) StoreChannel
+ UpdateMember(member *model.TeamMember) StoreChannel
+ GetMembers(teamId string) StoreChannel
+ GetTeamsForUser(userId string) StoreChannel
+ RemoveMember(teamId string, userId string) StoreChannel
+ RemoveAllMembersByTeam(teamId string) StoreChannel
+ RemoveAllMembersByUser(userId string) StoreChannel
}
type ChannelStore interface {
@@ -82,6 +91,7 @@ type ChannelStore interface {
PermanentDeleteMembersByUser(userId string) StoreChannel
GetExtraMembers(channelId string, limit int) StoreChannel
CheckPermissionsTo(teamId string, channelId string, userId string) StoreChannel
+ CheckPermissionsToNoTeam(channelId string, userId string) StoreChannel
CheckOpenChannelPermissions(teamId string, channelId string) StoreChannel
CheckPermissionsToByName(teamId string, channelName string, userId string) StoreChannel
UpdateLastViewedAt(channelId string, userId string) StoreChannel
@@ -120,12 +130,16 @@ type UserStore interface {
UpdateMfaSecret(userId, secret string) StoreChannel
UpdateMfaActive(userId string, active bool) StoreChannel
Get(id string) StoreChannel
+ GetAll() StoreChannel
GetProfiles(teamId string) StoreChannel
- GetByEmail(teamId string, email string) StoreChannel
- GetByAuth(teamId string, authData string, authService string) StoreChannel
- GetByUsername(teamId string, username string) StoreChannel
+ GetDirectProfiles(userId string) StoreChannel
+ GetProfileByIds(userId []string) StoreChannel
+ GetByEmail(email string) StoreChannel
+ GetByAuth(authData string, authService string) StoreChannel
+ GetByUsername(username string) StoreChannel
VerifyEmail(userId string) StoreChannel
GetEtagForProfiles(teamId string) StoreChannel
+ GetEtagForDirectProfiles(userId string) StoreChannel
UpdateFailedPasswordAttempts(userId string, attempts int) StoreChannel
GetForExport(teamId string) StoreChannel
GetTotalUsersCount() StoreChannel
@@ -133,7 +147,6 @@ type UserStore interface {
GetSystemAdminProfiles() StoreChannel
PermanentDelete(userId string) StoreChannel
AnalyticsUniqueUserCount(teamId string) StoreChannel
-
GetUnreadCount(userId string) StoreChannel
}
@@ -142,12 +155,12 @@ type SessionStore interface {
Get(sessionIdOrToken string) StoreChannel
GetSessions(userId string) StoreChannel
Remove(sessionIdOrToken string) StoreChannel
- RemoveAllSessionsForTeam(teamId string) StoreChannel
+ RemoveAllSessions() StoreChannel
PermanentDeleteSessionsByUser(teamId string) StoreChannel
UpdateLastActivityAt(sessionId string, time int64) StoreChannel
UpdateRoles(userId string, roles string) StoreChannel
UpdateDeviceId(id string, deviceId string) StoreChannel
- AnalyticsSessionCount(teamId string) StoreChannel
+ AnalyticsSessionCount() StoreChannel
}
type AuditStore interface {
@@ -228,3 +241,10 @@ type LicenseStore interface {
Save(license *model.LicenseRecord) StoreChannel
Get(id string) StoreChannel
}
+
+type PasswordRecoveryStore interface {
+ SaveOrUpdate(recovery *model.PasswordRecovery) StoreChannel
+ Delete(userId string) StoreChannel
+ Get(userId string) StoreChannel
+ GetByCode(code string) StoreChannel
+}
diff --git a/utils/config.go b/utils/config.go
index 8ff328008..1ae658b16 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -57,6 +57,14 @@ func FindDir(dir string) string {
return fileName + "/"
}
+func DisableDebugLogForTest() {
+ l4g.Global["stdout"].Level = l4g.WARNING
+}
+
+func EnableDebugLogForTest() {
+ l4g.Global["stdout"].Level = l4g.DEBUG
+}
+
func ConfigureCmdLineLog() {
ls := model.LogSettings{}
ls.EnableConsole = true
@@ -199,11 +207,10 @@ func getClientConfig(c *model.Config) map[string]string {
props["SiteName"] = c.TeamSettings.SiteName
props["EnableTeamCreation"] = strconv.FormatBool(c.TeamSettings.EnableTeamCreation)
props["EnableUserCreation"] = strconv.FormatBool(c.TeamSettings.EnableUserCreation)
+ props["EnableOpenServer"] = strconv.FormatBool(*c.TeamSettings.EnableOpenServer)
props["RestrictTeamNames"] = strconv.FormatBool(*c.TeamSettings.RestrictTeamNames)
- props["EnableTeamListing"] = strconv.FormatBool(*c.TeamSettings.EnableTeamListing)
props["EnableOAuthServiceProvider"] = strconv.FormatBool(c.ServiceSettings.EnableOAuthServiceProvider)
-
props["SegmentDeveloperKey"] = c.ServiceSettings.SegmentDeveloperKey
props["GoogleDeveloperKey"] = c.ServiceSettings.GoogleDeveloperKey
props["EnableIncomingWebhooks"] = strconv.FormatBool(c.ServiceSettings.EnableIncomingWebhooks)
@@ -242,7 +249,7 @@ func getClientConfig(c *model.Config) map[string]string {
props["AllowCorsFrom"] = *c.ServiceSettings.AllowCorsFrom
- if License.Features != nil {
+ if IsLicensed {
if *License.Features.CustomBrand {
props["EnableCustomBrand"] = strconv.FormatBool(*c.TeamSettings.EnableCustomBrand)
props["CustomBrandText"] = *c.TeamSettings.CustomBrandText
diff --git a/utils/license.go b/utils/license.go
index ba323d9b4..d3654932f 100644
--- a/utils/license.go
+++ b/utils/license.go
@@ -21,7 +21,9 @@ import (
)
var IsLicensed bool = false
-var License *model.License = &model.License{}
+var License *model.License = &model.License{
+ Features: new(model.Features),
+}
var ClientLicense map[string]string = map[string]string{"IsLicensed": "false"}
var publicKey []byte = []byte(`-----BEGIN PUBLIC KEY-----
diff --git a/utils/textgeneration.go b/utils/textgeneration.go
index 31b6517b8..96c43f402 100644
--- a/utils/textgeneration.go
+++ b/utils/textgeneration.go
@@ -473,16 +473,16 @@ func RandString(l int, charset string) string {
return string(ret)
}
-func RandomEmail(length Range, charset string) string {
- emaillen := RandIntFromRange(length)
- username := RandString(emaillen, charset)
- domain := "simulator.amazonses.com"
- return "success+" + username + "@" + domain
-}
-
-func FuzzEmail() string {
- return FUZZY_STRINGS_EMAILS[RandIntFromRange(Range{0, len(FUZZY_STRINGS_EMAILS) - 1})]
-}
+// func RandomEmail(length Range, charset string) string {
+// emaillen := RandIntFromRange(length)
+// username := RandString(emaillen, charset)
+// domain := "simulator.amazonses.com"
+// return "success+" + username + "@" + domain
+// }
+
+// func FuzzEmail() string {
+// return FUZZY_STRINGS_EMAILS[RandIntFromRange(Range{0, len(FUZZY_STRINGS_EMAILS) - 1})]
+// }
func RandomName(length Range, charset string) string {
namelen := RandIntFromRange(length)
diff --git a/web/web.go b/web/web.go
index ff5040a4b..8e96edd69 100644
--- a/web/web.go
+++ b/web/web.go
@@ -62,5 +62,11 @@ func root(c *api.Context, w http.ResponseWriter, r *http.Request) {
if !CheckBrowserCompatability(c, r) {
return
}
+
+ if api.IsApiCall(r) {
+ api.Handle404(w, r)
+ return
+ }
+
http.ServeFile(w, r, utils.FindDir(CLIENT_DIR)+"root.html")
}
diff --git a/web/web_test.go b/web/web_test.go
index 8dde5d747..9d7a84c5c 100644
--- a/web/web_test.go
+++ b/web/web_test.go
@@ -30,6 +30,8 @@ func Setup() {
ApiClient = model.NewClient(URL)
api.Srv.Store.MarkSystemRanUnitTests()
+
+ *utils.Cfg.TeamSettings.EnableOpenServer = true
}
}
@@ -62,8 +64,9 @@ func TestGetAccessToken(t *testing.T) {
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := ApiClient.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Password: "pwd"}
+ user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Password: "pwd"}
ruser := ApiClient.Must(ApiClient.CreateUser(&user, "")).Data.(*model.User)
+ api.JoinUserToTeam(rteam.Data.(*model.Team), ruser)
store.Must(api.Srv.Store.User().VerifyEmail(ruser.Id))
app := &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
@@ -77,6 +80,7 @@ func TestGetAccessToken(t *testing.T) {
} else {
ApiClient.Must(ApiClient.LoginById(ruser.Id, "pwd"))
+ ApiClient.SetTeamId(rteam.Data.(*model.Team).Id)
app = ApiClient.Must(ApiClient.RegisterApp(app)).Data.(*model.OAuthApp)
redirect := ApiClient.Must(ApiClient.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, app.Id, app.CallbackUrls[0], "all", "123")).Data.(map[string]string)["redirect"]
@@ -191,15 +195,17 @@ func TestIncomingWebhook(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = ApiClient.Must(ApiClient.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = ApiClient.Must(ApiClient.CreateUser(user, "")).Data.(*model.User)
store.Must(api.Srv.Store.User().VerifyEmail(user.Id))
+ api.JoinUserToTeam(team, user)
c := &api.Context{}
c.RequestId = model.NewId()
c.IpAddress = "cmd_line"
api.UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
ApiClient.LoginByEmail(team.Name, user.Email, "pwd")
+ ApiClient.SetTeamId(team.Id)
channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = ApiClient.Must(ApiClient.CreateChannel(channel1)).Data.(*model.Channel)
diff --git a/webapp/.eslintrc.json b/webapp/.eslintrc.json
index 13c9ee97f..3eb02cc40 100644
--- a/webapp/.eslintrc.json
+++ b/webapp/.eslintrc.json
@@ -19,6 +19,13 @@
"jquery": true,
"es6": true
},
+ "globals": {
+ "jest": true,
+ "describe": true,
+ "it": true,
+ "expect": true,
+ "before": true
+ },
"rules": {
"comma-dangle": [2, "never"],
"array-callback-return": 2,
diff --git a/webapp/Makefile b/webapp/Makefile
index 6ec75d1df..b0c2c831a 100644
--- a/webapp/Makefile
+++ b/webapp/Makefile
@@ -1,6 +1,6 @@
.PHONY: build test run clean stop
-test:
+test: .npminstall
@echo Checking for style guide compliance
npm run check
diff --git a/webapp/action_creators/global_actions.jsx b/webapp/action_creators/global_actions.jsx
index fd447ec93..5641246f9 100644
--- a/webapp/action_creators/global_actions.jsx
+++ b/webapp/action_creators/global_actions.jsx
@@ -4,11 +4,16 @@
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import PostStore from 'stores/post_store.jsx';
+import UserStore from 'stores/user_store.jsx';
+import BrowserStore from 'stores/browser_store.jsx';
+import ErrorStore from 'stores/error_store.jsx';
+import TeamStore from 'stores/team_store.jsx';
+import PreferenceStore from 'stores/preference_store.jsx';
import SearchStore from 'stores/search_store.jsx';
import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
import * as AsyncClient from 'utils/async_client.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import * as Websockets from './websocket_actions.jsx';
import * as I18n from 'i18n/i18n.jsx';
@@ -21,7 +26,6 @@ export function emitChannelClickEvent(channel) {
function userVisitedFakeChannel(chan, success, fail) {
const otherUserId = Utils.getUserIdFromChannelName(chan);
Client.createDirectChannel(
- chan,
otherUserId,
(data) => {
success(data);
@@ -61,6 +65,69 @@ export function emitChannelClickEvent(channel) {
}
}
+export function emitInitialLoad(callback) {
+ Client.getInitialLoad(
+ (data) => {
+ global.window.mm_config = data.client_cfg;
+ global.window.mm_license = data.license_cfg;
+
+ UserStore.setNoAccounts(data.no_accounts);
+
+ if (data.user && data.user.id) {
+ global.window.mm_user = data.user;
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_ME,
+ me: data.user
+ });
+ }
+
+ if (data.preferences) {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_PREFERENCES,
+ preferences: data.preferences
+ });
+ }
+
+ if (data.teams) {
+ var teams = {};
+ data.teams.forEach((team) => {
+ teams[team.id] = team;
+ });
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_ALL_TEAMS,
+ teams
+ });
+ }
+
+ if (data.team_members) {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_TEAM_MEMBERS,
+ team_members: data.team_members
+ });
+ }
+
+ if (data.direct_profiles) {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_DIRECT_PROFILES,
+ profiles: data.direct_profiles
+ });
+ }
+
+ if (callback) {
+ callback();
+ }
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'getInitialLoad');
+
+ if (callback) {
+ callback();
+ }
+ }
+ );
+}
+
export function emitPostFocusEvent(postId) {
AsyncClient.getChannels(true);
Client.getPostById(
@@ -80,6 +147,18 @@ export function emitPostFocusEvent(postId) {
);
}
+export function emitCloseRightHandSide() {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_SEARCH,
+ results: null
+ });
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_POST_SELECTED,
+ postId: null
+ });
+}
+
export function emitPostFocusRightHandSideFromSearch(post, isMentionSearch) {
Client.getPost(
post.channel_id,
@@ -133,21 +212,21 @@ export function emitLoadMorePostsFocusedBottomEvent() {
AsyncClient.getPostsAfter(latestPostId, 0, Constants.POST_CHUNK_SIZE);
}
-export function emitPostRecievedEvent(post, websocketMessageProps) {
+export function emitPostRecievedEvent(post, msg) {
if (ChannelStore.getCurrentId() === post.channel_id) {
if (window.isActive) {
AsyncClient.updateLastViewedAt();
} else {
AsyncClient.getChannel(post.channel_id);
}
- } else {
+ } else if (msg && TeamStore.getCurrentId() === msg.team_id) {
AsyncClient.getChannel(post.channel_id);
}
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POST,
post,
- websocketMessageProps
+ websocketMessageProps: msg.props
});
}
@@ -271,10 +350,6 @@ export function sendEphemeralPost(message, channelId) {
emitPostRecievedEvent(post);
}
-export function loadTeamRequiredPage() {
- AsyncClient.getAllTeams();
-}
-
export function newLocalizationSelected(locale) {
if (locale === 'en') {
AppDispatcher.handleServerAction({
@@ -311,8 +386,6 @@ export function loadBrowserLocale() {
export function viewLoggedIn() {
AsyncClient.getChannels();
AsyncClient.getChannelExtraInfo();
- AsyncClient.getMyTeam();
- AsyncClient.getMe();
// Clear pending posts (shouldn't have pending posts if we are loading)
PostStore.clearPendingPosts();
@@ -335,3 +408,21 @@ export function emitRemoteUserTypingEvent(channelId, userId, postParentId) {
postParentId
});
}
+
+export function emitUserLoggedOutEvent(redirectTo) {
+ const rURL = (redirectTo && typeof redirectTo === 'string') ? redirectTo : '/';
+ Client.logout(
+ () => {
+ BrowserStore.signalLogout();
+ BrowserStore.clear();
+ ErrorStore.clearLastError();
+ PreferenceStore.clear();
+ UserStore.clear();
+ TeamStore.clear();
+ browserHistory.push(rURL);
+ },
+ () => {
+ browserHistory.push(rURL);
+ }
+ );
+}
diff --git a/webapp/action_creators/websocket_actions.jsx b/webapp/action_creators/websocket_actions.jsx
index a66d79d18..c4e9c63c2 100644
--- a/webapp/action_creators/websocket_actions.jsx
+++ b/webapp/action_creators/websocket_actions.jsx
@@ -3,12 +3,14 @@
import $ from 'jquery';
import UserStore from 'stores/user_store.jsx';
+import TeamStore from 'stores/team_store.jsx';
import PostStore from 'stores/post_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import BrowserStore from 'stores/browser_store.jsx';
import ErrorStore from 'stores/error_store.jsx';
import NotificationStore from 'stores/notification_store.jsx'; //eslint-disable-line no-unused-vars
+import Client from 'utils/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
@@ -31,7 +33,7 @@ export function initialize() {
protocol = 'wss://';
}
- const connUrl = protocol + location.host + ((/:\d+/).test(location.host) ? '' : Utils.getWebsocketPort(protocol)) + '/api/v1/websocket';
+ const connUrl = protocol + location.host + ((/:\d+/).test(location.host) ? '' : Utils.getWebsocketPort(protocol)) + Client.getUsersRoute() + '/websocket';
if (connectFailCount === 0) {
console.log('websocket connecting to ' + connUrl); //eslint-disable-line no-console
@@ -145,6 +147,11 @@ function handleMessage(msg) {
export function sendMessage(msg) {
if (conn && conn.readyState === WebSocket.OPEN) {
+ var teamId = TeamStore.getCurrentId();
+ if (teamId && teamId.length > 0) {
+ msg.team_id = teamId;
+ }
+
conn.send(JSON.stringify(msg));
} else if (!conn || conn.readyState === WebSocket.Closed) {
conn = null;
@@ -161,7 +168,7 @@ export function close() {
function handleNewPostEvent(msg) {
const post = JSON.parse(msg.props.post);
- GlobalActions.emitPostRecievedEvent(post, msg.props);
+ GlobalActions.emitPostRecievedEvent(post, msg);
}
function handlePostEditEvent(msg) {
@@ -193,7 +200,7 @@ function handleUserAddedEvent(msg) {
AsyncClient.getChannelExtraInfo();
}
- if (UserStore.getCurrentId() === msg.user_id) {
+ if (TeamStore.getCurrentId() === msg.team_id && UserStore.getCurrentId() === msg.user_id) {
AsyncClient.getChannel(msg.channel_id);
}
}
@@ -219,7 +226,7 @@ function handleUserRemovedEvent(msg) {
function handleChannelViewedEvent(msg) {
// Useful for when multiple devices have the app open to different channels
- if (ChannelStore.getCurrentId() !== msg.channel_id && UserStore.getCurrentId() === msg.user_id) {
+ if (TeamStore.getCurrentId() === msg.team_id && ChannelStore.getCurrentId() !== msg.channel_id && UserStore.getCurrentId() === msg.user_id) {
AsyncClient.getChannel(msg.channel_id);
}
}
@@ -230,5 +237,7 @@ function handlePreferenceChangedEvent(msg) {
}
function handleUserTypingEvent(msg) {
- GlobalActions.emitRemoteUserTypingEvent(msg.channel_id, msg.user_id, msg.props.parent_id);
+ if (TeamStore.getCurrentId() === msg.team_id) {
+ GlobalActions.emitRemoteUserTypingEvent(msg.channel_id, msg.user_id, msg.props.parent_id);
+ }
}
diff --git a/webapp/client/client.jsx b/webapp/client/client.jsx
new file mode 100644
index 000000000..98e660227
--- /dev/null
+++ b/webapp/client/client.jsx
@@ -0,0 +1,1463 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import request from 'superagent';
+
+const HEADER_X_VERSION_ID = 'x-version-id';
+const HEADER_TOKEN = 'token';
+const HEADER_BEARER = 'BEARER';
+const HEADER_AUTH = 'Authorization';
+
+export default class Client {
+ constructor() {
+ this.teamId = '';
+ this.serverVersion = '';
+ this.logToConsole = false;
+ this.useToken = false;
+ this.token = '';
+ this.url = '';
+ this.urlVersion = '/api/v3';
+ this.defaultHeaders = {
+ 'X-Requested-With': 'XMLHttpRequest'
+ };
+
+ this.translations = {
+ connectionError: 'There appears to be a problem with your internet connection.',
+ unknownError: 'We received an unexpected status code from the server.'
+ };
+ }
+
+ setUrl = (url) => {
+ this.url = url;
+ }
+
+ setTeamId = (id) => {
+ this.teamId = id;
+ }
+
+ getTeamId = () => {
+ if (this.teamId === '') {
+ console.error('You are trying to use a route that requires a team_id, but you have not called setTeamId() in client.jsx'); // eslint-disable-line no-console
+ }
+
+ return this.teamId;
+ }
+
+ getServerVersion = () => {
+ return this.serverVersion;
+ }
+
+ getBaseRoute() {
+ return `${this.url}${this.urlVersion}`;
+ }
+
+ getAdminRoute() {
+ return `${this.url}${this.urlVersion}/admin`;
+ }
+
+ getLicenseRoute() {
+ return `${this.url}${this.urlVersion}/license`;
+ }
+
+ getTeamsRoute() {
+ return `${this.url}${this.urlVersion}/teams`;
+ }
+
+ getTeamNeededRoute() {
+ return `${this.url}${this.urlVersion}/teams/${this.getTeamId()}`;
+ }
+
+ getChannelsRoute() {
+ return `${this.url}${this.urlVersion}/teams/${this.getTeamId()}/channels`;
+ }
+
+ getChannelNeededRoute(channelId) {
+ return `${this.url}${this.urlVersion}/teams/${this.getTeamId()}/channels/${channelId}`;
+ }
+
+ getCommandsRoute() {
+ return `${this.url}${this.urlVersion}/teams/${this.getTeamId()}/commands`;
+ }
+
+ getHooksRoute() {
+ return `${this.url}${this.urlVersion}/teams/${this.getTeamId()}/hooks`;
+ }
+
+ getPostsRoute(channelId) {
+ return `${this.url}${this.urlVersion}/teams/${this.getTeamId()}/channels/${channelId}/posts`;
+ }
+
+ getUsersRoute() {
+ return `${this.url}${this.urlVersion}/users`;
+ }
+
+ getFilesRoute() {
+ return `${this.url}${this.urlVersion}/teams/${this.getTeamId()}/files`;
+ }
+
+ getOAuthRoute() {
+ return `${this.url}${this.urlVersion}/oauth`;
+ }
+
+ getUserNeededRoute(userId) {
+ return `${this.url}${this.urlVersion}/users/${userId}`;
+ }
+
+ setTranslations = (messages) => {
+ this.translations = messages;
+ }
+
+ enableLogErrorsToConsole = (enabled) => {
+ this.logToConsole = enabled;
+ }
+
+ useHeaderToken = () => {
+ this.useToken = true;
+ if (this.token !== '') {
+ this.defaultHeaders[HEADER_AUTH] = `${HEADER_BEARER} ${this.token}`;
+ }
+ }
+
+ track = (category, action, label, property, value) => { // eslint-disable-line no-unused-vars
+ // NO-OP for inherited classes to override
+ }
+
+ trackPage = () => {
+ // NO-OP for inherited classes to override
+ }
+
+ handleError = (err, res) => { // eslint-disable-line no-unused-vars
+ // NO-OP for inherited classes to override
+ }
+
+ handleResponse = (methodName, successCallback, errorCallback, err, res) => {
+ if (res && res.header) {
+ this.serverVersion = res.header[HEADER_X_VERSION_ID];
+ if (res.header[HEADER_X_VERSION_ID]) {
+ this.serverVersion = res.header[HEADER_X_VERSION_ID];
+ }
+ }
+
+ if (err) {
+ // test to make sure it looks like a server JSON error response
+ var e = null;
+ if (res && res.body && res.body.id) {
+ e = res.body;
+ }
+
+ var msg = '';
+
+ if (e) {
+ msg = 'method=' + methodName + ' msg=' + e.message + ' detail=' + e.detailed_error + ' rid=' + e.request_id;
+ } else {
+ msg = 'method=' + methodName + ' status=' + err.status + ' statusCode=' + err.statusCode + ' err=' + err;
+
+ if (err.status === 0 || !err.status) {
+ e = {message: this.translations.connectionError};
+ } else {
+ e = {message: this.translations.unknownError + ' (' + err.status + ')'};
+ }
+ }
+
+ if (this.logToConsole) {
+ console.error(msg); // eslint-disable-line no-console
+ console.error(e); // eslint-disable-line no-console
+ }
+
+ this.track('api', 'api_weberror', methodName, 'message', msg);
+
+ this.handleError(err, res);
+
+ if (errorCallback) {
+ errorCallback(e, err, res);
+ return;
+ }
+ }
+
+ if (successCallback) {
+ successCallback(res.body, res);
+ }
+ }
+
+ // General / Admin / Licensing Routes Section
+
+ getTranslations = (url, success, error) => {
+ return request.
+ get(url).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getTranslations', success, error));
+ }
+
+ getClientConfig = (success, error) => {
+ return request.
+ get(`${this.getAdminRoute()}/client_props`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getClientConfig', success, error));
+ }
+
+ getComplianceReports = (success, error) => {
+ return request.
+ get(`${this.getAdminRoute()}/compliance_reports`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getComplianceReports', success, error));
+ }
+
+ uploadBrandImage = (image, success, error) => {
+ request.
+ post(`${this.getAdminRoute()}/upload_brand_image`).
+ set(this.defaultHeaders).
+ accept('application/json').
+ attach('image', image, image.name).
+ end(this.handleResponse.bind(this, 'uploadBrandImage', success, error));
+ }
+
+ saveComplianceReports = (job, success, error) => {
+ return request.
+ post(`${this.getAdminRoute()}/save_compliance_report`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(job).
+ end(this.handleResponse.bind(this, 'saveComplianceReports', success, error));
+ }
+
+ getLogs = (success, error) => {
+ return request.
+ get(`${this.getAdminRoute()}/logs`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getLogs', success, error));
+ }
+
+ getServerAudits = (success, error) => {
+ return request.
+ get(`${this.getAdminRoute()}/audits`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getServerAudits', success, error));
+ }
+
+ getConfig = (success, error) => {
+ return request.
+ get(`${this.getAdminRoute()}/config`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getConfig', success, error));
+ }
+
+ getAnalytics = (name, teamId, success, error) => {
+ let url = `${this.getAdminRoute()}/analytics/`;
+ if (teamId == null) {
+ url += name;
+ } else {
+ url += teamId + '/' + name;
+ }
+
+ return request.
+ get(url).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getAnalytics', success, error));
+ }
+
+ getTeamAnalytics = (teamId, name, success, error) => {
+ return request.
+ get(`${this.getAdminRoute()}/analytics/${teamId}/${name}`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getTeamAnalytics', success, error));
+ }
+
+ saveConfig = (config, success, error) => {
+ request.
+ post(`${this.getAdminRoute()}/save_config`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(config).
+ end(this.handleResponse.bind(this, 'saveConfig', success, error));
+ }
+
+ testEmail = (config, success, error) => {
+ request.
+ post(`${this.getAdminRoute()}/test_email`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(config).
+ end(this.handleResponse.bind(this, 'testEmail', success, error));
+ }
+
+ logClientError = (msg) => {
+ var l = {};
+ l.level = 'ERROR';
+ l.message = msg;
+
+ request.
+ post(`${this.getAdminRoute()}/log_client`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(l).
+ end(this.handleResponse.bind(this, 'logClientError', null, null));
+ }
+
+ getClientLicenceConfig = (success, error) => {
+ request.
+ get(`${this.getLicenseRoute()}/client_config`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getClientLicenceConfig', success, error));
+ }
+
+ removeLicenseFile = (success, error) => {
+ request.
+ post(`${this.getLicenseRoute()}/remove`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'removeLicenseFile', success, error));
+ }
+
+ uploadLicenseFile = (license, success, error) => {
+ request.
+ post(`${this.getLicenseRoute()}/add`).
+ set(this.defaultHeaders).
+ accept('application/json').
+ attach('license', license, license.name).
+ end(this.handleResponse.bind(this, 'uploadLicenseFile', success, error));
+
+ this.track('api', 'api_license_upload');
+ }
+
+ importSlack = (fileData, success, error) => {
+ request.
+ post(`${this.getTeamsRoute()}/import_team`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(fileData).
+ end(this.handleResponse.bind(this, 'importSlack', success, error));
+ }
+
+ exportTeam = (success, error) => {
+ request.
+ get(`${this.getTeamsRoute()}/export_team`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'exportTeam', success, error));
+ }
+
+ signupTeam = (email, success, error) => {
+ request.
+ post(`${this.getTeamsRoute()}/signup`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({email}).
+ end(this.handleResponse.bind(this, 'signupTeam', success, error));
+
+ this.track('api', 'api_teams_signup');
+ }
+
+ adminResetMfa = (userId, success, error) => {
+ const data = {};
+ data.user_id = userId;
+
+ request.
+ post(`${this.getAdminRoute()}/reset_mfa`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'adminResetMfa', success, error));
+ }
+
+ adminResetPassword = (userId, newPassword, success, error) => {
+ var data = {};
+ data.new_password = newPassword;
+ data.user_id = userId;
+
+ request.
+ post(`${this.getAdminRoute()}/reset_password`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'adminResetPassword', success, error));
+
+ this.track('api', 'api_admin_reset_password');
+ }
+
+ // Team Routes Section
+
+ createTeamFromSignup = (teamSignup, success, error) => {
+ request.
+ post(`${this.getTeamsRoute()}/create_from_signup`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(teamSignup).
+ end(this.handleResponse.bind(this, 'createTeamFromSignup', success, error));
+ }
+
+ findTeamByName = (teamName, success, error) => {
+ request.
+ post(`${this.getTeamsRoute()}/find_team_by_name`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({name: teamName}).
+ end(this.handleResponse.bind(this, 'findTeamByName', success, error));
+ }
+
+ createTeam = (team, success, error) => {
+ request.
+ post(`${this.getTeamsRoute()}/create`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(team).
+ end(this.handleResponse.bind(this, 'createTeam', success, error));
+
+ this.track('api', 'api_users_create', '', 'email', team.name);
+ }
+
+ updateTeam = (team, success, error) => {
+ request.
+ post(`${this.getTeamNeededRoute()}/update`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(team).
+ end(this.handleResponse.bind(this, 'updateTeam', success, error));
+
+ this.track('api', 'api_teams_update_name');
+ }
+
+ getAllTeams = (success, error) => {
+ request.
+ get(`${this.getTeamsRoute()}/all`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getAllTeams', success, error));
+ }
+
+ getAllTeamListings = (success, error) => {
+ request.
+ get(`${this.getTeamsRoute()}/all_team_listings`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getAllTeamListings', success, error));
+ }
+
+ getMyTeam = (success, error) => {
+ request.
+ get(`${this.getTeamNeededRoute()}/me`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getMyTeam', success, error));
+ }
+
+ getTeamMembers = (teamId, success, error) => {
+ request.
+ get(`${this.getTeamsRoute()}/members/${teamId}`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getTeamMembers', success, error));
+ }
+
+ inviteMembers = (data, success, error) => {
+ request.
+ post(`${this.getTeamNeededRoute()}/invite_members`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'inviteMembers', success, error));
+
+ this.track('api', 'api_teams_invite_members');
+ }
+
+ addUserToTeam = (userId, success, error) => {
+ request.
+ post(`${this.getTeamNeededRoute()}/add_user_to_team`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({user_id: userId}).
+ end(this.handleResponse.bind(this, 'addUserToTeam', success, error));
+
+ this.track('api', 'api_teams_invite_members');
+ }
+
+ addUserToTeamFromInvite = (data, hash, inviteId, success, error) => {
+ request.
+ post(`${this.getTeamsRoute()}/add_user_to_team_from_invite`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({hash, data, invite_id: inviteId}).
+ end(this.handleResponse.bind(this, 'addUserToTeam', success, error));
+
+ this.track('api', 'api_teams_invite_members');
+ }
+
+ getInviteInfo = (inviteId, success, error) => {
+ request.
+ post(`${this.getTeamsRoute()}/get_invite_info`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({invite_id: inviteId}).
+ end(this.handleResponse.bind(this, 'getInviteInfo', success, error));
+ }
+
+ // User Routes Setions
+
+ createUser = (user, success, error) => {
+ this.createUserWithInvite(user, null, null, null, success, error);
+ }
+
+ createUserWithInvite = (user, data, emailHash, inviteId, success, error) => {
+ var url = `${this.getUsersRoute()}/create`;
+
+ if (data || emailHash || inviteId) {
+ url += '?d=' + encodeURIComponent(data) + '&h=' + encodeURIComponent(emailHash) + '&iid=' + encodeURIComponent(inviteId);
+ }
+
+ request.
+ post(url).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(user).
+ end(this.handleResponse.bind(this, 'createUser', success, error));
+
+ this.track('api', 'api_users_create', '', 'email', user.email);
+ }
+
+ updateUser = (user, success, error) => {
+ request.
+ post(`${this.getUsersRoute()}/update`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(user).
+ end(this.handleResponse.bind(this, 'updateUser', success, error));
+
+ this.track('api', 'api_users_update');
+ }
+
+ updatePassword = (userId, currentPassword, newPassword, success, error) => {
+ var data = {};
+ data.user_id = userId;
+ data.current_password = currentPassword;
+ data.new_password = newPassword;
+
+ request.
+ post(`${this.getUsersRoute()}/newpassword`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'updatePassword', success, error));
+
+ this.track('api', 'api_users_newpassword');
+ }
+
+ updateUserNotifyProps = (notifyProps, success, error) => {
+ request.
+ post(`${this.getUsersRoute()}/update_notify`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(notifyProps).
+ end(this.handleResponse.bind(this, 'updateUserNotifyProps', success, error));
+ }
+
+ updateRoles = (userId, newRoles, success, error) => {
+ var data = {
+ user_id: userId,
+ new_roles: newRoles
+ };
+
+ request.
+ post(`${this.getUsersRoute()}/update_roles`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'updateRoles', success, error));
+
+ this.track('api', 'api_users_update_roles');
+ }
+
+ updateActive = (userId, active, success, error) => {
+ var data = {};
+ data.user_id = userId;
+ data.active = '' + active;
+
+ request.
+ post(`${this.getUsersRoute()}/update_active`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'updateActive', success, error));
+
+ this.track('api', 'api_users_update_roles');
+ }
+
+ sendPasswordReset = (email, success, error) => {
+ var data = {};
+ data.email = email;
+
+ request.
+ post(`${this.getUsersRoute()}/send_password_reset`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'sendPasswordReset', success, error));
+
+ this.track('api', 'api_users_send_password_reset');
+ }
+
+ resetPassword = (code, newPassword, success, error) => {
+ var data = {};
+ data.new_password = newPassword;
+ data.code = code;
+
+ request.
+ post(`${this.getUsersRoute()}/reset_password`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'resetPassword', success, error));
+
+ this.track('api', 'api_users_reset_password');
+ }
+
+ emailToOAuth = (email, password, service, success, error) => {
+ var data = {};
+ data.password = password;
+ data.email = email;
+ data.service = service;
+
+ request.
+ post(`${this.getUsersRoute()}/claim/email_to_oauth`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'emailToOAuth', success, error));
+
+ this.track('api', 'api_users_email_to_oauth');
+ }
+
+ oauthToEmail = (email, password, success, error) => {
+ var data = {};
+ data.password = password;
+ data.email = email;
+
+ request.
+ post(`${this.getUsersRoute()}/claim/oauth_to_email`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'oauthToEmail', success, error));
+
+ this.track('api', 'api_users_oauth_to_email');
+ }
+
+ emailToLdap = (email, password, ldapId, ldapPassword, success, error) => {
+ var data = {};
+ data.email_password = password;
+ data.email = email;
+ data.ldap_id = ldapId;
+ data.ldap_password = ldapPassword;
+
+ request.
+ post(`${this.getUsersRoute()}/claim/email_to_ldap`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'emailToLdap', success, error));
+
+ this.track('api', 'api_users_email_to_ldap');
+ }
+
+ ldapToEmail = (email, emailPassword, ldapPassword, success, error) => {
+ var data = {};
+ data.email = email;
+ data.ldap_password = ldapPassword;
+ data.email_password = emailPassword;
+
+ request.
+ post(`${this.getUsersRoute()}/claim/ldap_to_email`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'ldapToEmail', success, error));
+
+ this.track('api', 'api_users_oauth_to_email');
+ }
+
+ getInitialLoad = (success, error) => {
+ request.
+ get(`${this.getUsersRoute()}/initial_load`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getInitialLoad', success, error));
+ }
+
+ getMe = (success, error) => {
+ request.
+ get(`${this.getUsersRoute()}/me`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getMe', success, error));
+ }
+
+ login = (email, username, password, mfaToken, success, error) => {
+ var outer = this; // eslint-disable-line consistent-this
+
+ request.
+ post(`${this.getUsersRoute()}/login`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({email, password, username, token: mfaToken}).
+ end(this.handleResponse.bind(
+ this,
+ 'login',
+ (data, res) => {
+ if (res && res.header) {
+ outer.token = res.header[HEADER_TOKEN];
+
+ if (outer.useToken) {
+ outer.defaultHeaders[HEADER_AUTH] = `${HEADER_BEARER} ${outer.token}`;
+ }
+ }
+
+ if (success) {
+ success(data, res);
+ }
+ },
+ error
+ ));
+
+ this.track('api', 'api_users_login', '', 'email', email);
+ }
+
+ loginByLdap = (ldapId, password, mfaToken, success, error) => {
+ var outer = this; // eslint-disable-line consistent-this
+
+ request.
+ post(`${this.getUsersRoute()}/login_ldap`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({id: ldapId, password, token: mfaToken}).
+ end(this.handleResponse.bind(
+ this,
+ 'loginByLdap',
+ (data, res) => {
+ if (res && res.header) {
+ outer.token = res.header[HEADER_TOKEN];
+
+ if (outer.useToken) {
+ outer.defaultHeaders[HEADER_AUTH] = `${HEADER_BEARER} ${outer.token}`;
+ }
+ }
+
+ if (success) {
+ success(data, res);
+ }
+ },
+ error
+ ));
+
+ this.track('api', 'api_users_loginLdap', '', 'email', ldapId);
+ }
+
+ logout = (success, error) => {
+ request.
+ post(`${this.getUsersRoute()}/logout`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'logout', success, error));
+
+ this.track('api', 'api_users_logout');
+ }
+
+ checkMfa = (method, loginId, success, error) => {
+ var data = {};
+ data.method = method;
+ data.login_id = loginId;
+
+ request.
+ post(`${this.getUsersRoute()}/mfa`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'checkMfa', success, error));
+
+ this.track('api', 'api_users_oauth_to_email');
+ }
+
+ revokeSession = (altId, success, error) => {
+ request.
+ post(`${this.getUsersRoute()}/revoke_session`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({id: altId}).
+ end(this.handleResponse.bind(this, 'revokeSession', success, error));
+ }
+
+ getSessions = (userId, success, error) => {
+ request.
+ get(`${this.getUserNeededRoute(userId)}/sessions`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getSessions', success, error));
+ }
+
+ getAudits = (userId, success, error) => {
+ request.
+ get(`${this.getUserNeededRoute(userId)}/audits`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getAudits', success, error));
+ }
+
+ getDirectProfiles = (success, error) => {
+ request.
+ get(`${this.getUsersRoute()}/direct_profiles`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getDirectProfiles', success, error));
+ }
+
+ getProfiles = (success, error) => {
+ request.
+ get(`${this.getUsersRoute()}/profiles/${this.getTeamId()}`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getProfiles', success, error));
+ }
+
+ getProfilesForTeam = (teamId, success, error) => {
+ request.
+ get(`${this.getUsersRoute()}/profiles/${teamId}?skip_direct=true`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getProfilesForTeam', success, error));
+ }
+
+ getStatuses = (ids, success, error) => {
+ request.
+ post(`${this.getUsersRoute()}/status`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(ids).
+ end(this.handleResponse.bind(this, 'getStatuses', success, error));
+ }
+
+ verifyEmail = (uid, hid, success, error) => {
+ request.
+ post(`${this.getUsersRoute()}/verify_email`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({uid, hid}).
+ end(this.handleResponse.bind(this, 'verifyEmail', success, error));
+ }
+
+ resendVerification = (email, success, error) => {
+ request.
+ post(`${this.getUsersRoute()}/resend_verification`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({email}).
+ end(this.handleResponse.bind(this, 'resendVerification', success, error));
+ }
+
+ updateMfa = (token, activate, success, error) => {
+ const data = {};
+ data.activate = activate;
+ data.token = token;
+
+ request.
+ post(`${this.getUsersRoute()}/update_mfa`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'updateMfa', success, error));
+ }
+
+ uploadProfileImage = (image, success, error) => {
+ request.
+ post(`${this.getUsersRoute()}/newimage`).
+ set(this.defaultHeaders).
+ attach('image', image, image.name).
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'uploadProfileImage', success, error));
+ }
+
+ // Channel Routes Section
+
+ createChannel = (channel, success, error) => {
+ request.
+ post(`${this.getChannelsRoute()}/create`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(channel).
+ end(this.handleResponse.bind(this, 'createChannel', success, error));
+
+ this.track('api', 'api_channels_create', channel.type, 'name', channel.name);
+ }
+
+ createDirectChannel = (userId, success, error) => {
+ request.
+ post(`${this.getChannelsRoute()}/create_direct`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({user_id: userId}).
+ end(this.handleResponse.bind(this, 'createDirectChannel', success, error));
+ }
+
+ updateChannel = (channel, success, error) => {
+ request.
+ post(`${this.getChannelsRoute()}/update`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(channel).
+ end(this.handleResponse.bind(this, 'updateChannel', success, error));
+
+ this.track('api', 'api_channels_update');
+ }
+
+ updateChannelHeader = (channelId, header, success, error) => {
+ const data = {
+ channel_id: channelId,
+ channel_header: header
+ };
+
+ request.
+ post(`${this.getChannelsRoute()}/update_header`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'updateChannel', success, error));
+
+ this.track('api', 'api_channels_header');
+ }
+
+ updateChannelPurpose = (channelId, purpose, success, error) => {
+ const data = {
+ channel_id: channelId,
+ channel_purpose: purpose
+ };
+
+ request.
+ post(`${this.getChannelsRoute()}/update_purpose`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'updateChannelPurpose', success, error));
+
+ this.track('api', 'api_channels_purpose');
+ }
+
+ updateChannelNotifyProps = (data, success, error) => {
+ request.
+ post(`${this.getChannelsRoute()}/update_notify_props`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'updateChannelNotifyProps', success, error));
+ }
+
+ leaveChannel = (channelId, success, error) => {
+ request.
+ post(`${this.getChannelNeededRoute(channelId)}/leave`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'leaveChannel', success, error));
+
+ this.track('api', 'api_channels_leave');
+ }
+
+ joinChannel = (channelId, success, error) => {
+ request.
+ post(`${this.getChannelNeededRoute(channelId)}/join`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'joinChannel', success, error));
+
+ this.track('api', 'api_channels_join');
+ }
+
+ deleteChannel = (channelId, success, error) => {
+ request.
+ post(`${this.getChannelNeededRoute(channelId)}/delete`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'deleteChannel', success, error));
+
+ this.track('api', 'api_channels_delete');
+ }
+
+ updateLastViewedAt = (channelId, success, error) => {
+ request.
+ post(`${this.getChannelNeededRoute(channelId)}/update_last_viewed_at`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'updateLastViewedAt', success, error));
+ }
+
+ getChannels = (success, error) => {
+ request.
+ get(`${this.getChannelsRoute()}/`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getChannels', success, error));
+ }
+
+ getChannel = (channelId, success, error) => {
+ request.
+ get(`${this.getChannelNeededRoute(channelId)}/`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getChannel', success, error));
+
+ this.track('api', 'api_channel_get');
+ }
+
+ getMoreChannels = (success, error) => {
+ request.
+ get(`${this.getChannelsRoute()}/more`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getMoreChannels', success, error));
+ }
+
+ getChannelCounts = (success, error) => {
+ request.
+ get(`${this.getChannelsRoute()}/counts`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getChannelCounts', success, error));
+ }
+
+ getChannelExtraInfo = (channelId, memberLimit, success, error) => {
+ var url = `${this.getChannelNeededRoute(channelId)}/extra_info`;
+ if (memberLimit) {
+ url += '/' + memberLimit;
+ }
+
+ request.
+ get(url).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getChannelExtraInfo', success, error));
+ }
+
+ addChannelMember = (channelId, userId, success, error) => {
+ request.
+ post(`${this.getChannelNeededRoute(channelId)}/add`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({user_id: userId}).
+ end(this.handleResponse.bind(this, 'addChannelMember', success, error));
+
+ this.track('api', 'api_channels_add_member');
+ }
+
+ removeChannelMember = (channelId, userId, success, error) => {
+ request.
+ post(`${this.getChannelNeededRoute(channelId)}/remove`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({user_id: userId}).
+ end(this.handleResponse.bind(this, 'removeChannelMember', success, error));
+
+ this.track('api', 'api_channels_remove_member');
+ }
+
+ // Routes for Commands
+
+ listCommands = (success, error) => {
+ request.
+ get(`${this.getCommandsRoute()}/list`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'listCommands', success, error));
+ }
+
+ executeCommand = (channelId, command, suggest, success, error) => {
+ request.
+ post(`${this.getCommandsRoute()}/execute`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({channelId, command, suggest: '' + suggest}).
+ end(this.handleResponse.bind(this, 'executeCommand', success, error));
+ }
+
+ addCommand = (command, success, error) => {
+ request.
+ post(`${this.getCommandsRoute()}/create`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(command).
+ end(this.handleResponse.bind(this, 'addCommand', success, error));
+ }
+
+ deleteCommand = (commandId, success, error) => {
+ request.
+ post(`${this.getCommandsRoute()}/delete`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({id: commandId}).
+ end(this.handleResponse.bind(this, 'deleteCommand', success, error));
+ }
+
+ listTeamCommands = (success, error) => {
+ request.
+ get(`${this.getCommandsRoute()}/list_team_commands`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'listTeamCommands', success, error));
+ }
+
+ regenCommandToken = (commandId, suggest, success, error) => {
+ request.
+ post(`${this.getCommandsRoute()}/regen_token`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({id: commandId}).
+ end(this.handleResponse.bind(this, 'regenCommandToken', success, error));
+ }
+
+ // Routes for Posts
+
+ createPost = (post, success, error) => {
+ request.
+ post(`${this.getPostsRoute(post.channel_id)}/create`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(post).
+ end(this.handleResponse.bind(this, 'createPost', success, error));
+
+ this.track('api', 'api_posts_create', post.channel_id, 'length', post.message.length);
+ }
+
+ getPostById = (postId, success, error) => {
+ request.
+ get(`${this.getTeamNeededRoute()}/posts/${postId}`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getPostById', success, error));
+ }
+
+ getPost = (channelId, postId, success, error) => {
+ request.
+ get(`${this.getChannelNeededRoute(channelId)}/posts/${postId}/get`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getPost', success, error));
+ }
+
+ updatePost = (post, success, error) => {
+ request.
+ post(`${this.getPostsRoute(post.channel_id)}/update`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(post).
+ end(this.handleResponse.bind(this, 'updatePost', success, error));
+
+ this.track('api', 'api_posts_update');
+ }
+
+ deletePost = (channelId, postId, success, error) => {
+ request.
+ post(`${this.getChannelNeededRoute(channelId)}/posts/${postId}/delete`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'deletePost', success, error));
+
+ this.track('api', 'api_posts_delete');
+ }
+
+ search = (terms, success, error) => {
+ request.
+ get(`${this.getTeamNeededRoute()}/posts/search`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ query({terms}).
+ end(this.handleResponse.bind(this, 'search', success, error));
+
+ this.track('api', 'api_posts_search');
+ }
+
+ getPostsPage = (channelId, offset, limit, success, error) => {
+ request.
+ get(`${this.getPostsRoute(channelId)}/page/${offset}/${limit}`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getPostsPage', success, error));
+ }
+
+ getPosts = (channelId, since, success, error) => {
+ request.
+ get(`${this.getPostsRoute(channelId)}/since/${since}`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getPosts', success, error));
+ }
+
+ getPostsBefore = (channelId, postId, offset, numPost, success, error) => {
+ request.
+ get(`${this.getPostsRoute(channelId)}/${postId}/before/${offset}/${numPost}`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getPostsBefore', success, error));
+ }
+
+ getPostsAfter = (channelId, postId, offset, numPost, success, error) => {
+ request.
+ get(`${this.getPostsRoute(channelId)}/${postId}/after/${offset}/${numPost}`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getPostsAfter', success, error));
+ }
+
+ // Routes for Files
+
+ getFileInfo = (filename, success, error) => {
+ request.
+ get(`${this.getFilesRoute()}/get_info${filename}`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getFileInfo', success, error));
+ }
+
+ getPublicLink = (data, success, error) => {
+ request.
+ post(`${this.getFilesRoute()}/get_public_link`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'getPublicLink', success, error));
+ }
+
+ uploadFile = (file, filename, channelId, clientId, success, error) => {
+ return request.
+ post(`${this.getFilesRoute()}/upload`).
+ set(this.defaultHeaders).
+ attach('files', file, filename).
+ field('channel_id', channelId).
+ field('client_ids', clientId).
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'uploadFile', success, error));
+ }
+
+ // Routes for OAuth
+
+ registerOAuthApp = (app, success, error) => {
+ request.
+ post(`${this.getOAuthRoute()}/register`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(app).
+ end(this.handleResponse.bind(this, 'registerOAuthApp', success, error));
+
+ this.track('api', 'api_apps_register');
+ }
+
+ allowOAuth2 = (responseType, clientId, redirectUri, state, scope, success, error) => {
+ request.
+ get(`${this.getOAuthRoute()}/allow`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ query({response_type: responseType}).
+ query({client_id: clientId}).
+ query({redirect_uri: redirectUri}).
+ query({scope}).
+ query({state}).
+ end(this.handleResponse.bind(this, 'allowOAuth2', success, error));
+ }
+
+ // Routes for Hooks
+
+ addIncomingHook = (hook, success, error) => {
+ request.
+ post(`${this.getHooksRoute()}/incoming/create`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(hook).
+ end(this.handleResponse.bind(this, 'addIncomingHook', success, error));
+ }
+
+ deleteIncomingHook = (hookId, success, error) => {
+ request.
+ post(`${this.getHooksRoute()}/incoming/delete`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({id: hookId}).
+ end(this.handleResponse.bind(this, 'deleteIncomingHook', success, error));
+ }
+
+ listIncomingHooks = (success, error) => {
+ request.
+ get(`${this.getHooksRoute()}/incoming/list`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'listIncomingHooks', success, error));
+ }
+
+ addOutgoingHook = (hook, success, error) => {
+ request.
+ post(`${this.getHooksRoute()}/outgoing/create`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(hook).
+ end(this.handleResponse.bind(this, 'addOutgoingHook', success, error));
+ }
+
+ deleteOutgoingHook = (hookId, success, error) => {
+ request.
+ post(`${this.getHooksRoute()}/outgoing/delete`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({id: hookId}).
+ end(this.handleResponse.bind(this, 'deleteOutgoingHook', success, error));
+ }
+
+ listOutgoingHooks = (success, error) => {
+ request.
+ get(`${this.getHooksRoute()}/outgoing/list`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'listOutgoingHooks', success, error));
+ }
+
+ regenOutgoingHookToken = (hookId, success, error) => {
+ request.
+ post(`${this.getHooksRoute()}/outgoing/regen_token`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({id: hookId}).
+ end(this.handleResponse.bind(this, 'regenOutgoingHookToken', success, error));
+ }
+
+ //Routes for Prefrecnes
+
+ getAllPreferences = (success, error) => {
+ request.
+ get(`${this.getBaseRoute()}/preferences/`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getAllPreferences', success, error));
+ }
+
+ savePreferences = (preferences, success, error) => {
+ request.
+ post(`${this.getBaseRoute()}/preferences/save`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(preferences).
+ end(this.handleResponse.bind(this, 'savePreferences', success, error));
+ }
+
+ getPreferenceCategory = (category, success, error) => {
+ request.
+ get(`${this.getBaseRoute()}/preferences/${category}`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getPreferenceCategory', success, error));
+ }
+}
diff --git a/webapp/components/activity_log_modal.jsx b/webapp/components/activity_log_modal.jsx
index f1dd4a26a..d3e5ce66d 100644
--- a/webapp/components/activity_log_modal.jsx
+++ b/webapp/components/activity_log_modal.jsx
@@ -3,7 +3,7 @@
import $ from 'jquery';
import UserStore from 'stores/user_store.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {Modal} from 'react-bootstrap';
import LoadingScreen from './loading_screen.jsx';
diff --git a/webapp/components/admin_console/admin_navbar_dropdown.jsx b/webapp/components/admin_console/admin_navbar_dropdown.jsx
index 729d4b14d..dd56411f4 100644
--- a/webapp/components/admin_console/admin_navbar_dropdown.jsx
+++ b/webapp/components/admin_console/admin_navbar_dropdown.jsx
@@ -3,27 +3,20 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
-import * as Utils from 'utils/utils.jsx';
-import TeamStore from 'stores/team_store.jsx';
import Constants from 'utils/constants.jsx';
+import * as GlobalActions from 'action_creators/global_actions.jsx';
import {FormattedMessage} from 'react-intl';
import {Link} from 'react-router';
-function getStateFromStores() {
- return {currentTeam: TeamStore.getCurrent()};
-}
-
import React from 'react';
export default class AdminNavbarDropdown extends React.Component {
constructor(props) {
super(props);
this.blockToggle = false;
-
- this.state = getStateFromStores();
}
componentDidMount() {
@@ -64,24 +57,27 @@ export default class AdminNavbarDropdown extends React.Component {
>
<li>
<Link
- to={'/' + this.state.currentTeam.name + '/channels/town-square'}
+ to={'/select_team'}
>
<FormattedMessage
id='admin.nav.switch'
defaultMessage='Switch to {display_name}'
values={{
- display_name: this.state.currentTeam.display_name
+ display_name: global.window.mm_config.SiteName
}}
/>
</Link>
</li>
<li>
- <Link to={Utils.getTeamURLFromAddressBar() + '/logout'}>
+ <a
+ href='#'
+ onClick={GlobalActions.emitUserLoggedOutEvent}
+ >
<FormattedMessage
id='admin.nav.logout'
defaultMessage='Logout'
/>
- </Link>
+ </a>
</li>
</ul>
</li>
diff --git a/webapp/components/admin_console/admin_sidebar_header.jsx b/webapp/components/admin_console/admin_sidebar_header.jsx
index 2e6252075..400730030 100644
--- a/webapp/components/admin_console/admin_sidebar_header.jsx
+++ b/webapp/components/admin_console/admin_sidebar_header.jsx
@@ -4,6 +4,7 @@
import $ from 'jquery';
import AdminNavbarDropdown from './admin_navbar_dropdown.jsx';
import UserStore from 'stores/user_store.jsx';
+import Client from 'utils/web_client.jsx';
import {FormattedMessage} from 'react-intl';
@@ -41,7 +42,7 @@ export default class SidebarHeader extends React.Component {
profilePicture = (
<img
className='user__picture'
- src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at}
+ src={Client.getUsersRoute() + '/' + me.id + '/image?time=' + me.update_at}
/>
);
}
diff --git a/webapp/components/admin_console/compliance_reports.jsx b/webapp/components/admin_console/compliance_reports.jsx
index 84def2bce..702c1a969 100644
--- a/webapp/components/admin_console/compliance_reports.jsx
+++ b/webapp/components/admin_console/compliance_reports.jsx
@@ -7,7 +7,7 @@ import * as Utils from '../../utils/utils.jsx';
import AdminStore from '../../stores/admin_store.jsx';
import UserStore from '../../stores/user_store.jsx';
-import * as Client from '../../utils/client.jsx';
+import * as Client from '../../utils/web_client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import {FormattedMessage, FormattedDate, FormattedTime} from 'react-intl';
@@ -153,7 +153,7 @@ export default class ComplianceReports extends React.Component {
var download = '';
if (report.status === 'finished') {
download = (
- <a href={'/api/v1/admin/download_compliance_report/' + report.id}>
+ <a href={Client.getAdminRoute() + '/download_compliance_report/' + report.id}>
<FormattedMessage
id='admin.compliance_table.download'
defaultMessage='Download'
diff --git a/webapp/components/admin_console/compliance_settings.jsx b/webapp/components/admin_console/compliance_settings.jsx
index 206bb0faa..b127634e8 100644
--- a/webapp/components/admin_console/compliance_settings.jsx
+++ b/webapp/components/admin_console/compliance_settings.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import $ from 'jquery';
-import * as Client from '../../utils/client.jsx';
+import * as Client from '../../utils/web_client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import * as Utils from '../../utils/utils.jsx';
diff --git a/webapp/components/admin_console/email_settings.jsx b/webapp/components/admin_console/email_settings.jsx
index 8df48b206..1fa75ead9 100644
--- a/webapp/components/admin_console/email_settings.jsx
+++ b/webapp/components/admin_console/email_settings.jsx
@@ -3,7 +3,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import crypto from 'crypto';
import ConnectionSecurityDropdownSetting from './connection_security_dropdown_setting.jsx';
diff --git a/webapp/components/admin_console/gitlab_settings.jsx b/webapp/components/admin_console/gitlab_settings.jsx
index 7fdedde13..747905ac6 100644
--- a/webapp/components/admin_console/gitlab_settings.jsx
+++ b/webapp/components/admin_console/gitlab_settings.jsx
@@ -3,7 +3,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
diff --git a/webapp/components/admin_console/image_settings.jsx b/webapp/components/admin_console/image_settings.jsx
index 576ff18fd..023e9af3b 100644
--- a/webapp/components/admin_console/image_settings.jsx
+++ b/webapp/components/admin_console/image_settings.jsx
@@ -3,7 +3,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import crypto from 'crypto';
diff --git a/webapp/components/admin_console/ldap_settings.jsx b/webapp/components/admin_console/ldap_settings.jsx
index dd6e4338f..01402a588 100644
--- a/webapp/components/admin_console/ldap_settings.jsx
+++ b/webapp/components/admin_console/ldap_settings.jsx
@@ -3,7 +3,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
@@ -61,6 +61,7 @@ class LdapSettings extends React.Component {
config.LdapSettings.BindPassword = this.refs.BindPassword.value.trim();
config.LdapSettings.FirstNameAttribute = this.refs.FirstNameAttribute.value.trim();
config.LdapSettings.LastNameAttribute = this.refs.LastNameAttribute.value.trim();
+ config.LdapSettings.NicknameAttribute = this.refs.NicknameAttribute.value.trim();
config.LdapSettings.EmailAttribute = this.refs.EmailAttribute.value.trim();
config.LdapSettings.UsernameAttribute = this.refs.UsernameAttribute.value.trim();
config.LdapSettings.IdAttribute = this.refs.IdAttribute.value.trim();
@@ -441,6 +442,35 @@ class LdapSettings extends React.Component {
<div className='form-group'>
<label
className='control-label col-sm-4'
+ htmlFor='NicknameAttribute'
+ >
+ <FormattedMessage
+ id='admin.ldap.nicknameAttrTitle'
+ defaultMessage='Nickname Attribute:'
+ />
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='NicknameAttribute'
+ ref='NicknameAttribute'
+ placeholder={Utils.localizeMessage('admin.ldap.nicknameAttrEx', 'Ex "nickname"')}
+ defaultValue={this.props.config.LdapSettings.NicknameAttribute}
+ onChange={this.handleChange}
+ disabled={!this.state.enable}
+ />
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.ldap.nicknameAttrDesc'
+ defaultMessage='(Optional) The attribute in the LDAP server that will be used to populate the nickname of users in Mattermost.'
+ />
+ </p>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
htmlFor='EmailAttribute'
>
<FormattedMessage
diff --git a/webapp/components/admin_console/legal_and_support_settings.jsx b/webapp/components/admin_console/legal_and_support_settings.jsx
index bbbb3713c..9f72f5fdf 100644
--- a/webapp/components/admin_console/legal_and_support_settings.jsx
+++ b/webapp/components/admin_console/legal_and_support_settings.jsx
@@ -3,7 +3,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
diff --git a/webapp/components/admin_console/license_settings.jsx b/webapp/components/admin_console/license_settings.jsx
index 20e2fc83a..f2c511e44 100644
--- a/webapp/components/admin_console/license_settings.jsx
+++ b/webapp/components/admin_console/license_settings.jsx
@@ -4,7 +4,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
import * as Utils from 'utils/utils.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
@@ -54,10 +54,7 @@ class LicenseSettings extends React.Component {
$('#upload-button').button('loading');
- const formData = new FormData();
- formData.append('license', file, file.name);
-
- Client.uploadLicenseFile(formData,
+ Client.uploadLicenseFile(file,
() => {
Utils.clearFileInput(element[0]);
$('#upload-button').button('reset');
diff --git a/webapp/components/admin_console/log_settings.jsx b/webapp/components/admin_console/log_settings.jsx
index 5aa3ca1e0..061c2b6e3 100644
--- a/webapp/components/admin_console/log_settings.jsx
+++ b/webapp/components/admin_console/log_settings.jsx
@@ -3,7 +3,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
diff --git a/webapp/components/admin_console/privacy_settings.jsx b/webapp/components/admin_console/privacy_settings.jsx
index a312dddca..5045a6d31 100644
--- a/webapp/components/admin_console/privacy_settings.jsx
+++ b/webapp/components/admin_console/privacy_settings.jsx
@@ -3,7 +3,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
diff --git a/webapp/components/admin_console/rate_settings.jsx b/webapp/components/admin_console/rate_settings.jsx
index f3fb1742c..de7a40e6b 100644
--- a/webapp/components/admin_console/rate_settings.jsx
+++ b/webapp/components/admin_console/rate_settings.jsx
@@ -3,7 +3,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
diff --git a/webapp/components/admin_console/reset_password_modal.jsx b/webapp/components/admin_console/reset_password_modal.jsx
index f80c740e3..5133f3f28 100644
--- a/webapp/components/admin_console/reset_password_modal.jsx
+++ b/webapp/components/admin_console/reset_password_modal.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import ReactDOM from 'react-dom';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import Constants from 'utils/constants.jsx';
import {Modal} from 'react-bootstrap';
@@ -40,12 +40,9 @@ class ResetPasswordModal extends React.Component {
this.setState({serverError: null});
- var data = {};
- data.new_password = password;
- data.name = this.props.team.name;
- data.user_id = this.props.user.id;
-
- Client.resetPassword(data,
+ Client.adminResetPassword(
+ this.props.user.id,
+ password,
() => {
this.props.onModalSubmit(ReactDOM.findDOMNode(this.refs.password).value);
},
@@ -159,4 +156,4 @@ ResetPasswordModal.propTypes = {
onModalDismissed: React.PropTypes.func
};
-export default injectIntl(ResetPasswordModal); \ No newline at end of file
+export default injectIntl(ResetPasswordModal);
diff --git a/webapp/components/admin_console/service_settings.jsx b/webapp/components/admin_console/service_settings.jsx
index 2c3f4081c..90b6a39b4 100644
--- a/webapp/components/admin_console/service_settings.jsx
+++ b/webapp/components/admin_console/service_settings.jsx
@@ -3,7 +3,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
diff --git a/webapp/components/admin_console/sql_settings.jsx b/webapp/components/admin_console/sql_settings.jsx
index 33bb2cece..f2e005b83 100644
--- a/webapp/components/admin_console/sql_settings.jsx
+++ b/webapp/components/admin_console/sql_settings.jsx
@@ -3,7 +3,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import crypto from 'crypto';
diff --git a/webapp/components/admin_console/team_settings.jsx b/webapp/components/admin_console/team_settings.jsx
index 6c9828351..bbb7ec3c4 100644
--- a/webapp/components/admin_console/team_settings.jsx
+++ b/webapp/components/admin_console/team_settings.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import $ from 'jquery';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
@@ -42,6 +42,7 @@ class TeamSettings extends React.Component {
this.handleImageSubmit = this.handleImageSubmit.bind(this);
this.uploading = false;
+ this.timestamp = 0;
this.state = {
saveNeeded: false,
@@ -53,7 +54,7 @@ class TeamSettings extends React.Component {
componentWillMount() {
if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.CustomBrand === 'true') {
- $.get('/api/v1/admin/get_brand_image').done(() => this.setState({brandImageExists: true}));
+ $.get(Client.getAdminRoute() + '/get_brand_image').done(() => this.setState({brandImageExists: true}));
}
}
@@ -89,6 +90,7 @@ class TeamSettings extends React.Component {
if (element.prop('files').length > 0) {
this.setState({fileSelected: true, brandImage: element.prop('files')[0]});
}
+ $('#upload-button').button('reset');
}
handleSubmit(e) {
@@ -100,8 +102,8 @@ class TeamSettings extends React.Component {
config.TeamSettings.RestrictCreationToDomains = this.refs.RestrictCreationToDomains.value.trim();
config.TeamSettings.EnableTeamCreation = this.refs.EnableTeamCreation.checked;
config.TeamSettings.EnableUserCreation = this.refs.EnableUserCreation.checked;
+ config.TeamSettings.EnableOpenServer = this.refs.EnableOpenServer.checked;
config.TeamSettings.RestrictTeamNames = this.refs.RestrictTeamNames.checked;
- config.TeamSettings.EnableTeamListing = this.refs.EnableTeamListing.checked;
if (this.refs.EnableCustomBrand) {
config.TeamSettings.EnableCustomBrand = this.refs.EnableCustomBrand.checked;
@@ -155,6 +157,7 @@ class TeamSettings extends React.Component {
Client.uploadBrandImage(this.state.brandImage,
() => {
$('#upload-button').button('complete');
+ this.timestamp = Utils.getTimestamp();
this.setState({brandImageExists: true, brandImage: null});
this.uploading = false;
},
@@ -193,7 +196,7 @@ class TeamSettings extends React.Component {
img = (
<img
className='brand-img'
- src='/api/v1/admin/get_brand_image'
+ src={Client.getAdminRoute() + '/get_brand_image?t=' + this.timestamp}
/>
);
} else {
@@ -537,6 +540,53 @@ class TeamSettings extends React.Component {
<div className='form-group'>
<label
className='control-label col-sm-4'
+ htmlFor='EnableOpenServer'
+ >
+ <FormattedMessage
+ id='admin.team.openServerTitle'
+ defaultMessage='Enable Open Server: '
+ />
+ </label>
+ <div className='col-sm-8'>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='EnableOpenServer'
+ value='true'
+ ref='EnableOpenServer'
+ defaultChecked={this.props.config.TeamSettings.EnableOpenServer}
+ onChange={this.handleChange}
+ />
+ <FormattedMessage
+ id='admin.team.true'
+ defaultMessage='true'
+ />
+ </label>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='EnableOpenServer'
+ value='false'
+ defaultChecked={!this.props.config.TeamSettings.EnableOpenServer}
+ onChange={this.handleChange}
+ />
+ <FormattedMessage
+ id='admin.team.false'
+ defaultMessage='false'
+ />
+ </label>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.team.openServerDescription'
+ defaultMessage='When true, anyone can signup for a user account on this server without the need to be invited.'
+ />
+ </p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
htmlFor='RestrictCreationToDomains'
>
<FormattedMessage
@@ -610,53 +660,6 @@ class TeamSettings extends React.Component {
</div>
</div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='EnableTeamListing'
- >
- <FormattedMessage
- id='admin.team.dirTitle'
- defaultMessage='Enable Team Directory: '
- />
- </label>
- <div className='col-sm-8'>
- <label className='radio-inline'>
- <input
- type='radio'
- name='EnableTeamListing'
- value='true'
- ref='EnableTeamListing'
- defaultChecked={this.props.config.TeamSettings.EnableTeamListing}
- onChange={this.handleChange}
- />
- <FormattedMessage
- id='admin.team.true'
- defaultMessage='true'
- />
- </label>
- <label className='radio-inline'>
- <input
- type='radio'
- name='EnableTeamListing'
- value='false'
- defaultChecked={!this.props.config.TeamSettings.EnableTeamListing}
- onChange={this.handleChange}
- />
- <FormattedMessage
- id='admin.team.false'
- defaultMessage='false'
- />
- </label>
- <p className='help-text'>
- <FormattedMessage
- id='admin.team.dirDesc'
- defaultMessage='When true, teams that are configured to show in team directory will show on main page inplace of creating a new team.'
- />
- </p>
- </div>
- </div>
-
{brand}
<div className='form-group'>
diff --git a/webapp/components/admin_console/team_users.jsx b/webapp/components/admin_console/team_users.jsx
index 8b37bd237..2b0e6af0a 100644
--- a/webapp/components/admin_console/team_users.jsx
+++ b/webapp/components/admin_console/team_users.jsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import LoadingScreen from '../loading_screen.jsx';
import UserItem from './user_item.jsx';
import ResetPasswordModal from './reset_password_modal.jsx';
diff --git a/webapp/components/admin_console/user_item.jsx b/webapp/components/admin_console/user_item.jsx
index 5bd05d063..b8f21d77e 100644
--- a/webapp/components/admin_console/user_item.jsx
+++ b/webapp/components/admin_console/user_item.jsx
@@ -1,13 +1,13 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import UserStore from 'stores/user_store.jsx';
import ConfirmModal from '../confirm_modal.jsx';
import TeamStore from 'stores/team_store.jsx';
-import {FormattedMessage} from 'react-intl';
+import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
import React from 'react';
import {browserHistory} from 'react-router';
@@ -22,6 +22,7 @@ export default class UserItem extends React.Component {
this.handleMakeAdmin = this.handleMakeAdmin.bind(this);
this.handleMakeSystemAdmin = this.handleMakeSystemAdmin.bind(this);
this.handleResetPassword = this.handleResetPassword.bind(this);
+ this.handleResetMfa = this.handleResetMfa.bind(this);
this.handleDemote = this.handleDemote.bind(this);
this.handleDemoteSubmit = this.handleDemoteSubmit.bind(this);
this.handleDemoteCancel = this.handleDemoteCancel.bind(this);
@@ -40,12 +41,9 @@ export default class UserItem extends React.Component {
if (this.props.user.id === me.id) {
this.handleDemote(this.props.user, '');
} else {
- const data = {
- user_id: this.props.user.id,
- new_roles: ''
- };
-
- Client.updateRoles(data,
+ Client.updateRoles(
+ this.props.user.id,
+ '',
() => {
this.props.refreshProfiles();
},
@@ -86,12 +84,9 @@ export default class UserItem extends React.Component {
if (this.props.user.id === me.id) {
this.handleDemote(this.props.user, 'admin');
} else {
- const data = {
- user_id: this.props.user.id,
- new_roles: 'admin'
- };
-
- Client.updateRoles(data,
+ Client.updateRoles(
+ this.props.user.id,
+ 'admin',
() => {
this.props.refreshProfiles();
},
@@ -104,12 +99,10 @@ export default class UserItem extends React.Component {
handleMakeSystemAdmin(e) {
e.preventDefault();
- const data = {
- user_id: this.props.user.id,
- new_roles: 'system_admin'
- };
- Client.updateRoles(data,
+ Client.updateRoles(
+ this.props.user.id,
+ 'system_admin',
() => {
this.props.refreshProfiles();
},
@@ -124,6 +117,19 @@ export default class UserItem extends React.Component {
this.props.doPasswordReset(this.props.user);
}
+ handleResetMfa(e) {
+ e.preventDefault();
+
+ Client.adminResetMfa(this.props.user.id,
+ () => {
+ this.props.refreshProfiles();
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+ }
+
handleDemote(user, role) {
this.setState({
serverError: this.state.serverError,
@@ -143,12 +149,9 @@ export default class UserItem extends React.Component {
}
handleDemoteSubmit() {
- const data = {
- user_id: this.props.user.id,
- new_roles: this.state.role
- };
-
- Client.updateRoles(data,
+ Client.updateRoles(
+ this.props.user.id,
+ this.state.role,
() => {
this.setState({
serverError: null,
@@ -211,10 +214,15 @@ export default class UserItem extends React.Component {
const email = user.email;
let showMakeMember = user.roles === 'admin' || user.roles === 'system_admin';
- let showMakeAdmin = user.roles === '' || user.roles === 'system_admin';
+
+ //let showMakeAdmin = user.roles === '' || user.roles === 'system_admin';
+ let showMakeAdmin = false;
+
let showMakeSystemAdmin = user.roles === '' || user.roles === 'admin';
let showMakeActive = false;
let showMakeNotActive = user.roles !== 'system_admin';
+ let mfaEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MFA === 'true' && global.window.mm_config.EnableMultifactorAuthentication === 'true';
+ let showMfaReset = mfaEnabled && user.mfa_active;
if (user.delete_at > 0) {
currentRoles = (
@@ -319,6 +327,64 @@ export default class UserItem extends React.Component {
</li>
);
}
+
+ let mfaReset = null;
+ if (showMfaReset) {
+ mfaReset = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.handleResetMfa}
+ >
+ <FormattedMessage
+ id='admin.user_item.resetMfa'
+ defaultMessage='Remove MFA'
+ />
+ </a>
+ </li>
+ );
+ }
+
+ let mfaActiveText;
+ if (mfaEnabled) {
+ if (user.mfa_active) {
+ mfaActiveText = (
+ <FormattedHTMLMessage
+ id='admin.user_item.mfaYes'
+ defaultMessage=', <strong>MFA</strong>: Yes'
+ />
+ );
+ } else {
+ mfaActiveText = (
+ <FormattedHTMLMessage
+ id='admin.user_item.mfaNo'
+ defaultMessage=', <strong>MFA</strong>: No'
+ />
+ );
+ }
+ }
+
+ let authServiceText;
+ if (user.auth_service) {
+ authServiceText = (
+ <FormattedHTMLMessage
+ id='admin.user_item.authServiceNotEmail'
+ defaultMessage=', <strong>Sign-in Method:</strong> {service}'
+ values={{
+ service: Utils.toTitleCase(user.auth_service)
+ }}
+ />
+ );
+ } else {
+ authServiceText = (
+ <FormattedHTMLMessage
+ id='admin.user_item.authServiceEmail'
+ defaultMessage=', <strong>Sign-in Method:</strong> Email'
+ />
+ );
+ }
+
const me = UserStore.getCurrentUser();
let makeDemoteModal = null;
if (this.props.user.id === me.id) {
@@ -368,13 +434,23 @@ export default class UserItem extends React.Component {
<div className='more-modal__row'>
<img
className='more-modal__image pull-left'
- src={`/api/v1/users/${user.id}/image?time=${user.update_at}`}
+ src={`${Client.getUsersRoute()}/${user.id}/image?time=${user.update_at}`}
height='36'
width='36'
/>
<div className='more-modal__details'>
<div className='more-modal__name'>{Utils.getDisplayName(user)}</div>
- <div className='more-modal__description'>{email}</div>
+ <div className='more-modal__description'>
+ <FormattedHTMLMessage
+ id='admin.user_item.emailTitle'
+ defaultMessage='<strong>Email:</strong> {email}'
+ values={{
+ email
+ }}
+ />
+ {authServiceText}
+ {mfaActiveText}
+ </div>
</div>
<div className='more-modal__actions'>
<div className='dropdown member-drop'>
@@ -397,6 +473,7 @@ export default class UserItem extends React.Component {
{makeActive}
{makeNotActive}
{makeSystemAdmin}
+ {mfaReset}
<li role='presentation'>
<a
role='menuitem'
diff --git a/webapp/components/authorize.jsx b/webapp/components/authorize.jsx
index 01b37f439..b18469568 100644
--- a/webapp/components/authorize.jsx
+++ b/webapp/components/authorize.jsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
diff --git a/webapp/components/backstage/add_command.jsx b/webapp/components/backstage/add_command.jsx
index 2eb7bdb21..ba9ac4e79 100644
--- a/webapp/components/backstage/add_command.jsx
+++ b/webapp/components/backstage/add_command.jsx
@@ -89,6 +89,8 @@ export default class AddCommand extends React.Component {
/>
)
});
+
+ return;
}
if (!command.url) {
@@ -101,12 +103,14 @@ export default class AddCommand extends React.Component {
/>
)
});
+
+ return;
}
AsyncClient.addCommand(
command,
() => {
- browserHistory.push('/settings/integrations/commands');
+ browserHistory.push('/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands');
},
(err) => {
this.setState({
@@ -251,7 +255,7 @@ export default class AddCommand extends React.Component {
return (
<div className='backstage-content row'>
<BackstageHeader>
- <Link to={'/settings/integrations/commands'}>
+ <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'}>
<FormattedMessage
id='installed_command.header'
defaultMessage='Slash Commands'
@@ -482,7 +486,7 @@ export default class AddCommand extends React.Component {
<FormError errors={[this.state.serverError, this.state.clientError]}/>
<Link
className='btn btn-sm'
- to={'/settings/integrations/commands'}
+ to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'}
>
<FormattedMessage
id='add_command.cancel'
diff --git a/webapp/components/backstage/add_incoming_webhook.jsx b/webapp/components/backstage/add_incoming_webhook.jsx
index f68a263be..0f0d49ea7 100644
--- a/webapp/components/backstage/add_incoming_webhook.jsx
+++ b/webapp/components/backstage/add_incoming_webhook.jsx
@@ -5,6 +5,7 @@ import React from 'react';
import * as AsyncClient from 'utils/async_client.jsx';
import {browserHistory} from 'react-router';
+import * as Utils from 'utils/utils.jsx';
import BackstageHeader from './backstage_header.jsx';
import ChannelSelect from 'components/channel_select.jsx';
@@ -69,7 +70,7 @@ export default class AddIncomingWebhook extends React.Component {
AsyncClient.addIncomingHook(
hook,
() => {
- browserHistory.push('/settings/integrations/incoming_webhooks');
+ browserHistory.push('/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks');
},
(err) => {
this.setState({
@@ -102,7 +103,7 @@ export default class AddIncomingWebhook extends React.Component {
return (
<div className='backstage-content'>
<BackstageHeader>
- <Link to={'/settings/integrations/incoming_webhooks'}>
+ <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks'}>
<FormattedMessage
id='installed_incoming_webhooks.header'
defaultMessage='Incoming Webhooks'
diff --git a/webapp/components/backstage/add_outgoing_webhook.jsx b/webapp/components/backstage/add_outgoing_webhook.jsx
index ff5e90e07..245df1604 100644
--- a/webapp/components/backstage/add_outgoing_webhook.jsx
+++ b/webapp/components/backstage/add_outgoing_webhook.jsx
@@ -5,6 +5,7 @@ import React from 'react';
import * as AsyncClient from 'utils/async_client.jsx';
import {browserHistory} from 'react-router';
+import * as Utils from 'utils/utils.jsx';
import BackstageHeader from './backstage_header.jsx';
import ChannelSelect from 'components/channel_select.jsx';
@@ -109,7 +110,7 @@ export default class AddOutgoingWebhook extends React.Component {
AsyncClient.addOutgoingHook(
hook,
() => {
- browserHistory.push('/settings/integrations/outgoing_webhooks');
+ browserHistory.push('/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks');
},
(err) => {
this.setState({
@@ -154,7 +155,7 @@ export default class AddOutgoingWebhook extends React.Component {
return (
<div className='backstage-content'>
<BackstageHeader>
- <Link to={'/settings/integrations/outgoing_webhooks'}>
+ <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'}>
<FormattedMessage
id='installed_outgoing_webhooks.header'
defaultMessage='Outgoing Webhooks'
@@ -273,7 +274,7 @@ export default class AddOutgoingWebhook extends React.Component {
<FormError errors={[this.state.serverError, this.state.clientError]}/>
<Link
className='btn btn-sm'
- to={'/settings/integrations/outgoing_webhooks'}
+ to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'}
>
<FormattedMessage
id='add_outgoing_webhook.cancel'
diff --git a/webapp/components/backstage/backstage_navbar.jsx b/webapp/components/backstage/backstage_navbar.jsx
index d2d2da1ed..8352296b7 100644
--- a/webapp/components/backstage/backstage_navbar.jsx
+++ b/webapp/components/backstage/backstage_navbar.jsx
@@ -46,7 +46,7 @@ export default class BackstageNavbar extends React.Component {
<div className='backstage-navbar row'>
<Link
className='backstage-navbar__back'
- to={`/${this.state.team.display_name}/channels/town-square`}
+ to={`/${this.state.team.name}/channels/town-square`}
>
<i className='fa fa-angle-left'/>
<span>
diff --git a/webapp/components/backstage/backstage_sidebar.jsx b/webapp/components/backstage/backstage_sidebar.jsx
index eb84709a3..6f8e0b86a 100644
--- a/webapp/components/backstage/backstage_sidebar.jsx
+++ b/webapp/components/backstage/backstage_sidebar.jsx
@@ -3,6 +3,7 @@
import React from 'react';
+import * as Utils from 'utils/utils.jsx';
import BackstageCategory from './backstage_category.jsx';
import BackstageSection from './backstage_section.jsx';
import {FormattedMessage} from 'react-intl';
@@ -14,7 +15,7 @@ export default class BackstageSidebar extends React.Component {
<ul>
<BackstageCategory
name='integrations'
- parentLink={'/settings'}
+ parentLink={'/' + Utils.getTeamNameFromUrl() + '/settings'}
icon='fa-link'
title={
<FormattedMessage
diff --git a/webapp/components/backstage/installed_commands.jsx b/webapp/components/backstage/installed_commands.jsx
index 3527a574b..8b0cd59c8 100644
--- a/webapp/components/backstage/installed_commands.jsx
+++ b/webapp/components/backstage/installed_commands.jsx
@@ -5,6 +5,7 @@ import React from 'react';
import * as AsyncClient from 'utils/async_client.jsx';
import IntegrationStore from 'stores/integration_store.jsx';
+import * as Utils from 'utils/utils.jsx';
import {FormattedMessage} from 'react-intl';
import InstalledCommand from './installed_command.jsx';
@@ -84,7 +85,7 @@ export default class InstalledCommands extends React.Component {
defaultMessage='Add Slash Command'
/>
}
- addLink='/settings/integrations/commands/add'
+ addLink={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands/add'}
>
{commands}
</InstalledIntegrations>
diff --git a/webapp/components/backstage/installed_incoming_webhook.jsx b/webapp/components/backstage/installed_incoming_webhook.jsx
index 58d318310..afa6e9958 100644
--- a/webapp/components/backstage/installed_incoming_webhook.jsx
+++ b/webapp/components/backstage/installed_incoming_webhook.jsx
@@ -90,6 +90,17 @@ export default class InstalledIncomingWebhook extends React.Component {
</span>
</div>
{description}
+ <div className='item-details__row'>
+ <span className='item-details__url'>
+ <FormattedMessage
+ id='installed_integrations.url'
+ defaultMessage='URL: {url}'
+ values={{
+ url: Utils.getWindowLocationOrigin() + '/hooks/' + incomingWebhook.id
+ }}
+ />
+ </span>
+ </div>
<div className='tem-details__row'>
<span className='item-details__creation'>
<FormattedMessage
diff --git a/webapp/components/backstage/installed_incoming_webhooks.jsx b/webapp/components/backstage/installed_incoming_webhooks.jsx
index de7154afe..0d6f900d1 100644
--- a/webapp/components/backstage/installed_incoming_webhooks.jsx
+++ b/webapp/components/backstage/installed_incoming_webhooks.jsx
@@ -5,6 +5,7 @@ import React from 'react';
import * as AsyncClient from 'utils/async_client.jsx';
import IntegrationStore from 'stores/integration_store.jsx';
+import * as Utils from 'utils/utils.jsx';
import {FormattedMessage} from 'react-intl';
import InstalledIncomingWebhook from './installed_incoming_webhook.jsx';
@@ -76,7 +77,7 @@ export default class InstalledIncomingWebhooks extends React.Component {
defaultMessage='Add Incoming Webhook'
/>
}
- addLink='/settings/integrations/incoming_webhooks/add'
+ addLink={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks/add'}
>
{incomingWebhooks}
</InstalledIntegrations>
diff --git a/webapp/components/backstage/installed_outgoing_webhooks.jsx b/webapp/components/backstage/installed_outgoing_webhooks.jsx
index 15d927a41..98992b081 100644
--- a/webapp/components/backstage/installed_outgoing_webhooks.jsx
+++ b/webapp/components/backstage/installed_outgoing_webhooks.jsx
@@ -5,6 +5,7 @@ import React from 'react';
import * as AsyncClient from 'utils/async_client.jsx';
import IntegrationStore from 'stores/integration_store.jsx';
+import * as Utils from 'utils/utils.jsx';
import {FormattedMessage} from 'react-intl';
import InstalledOutgoingWebhook from './installed_outgoing_webhook.jsx';
@@ -82,7 +83,7 @@ export default class InstalledOutgoingWebhooks extends React.Component {
defaultMessage='Add Outgoing Webhook'
/>
}
- addLink='/settings/integrations/outgoing_webhooks/add'
+ addLink={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks/add'}
>
{outgoingWebhooks}
</InstalledIntegrations>
diff --git a/webapp/components/backstage/integrations.jsx b/webapp/components/backstage/integrations.jsx
index 71232ea45..fdd75026a 100644
--- a/webapp/components/backstage/integrations.jsx
+++ b/webapp/components/backstage/integrations.jsx
@@ -5,6 +5,7 @@ import React from 'react';
import {FormattedMessage} from 'react-intl';
import IntegrationOption from './integration_option.jsx';
+import * as Utils from 'utils/utils.jsx';
import WebhookIcon from 'images/webhook_icon.jpg';
@@ -29,7 +30,7 @@ export default class Integrations extends React.Component {
defaultMessage='Incoming webhooks allow external integrations to send messages'
/>
}
- link={'/settings/integrations/incoming_webhooks'}
+ link={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks'}
/>
);
}
@@ -51,7 +52,7 @@ export default class Integrations extends React.Component {
defaultMessage='Outgoing webhooks allow external integrations to receive and respond to messages'
/>
}
- link={'/settings/integrations/outgoing_webhooks'}
+ link={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'}
/>
);
}
@@ -73,7 +74,7 @@ export default class Integrations extends React.Component {
defaultMessage='Slash commands send events to an external integration'
/>
}
- link={'/settings/integrations/commands'}
+ link={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'}
/>
);
}
diff --git a/webapp/components/channel_header.jsx b/webapp/components/channel_header.jsx
index 16d9ea536..c82f59299 100644
--- a/webapp/components/channel_header.jsx
+++ b/webapp/components/channel_header.jsx
@@ -25,7 +25,7 @@ import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import * as Utils from 'utils/utils.jsx';
import * as TextFormatting from 'utils/text_formatting.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import Constants from 'utils/constants.jsx';
import {FormattedMessage} from 'react-intl';
diff --git a/webapp/components/channel_info_modal.jsx b/webapp/components/channel_info_modal.jsx
index c7f9f9f79..4c16dda90 100644
--- a/webapp/components/channel_info_modal.jsx
+++ b/webapp/components/channel_info_modal.jsx
@@ -3,33 +3,26 @@
import * as Utils from 'utils/utils.jsx';
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
-
+import {FormattedMessage} from 'react-intl';
import {Modal} from 'react-bootstrap';
-const holders = defineMessages({
- notFound: {
- id: 'channel_info.notFound',
- defaultMessage: 'No Channel Found'
- }
-});
-
import React from 'react';
-class ChannelInfoModal extends React.Component {
+export default class ChannelInfoModal extends React.Component {
render() {
- const {formatMessage} = this.props.intl;
let channel = this.props.channel;
if (!channel) {
+ const notFound = Utils.localizeMessage('channel_info.notFound', 'No Channel Found');
+
channel = {
- display_name: formatMessage(holders.notFound),
- name: formatMessage(holders.notFound),
- purpose: formatMessage(holders.notFound),
- id: formatMessage(holders.notFound)
+ display_name: notFound,
+ name: notFound,
+ purpose: notFound,
+ id: notFound
};
}
- const channelURL = Utils.getShortenedTeamURL() + channel.name;
+ const channelURL = Utils.getTeamURLFromAddressBar() + '/channels/' + channel.name;
return (
<Modal
@@ -97,10 +90,7 @@ class ChannelInfoModal extends React.Component {
}
ChannelInfoModal.propTypes = {
- intl: intlShape.isRequired,
show: React.PropTypes.bool.isRequired,
onHide: React.PropTypes.func.isRequired,
channel: React.PropTypes.object.isRequired
-};
-
-export default injectIntl(ChannelInfoModal); \ No newline at end of file
+}; \ No newline at end of file
diff --git a/webapp/components/channel_invite_button.jsx b/webapp/components/channel_invite_button.jsx
index 1fcd461ea..ed013bb26 100644
--- a/webapp/components/channel_invite_button.jsx
+++ b/webapp/components/channel_invite_button.jsx
@@ -4,7 +4,7 @@
import React from 'react';
import * as AsyncClient from 'utils/async_client.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import {FormattedMessage} from 'react-intl';
import SpinnerButton from 'components/spinner_button.jsx';
@@ -37,13 +37,9 @@ export default class ChannelInviteButton extends React.Component {
addingUser: true
});
- const data = {
- user_id: this.props.user.id
- };
-
Client.addChannelMember(
this.props.channel.id,
- data,
+ this.props.user.id,
() => {
this.setState({
addingUser: false
diff --git a/webapp/components/channel_members_modal.jsx b/webapp/components/channel_members_modal.jsx
index 67be2ef50..ecea891bb 100644
--- a/webapp/components/channel_members_modal.jsx
+++ b/webapp/components/channel_members_modal.jsx
@@ -9,7 +9,7 @@ import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import {FormattedMessage} from 'react-intl';
@@ -97,12 +97,9 @@ export default class ChannelMembersModal extends React.Component {
handleRemove(user) {
const userId = user.id;
- const data = {};
- data.user_id = userId;
-
Client.removeChannelMember(
ChannelStore.getCurrentId(),
- data,
+ userId,
() => {
const memberList = this.state.memberList.slice();
for (let i = 0; i < memberList.length; i++) {
diff --git a/webapp/components/channel_notifications_modal.jsx b/webapp/components/channel_notifications_modal.jsx
index 564776876..112c07ad0 100644
--- a/webapp/components/channel_notifications_modal.jsx
+++ b/webapp/components/channel_notifications_modal.jsx
@@ -6,7 +6,7 @@ import {Modal} from 'react-bootstrap';
import SettingItemMin from './setting_item_min.jsx';
import SettingItemMax from './setting_item_max.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import {FormattedMessage} from 'react-intl';
@@ -60,7 +60,7 @@ export default class ChannelNotificationsModal extends React.Component {
data.desktop = notifyLevel;
//TODO: This should be moved to event_helpers
- Client.updateNotifyProps(data,
+ Client.updateChannelNotifyProps(data,
() => {
// YUCK
var member = ChannelStore.getMember(channelId);
@@ -252,7 +252,7 @@ export default class ChannelNotificationsModal extends React.Component {
};
//TODO: This should be fixed, moved to event_helpers
- Client.updateNotifyProps(data,
+ Client.updateChannelNotifyProps(data,
() => {
// Yuck...
var member = ChannelStore.getMember(channelId);
diff --git a/webapp/components/channel_select.jsx b/webapp/components/channel_select.jsx
index 8622d1f57..238cfa1ae 100644
--- a/webapp/components/channel_select.jsx
+++ b/webapp/components/channel_select.jsx
@@ -54,7 +54,7 @@ export default class ChannelSelect extends React.Component {
];
this.state.channels.forEach((channel) => {
- if (channel.type !== Constants.DM_CHANNEL) {
+ if (channel.type === Constants.OPEN_CHANNEL) {
options.push(
<option
key={channel.id}
diff --git a/webapp/components/claim/claim.jsx b/webapp/components/claim/claim.jsx
index 5cfb04af3..0197e1677 100644
--- a/webapp/components/claim/claim.jsx
+++ b/webapp/components/claim/claim.jsx
@@ -1,8 +1,6 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import TeamStore from 'stores/team_store.jsx';
-
import React from 'react';
import {FormattedMessage} from 'react-intl';
import {Link} from 'react-router';
@@ -13,40 +11,15 @@ export default class Claim extends React.Component {
constructor(props) {
super(props);
- this.onTeamChange = this.onTeamChange.bind(this);
- this.updateStateFromStores = this.updateStateFromStores.bind(this);
-
this.state = {};
}
componentWillMount() {
this.setState({
email: this.props.location.query.email,
newType: this.props.location.query.new_type,
- oldType: this.props.location.query.old_type,
- teamName: this.props.params.team,
- teamDisplayName: ''
- });
- this.updateStateFromStores();
- }
- componentDidMount() {
- TeamStore.addChangeListener(this.onTeamChange);
- }
- componentWillUnmount() {
- TeamStore.removeChangeListener(this.onTeamChange);
- }
- updateStateFromStores() {
- const team = TeamStore.getByName(this.state.teamName);
- let displayName = '';
- if (team) {
- displayName = team.display_name;
- }
- this.setState({
- teamDisplayName: displayName
+ oldType: this.props.location.query.old_type
});
}
- onTeamChange() {
- this.updateStateFromStores();
- }
render() {
return (
<div>
@@ -66,8 +39,6 @@ export default class Claim extends React.Component {
/>
<div id='claim'>
{React.cloneElement(this.props.children, {
- teamName: this.state.teamName,
- teamDisplayName: this.state.teamDisplayName,
currentType: this.state.oldType,
newType: this.state.newType,
email: this.state.email
@@ -83,7 +54,6 @@ export default class Claim extends React.Component {
Claim.defaultProps = {
};
Claim.propTypes = {
- params: React.PropTypes.object.isRequired,
location: React.PropTypes.object.isRequired,
children: React.PropTypes.node
};
diff --git a/webapp/components/claim/components/email_to_ldap.jsx b/webapp/components/claim/components/email_to_ldap.jsx
index 1ceb42a27..fbf26cade 100644
--- a/webapp/components/claim/components/email_to_ldap.jsx
+++ b/webapp/components/claim/components/email_to_ldap.jsx
@@ -2,12 +2,11 @@
// See License.txt for license information.
import * as Utils from 'utils/utils.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import React from 'react';
import ReactDOM from 'react-dom';
import {FormattedMessage} from 'react-intl';
-import {browserHistory} from 'react-router';
export default class EmailToLDAP extends React.Component {
constructor(props) {
@@ -45,17 +44,14 @@ export default class EmailToLDAP extends React.Component {
state.error = null;
this.setState(state);
- var postData = {};
- postData.email_password = password;
- postData.ldap_id = ldapId;
- postData.ldap_password = ldapPassword;
- postData.email = this.props.email;
- postData.team_name = this.props.teamName;
-
- Client.emailToLDAP(postData,
+ Client.emailToLdap(
+ this.props.email,
+ password,
+ ldapId,
+ ldapPassword,
(data) => {
if (data.follow_link) {
- browserHistory.push(data.follow_link);
+ window.location.href = data.follow_link;
}
},
(error) => {
@@ -74,6 +70,20 @@ export default class EmailToLDAP extends React.Component {
formClass += ' has-error';
}
+ let loginPlaceholder;
+ if (global.window.mm_config.LdapLoginFieldName) {
+ loginPlaceholder = global.window.mm_config.LdapLoginFieldName;
+ } else {
+ loginPlaceholder = Utils.localizeMessage('claim.email_to_ldap.ldapId', 'LDAP ID');
+ }
+
+ let passwordPlaceholder;
+ if (global.window.mm_config.LdapPasswordFieldName) {
+ passwordPlaceholder = global.window.mm_config.LdapPasswordFieldName;
+ } else {
+ passwordPlaceholder = Utils.localizeMessage('claim.email_to_ldap.ldapPwd', 'LDAP Password');
+ }
+
return (
<div>
<h3>
@@ -98,9 +108,8 @@ export default class EmailToLDAP extends React.Component {
<p>
<FormattedMessage
id='claim.email_to_ldap.enterPwd'
- defaultMessage='Enter the password for your {team} {site} email account'
+ defaultMessage='Enter the password for your {site} email account'
values={{
- team: this.props.teamDisplayName,
site: global.window.mm_config.SiteName
}}
/>
@@ -125,10 +134,6 @@ export default class EmailToLDAP extends React.Component {
<FormattedMessage
id='claim.email_to_ldap.enterLdapPwd'
defaultMessage='Enter the ID and password for your LDAP account'
- values={{
- team: this.props.teamDisplayName,
- site: global.window.mm_config.SiteName
- }}
/>
</p>
<div className={formClass}>
@@ -138,7 +143,7 @@ export default class EmailToLDAP extends React.Component {
name='ldapId'
ref='ldapid'
autoComplete='off'
- placeholder={Utils.localizeMessage('claim.email_to_ldap.ldapId', 'LDAP ID')}
+ placeholder={loginPlaceholder}
spellCheck='false'
/>
</div>
@@ -149,7 +154,7 @@ export default class EmailToLDAP extends React.Component {
name='ldapPassword'
ref='ldappassword'
autoComplete='off'
- placeholder={Utils.localizeMessage('claim.email_to_ldap.ldapPwd', 'LDAP Password')}
+ placeholder={passwordPlaceholder}
spellCheck='false'
/>
</div>
@@ -172,7 +177,5 @@ export default class EmailToLDAP extends React.Component {
EmailToLDAP.defaultProps = {
};
EmailToLDAP.propTypes = {
- email: React.PropTypes.string,
- teamName: React.PropTypes.string,
- teamDisplayName: React.PropTypes.string
+ email: React.PropTypes.string
};
diff --git a/webapp/components/claim/components/email_to_oauth.jsx b/webapp/components/claim/components/email_to_oauth.jsx
index f4376067a..1fd284bed 100644
--- a/webapp/components/claim/components/email_to_oauth.jsx
+++ b/webapp/components/claim/components/email_to_oauth.jsx
@@ -2,12 +2,11 @@
// See License.txt for license information.
import * as Utils from 'utils/utils.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import React from 'react';
import ReactDOM from 'react-dom';
import {FormattedMessage} from 'react-intl';
-import {browserHistory} from 'react-router';
export default class EmailToOAuth extends React.Component {
constructor(props) {
@@ -31,16 +30,13 @@ export default class EmailToOAuth extends React.Component {
state.error = null;
this.setState(state);
- var postData = {};
- postData.password = password;
- postData.email = this.props.email;
- postData.team_name = this.props.teamName;
- postData.service = this.props.newType;
-
- Client.emailToOAuth(postData,
+ Client.emailToOAuth(
+ this.props.email,
+ password,
+ this.props.newType,
(data) => {
if (data.follow_link) {
- browserHistory.push(data.follow_link);
+ window.location.href = data.follow_link;
}
},
(error) => {
@@ -94,9 +90,8 @@ export default class EmailToOAuth extends React.Component {
<p>
<FormattedMessage
id='claim.email_to_oauth.enterPwd'
- defaultMessage='Enter the password for your {team} {site} account'
+ defaultMessage='Enter the password for your {site} account'
values={{
- team: this.props.teamDisplayName,
site: global.window.mm_config.SiteName
}}
/>
@@ -134,7 +129,5 @@ EmailToOAuth.defaultProps = {
};
EmailToOAuth.propTypes = {
newType: React.PropTypes.string,
- email: React.PropTypes.string,
- teamName: React.PropTypes.string,
- teamDisplayName: React.PropTypes.string
+ email: React.PropTypes.string
};
diff --git a/webapp/components/claim/components/ldap_to_email.jsx b/webapp/components/claim/components/ldap_to_email.jsx
index ed8a314bd..a10cefd6f 100644
--- a/webapp/components/claim/components/ldap_to_email.jsx
+++ b/webapp/components/claim/components/ldap_to_email.jsx
@@ -2,12 +2,11 @@
// See License.txt for license information.
import * as Utils from 'utils/utils.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import React from 'react';
import ReactDOM from 'react-dom';
import {FormattedMessage} from 'react-intl';
-import {browserHistory} from 'react-router';
export default class LDAPToEmail extends React.Component {
constructor(props) {
@@ -45,16 +44,13 @@ export default class LDAPToEmail extends React.Component {
state.error = null;
this.setState(state);
- var postData = {};
- postData.email_password = password;
- postData.ldap_password = ldapPassword;
- postData.email = this.props.email;
- postData.team_name = this.props.teamName;
-
- Client.ldapToEmail(postData,
+ Client.ldapToEmail(
+ this.props.email,
+ password,
+ ldapPassword,
(data) => {
if (data.follow_link) {
- browserHistory.push(data.follow_link);
+ window.location.href = data.follow_link;
}
},
(error) => {
@@ -73,6 +69,13 @@ export default class LDAPToEmail extends React.Component {
formClass += ' has-error';
}
+ let passwordPlaceholder;
+ if (global.window.mm_config.LdapPasswordFieldName) {
+ passwordPlaceholder = global.window.mm_config.LdapPasswordFieldName;
+ } else {
+ passwordPlaceholder = Utils.localizeMessage('claim.ldap_to_email.ldapPwd', 'LDAP Password');
+ }
+
return (
<div>
<h3>
@@ -100,9 +103,9 @@ export default class LDAPToEmail extends React.Component {
<p>
<FormattedMessage
id='claim.ldap_to_email.enterLdapPwd'
- defaultMessage='Enter your LDAP password for your {team} {site} email account'
+ defaultMessage='Enter your {ldapPassword} for your {site} email account'
values={{
- team: this.props.teamDisplayName,
+ ldapPassword: passwordPlaceholder,
site: global.window.mm_config.SiteName
}}
/>
@@ -113,7 +116,7 @@ export default class LDAPToEmail extends React.Component {
className='form-control'
name='ldapPassword'
ref='ldappassword'
- placeholder={Utils.localizeMessage('claim.ldap_to_email.ldapPwd', 'LDAP Password')}
+ placeholder={passwordPlaceholder}
spellCheck='false'
/>
</div>
@@ -162,7 +165,5 @@ export default class LDAPToEmail extends React.Component {
LDAPToEmail.defaultProps = {
};
LDAPToEmail.propTypes = {
- email: React.PropTypes.string,
- teamName: React.PropTypes.string,
- teamDisplayName: React.PropTypes.string
+ email: React.PropTypes.string
};
diff --git a/webapp/components/claim/components/oauth_to_email.jsx b/webapp/components/claim/components/oauth_to_email.jsx
index 72e0500a9..7fd18aaa6 100644
--- a/webapp/components/claim/components/oauth_to_email.jsx
+++ b/webapp/components/claim/components/oauth_to_email.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import * as Utils from 'utils/utils.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import React from 'react';
import ReactDOM from 'react-dom';
@@ -38,12 +38,9 @@ export default class OAuthToEmail extends React.Component {
state.error = null;
this.setState(state);
- var postData = {};
- postData.password = password;
- postData.email = this.props.email;
- postData.team_name = this.props.teamName;
-
- Client.oauthToEmail(postData,
+ Client.oauthToEmail(
+ this.props.email,
+ password,
(data) => {
if (data.follow_link) {
browserHistory.push(data.follow_link);
@@ -87,10 +84,9 @@ export default class OAuthToEmail extends React.Component {
</p>
<p>
<FormattedMessage
- id='claim.oauth_to_email_newPwd'
- defaultMessage='Enter a new password for your {team} {site} account'
+ id='claim.oauth_to_email.enterNewPwd'
+ defaultMessage='Enter a new password for your {site} account'
values={{
- team: this.props.teamDisplayName,
site: global.window.mm_config.SiteName
}}
/>
@@ -137,8 +133,6 @@ export default class OAuthToEmail extends React.Component {
OAuthToEmail.defaultProps = {
};
OAuthToEmail.propTypes = {
- teamName: React.PropTypes.string,
- teamDisplayName: React.PropTypes.string,
currentType: React.PropTypes.string,
email: React.PropTypes.string
};
diff --git a/webapp/components/create_comment.jsx b/webapp/components/create_comment.jsx
index a91c03d58..e8fa59165 100644
--- a/webapp/components/create_comment.jsx
+++ b/webapp/components/create_comment.jsx
@@ -4,7 +4,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import UserStore from 'stores/user_store.jsx';
@@ -148,7 +148,6 @@ class CreateComment extends React.Component {
Client.createPost(
post,
- ChannelStore.getCurrent(),
(data) => {
AsyncClient.getPosts(this.props.channelId);
diff --git a/webapp/components/create_post.jsx b/webapp/components/create_post.jsx
index 232537d8b..9bbc44f38 100644
--- a/webapp/components/create_post.jsx
+++ b/webapp/components/create_post.jsx
@@ -11,7 +11,7 @@ import TutorialTip from './tutorial/tutorial_tip.jsx';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
@@ -172,7 +172,7 @@ class CreatePost extends React.Component {
GlobalActions.emitUserPostedEvent(post);
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
- Client.createPost(post, channel,
+ Client.createPost(post,
(data) => {
AsyncClient.getPosts();
diff --git a/webapp/components/signup_team_complete/components/team_signup_display_name_page.jsx b/webapp/components/create_team/components/display_name.jsx
index 111fc6835..e33eee1bc 100644
--- a/webapp/components/signup_team_complete/components/team_signup_display_name_page.jsx
+++ b/webapp/components/create_team/components/display_name.jsx
@@ -3,7 +3,8 @@
import ReactDOM from 'react-dom';
import * as utils from 'utils/utils.jsx';
-import * as client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
+import {Link} from 'react-router';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
@@ -11,11 +12,11 @@ import logoImage from 'images/logo.png';
const holders = defineMessages({
required: {
- id: 'team_signup_display_name.required',
+ id: 'create_team_display_name.required',
defaultMessage: 'This field is required'
},
charLength: {
- id: 'team_signup_display_name.charLength',
+ id: 'create_team_display_name.charLength',
defaultMessage: 'Name must be 4 or more characters up to a maximum of 15'
}
});
@@ -26,16 +27,11 @@ class TeamSignupDisplayNamePage extends React.Component {
constructor(props) {
super(props);
- this.submitBack = this.submitBack.bind(this);
this.submitNext = this.submitNext.bind(this);
this.state = {};
}
- submitBack(e) {
- e.preventDefault();
- this.props.state.wizard = 'welcome';
- this.props.updateParent(this.props.state);
- }
+
submitNext(e) {
e.preventDefault();
@@ -54,12 +50,14 @@ class TeamSignupDisplayNamePage extends React.Component {
this.props.state.team.name = utils.cleanUpUrlable(displayName);
this.props.updateParent(this.props.state);
}
+
handleFocus(e) {
e.preventDefault();
e.currentTarget.select();
}
+
render() {
- client.track('signup', 'signup_team_02_name');
+ Client.track('signup', 'signup_team_02_name');
var nameError = null;
var nameDivClass = 'form-group';
@@ -77,7 +75,7 @@ class TeamSignupDisplayNamePage extends React.Component {
/>
<h2>
<FormattedMessage
- id='team_signup_display_name.teamName'
+ id='create_team_display_name.teamName'
defaultMessage='Team Name'
/>
</h2>
@@ -101,7 +99,7 @@ class TeamSignupDisplayNamePage extends React.Component {
</div>
<div>
<FormattedMessage
- id='team_signup_display_name.nameHelp'
+ id='create_team_display_name.nameHelp'
defaultMessage='Name your team in any language. Your team name shows in menus and headings.'
/>
</div>
@@ -111,20 +109,17 @@ class TeamSignupDisplayNamePage extends React.Component {
onClick={this.submitNext}
>
<FormattedMessage
- id='team_signup_display_name.next'
+ id='create_team_display_name.next'
defaultMessage='Next'
/><i className='glyphicon glyphicon-chevron-right'></i>
</button>
<div className='margin--extra'>
- <a
- href='#'
- onClick={this.submitBack}
- >
+ <Link to='/select_team'>
<FormattedMessage
- id='team_signup_display_name.back'
+ id='create_team_display_name.back'
defaultMessage='Back to previous step'
/>
- </a>
+ </Link>
</div>
</form>
</div>
diff --git a/webapp/components/signup_team_complete/components/team_signup_url_page.jsx b/webapp/components/create_team/components/team_url.jsx
index b2ab57285..025103141 100644
--- a/webapp/components/signup_team_complete/components/team_signup_url_page.jsx
+++ b/webapp/components/create_team/components/team_url.jsx
@@ -4,8 +4,11 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
import * as Utils from 'utils/utils.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
+import TeamStore from 'stores/team_store.jsx';
+import UserStore from 'stores/user_store.jsx';
import Constants from 'utils/constants.jsx';
+import {browserHistory} from 'react-router';
import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
@@ -13,30 +16,30 @@ import logoImage from 'images/logo.png';
const holders = defineMessages({
required: {
- id: 'team_signup_url.required',
+ id: 'create_team_url.required',
defaultMessage: 'This field is required'
},
regex: {
- id: 'team_signup_url.regex',
+ id: 'create_team_url.regex',
defaultMessage: "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash."
},
charLength: {
- id: 'team_signup_url.charLength',
+ id: 'create_team_url.charLength',
defaultMessage: 'Name must be 4 or more characters up to a maximum of 15'
},
taken: {
- id: 'team_signup_url.taken',
+ id: 'create_team_url.taken',
defaultMessage: 'URL is taken or contains a reserved word'
},
unavailable: {
- id: 'team_signup_url.unavailable',
+ id: 'create_team_url.unavailable',
defaultMessage: 'This URL is unavailable. Please try another.'
}
});
import React from 'react';
-class TeamSignupUrlPage extends React.Component {
+class TeamUrl extends React.Component {
constructor(props) {
super(props);
@@ -48,7 +51,7 @@ class TeamSignupUrlPage extends React.Component {
}
submitBack(e) {
e.preventDefault();
- this.props.state.wizard = 'team_display_name';
+ this.props.state.wizard = 'display_name';
this.props.updateParent(this.props.state);
}
submitNext(e) {
@@ -81,25 +84,39 @@ class TeamSignupUrlPage extends React.Component {
}
}
+ $('#finish-button').button('loading');
+ var teamSignup = JSON.parse(JSON.stringify(this.props.state));
+ teamSignup.team.type = 'O';
+ teamSignup.team.name = name;
+
Client.findTeamByName(name,
- (data) => {
- if (data) {
- this.setState({nameError: formatMessage(holders.unavailable)});
- } else {
- if (global.window.mm_config.SendEmailNotifications === 'true') {
- this.props.state.wizard = 'send_invites';
- } else {
- this.props.state.wizard = 'username';
- }
- this.props.state.team.type = 'O';
-
- this.props.state.team.name = name;
- this.props.updateParent(this.props.state);
- }
- },
- (err) => {
- this.setState({nameError: err.message});
- }
+ (findTeam) => {
+ if (findTeam) {
+ this.setState({nameError: formatMessage(holders.unavailable)});
+ $('#finish-button').button('reset');
+ } else {
+ Client.createTeam(teamSignup.team,
+ (team) => {
+ Client.track('signup', 'signup_team_08_complete');
+ $('#sign-up-button').button('reset');
+ TeamStore.saveTeam(team);
+ TeamStore.appendTeamMember({team_id: team.id, user_id: UserStore.getCurrentId(), roles: 'admin'});
+ TeamStore.emitChange();
+ browserHistory.push('/' + team.name + '/channels/town-square');
+ },
+ (err) => {
+ this.setState({nameError: err.message});
+ $('#finish-button').button('reset');
+ }
+ );
+
+ $('#finish-button').button('reset');
+ }
+ },
+ (err) => {
+ this.setState({nameError: err.message});
+ $('#finish-button').button('reset');
+ }
);
}
handleFocus(e) {
@@ -130,7 +147,7 @@ class TeamSignupUrlPage extends React.Component {
/>
<h2>
<FormattedMessage
- id='team_signup_url.teamUrl'
+ id='create_team_url.teamUrl'
defaultMessage='Team URL'
/>
</h2>
@@ -163,13 +180,13 @@ class TeamSignupUrlPage extends React.Component {
</div>
<p>
<FormattedMessage
- id='team_signup_url.webAddress'
+ id='create_team_url.webAddress'
defaultMessage='Choose the web address of your new team:'
/>
</p>
<ul className='color--light'>
<FormattedHTMLMessage
- id='team_signup_url.hint'
+ id='create_team_url.hint'
defaultMessage="<li>Short and memorable is best</li>
<li>Use lowercase letters, numbers and dashes</li>
<li>Must start with a letter and can't end in a dash</li>"
@@ -177,13 +194,14 @@ class TeamSignupUrlPage extends React.Component {
</ul>
<button
type='submit'
+ id='finish-button'
className='btn btn-primary margin--extra'
onClick={this.submitNext}
>
<FormattedMessage
- id='team_signup_url.next'
- defaultMessage='Next'
- /><i className='glyphicon glyphicon-chevron-right'></i>
+ id='create_team_password.finish'
+ defaultMessage='Finish'
+ />
</button>
<div className='margin--extra'>
<a
@@ -191,7 +209,7 @@ class TeamSignupUrlPage extends React.Component {
onClick={this.submitBack}
>
<FormattedMessage
- id='team_signup_url.back'
+ id='create_team_url.back'
defaultMessage='Back to previous step'
/>
</a>
@@ -202,10 +220,10 @@ class TeamSignupUrlPage extends React.Component {
}
}
-TeamSignupUrlPage.propTypes = {
+TeamUrl.propTypes = {
intl: intlShape.isRequired,
state: React.PropTypes.object,
updateParent: React.PropTypes.func
};
-export default injectIntl(TeamSignupUrlPage);
+export default injectIntl(TeamUrl);
diff --git a/webapp/components/create_team/create_team.jsx b/webapp/components/create_team/create_team.jsx
new file mode 100644
index 000000000..8a119a122
--- /dev/null
+++ b/webapp/components/create_team/create_team.jsx
@@ -0,0 +1,72 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import ErrorBar from 'components/error_bar.jsx';
+
+import {FormattedMessage} from 'react-intl';
+import {browserHistory, Link} from 'react-router';
+
+import React from 'react';
+
+export default class CreateTeam extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submit = this.submit.bind(this);
+ this.updateParent = this.updateParent.bind(this);
+
+ const state = {};
+ state.team = {};
+ state.wizard = 'display_name';
+ this.state = state;
+ }
+
+ submit() {
+ // todo fill in
+ }
+
+ componentDidMount() {
+ browserHistory.push('/create_team/display_name');
+ }
+
+ updateParent(state) {
+ this.setState(state);
+ browserHistory.push('/create_team/' + state.wizard);
+ }
+
+ render() {
+ return (
+ <div>
+ <ErrorBar/>
+ <div className='signup-header'>
+ <Link to='/select_team'>
+ <span className='fa fa-chevron-left'/>
+ <FormattedMessage
+ id='web.header.back'
+ />
+ </Link>
+ </div>
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <h1>{global.window.mm_config.SiteName}</h1>
+ <h4 className='color--light'>
+ <FormattedMessage
+ id='web.root.singup_info'
+ />
+ </h4>
+ <div className='signup__content'>
+ {React.cloneElement(this.props.children, {
+ state: this.state,
+ updateParent: this.updateParent
+ })}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+CreateTeam.propTypes = {
+ children: React.PropTypes.node
+};
diff --git a/webapp/components/delete_channel_modal.jsx b/webapp/components/delete_channel_modal.jsx
index 244472a56..222b72c41 100644
--- a/webapp/components/delete_channel_modal.jsx
+++ b/webapp/components/delete_channel_modal.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import * as AsyncClient from 'utils/async_client.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import {Modal} from 'react-bootstrap';
import TeamStore from 'stores/team_store.jsx';
import Constants from 'utils/constants.jsx';
diff --git a/webapp/components/delete_post_modal.jsx b/webapp/components/delete_post_modal.jsx
index 0dbdc2b43..b2aea6590 100644
--- a/webapp/components/delete_post_modal.jsx
+++ b/webapp/components/delete_post_modal.jsx
@@ -3,7 +3,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import PostStore from 'stores/post_store.jsx';
import ModalStore from 'stores/modal_store.jsx';
import {Modal} from 'react-bootstrap';
diff --git a/webapp/components/do_verify_email.jsx b/webapp/components/do_verify_email.jsx
index c3be111ed..fe4d61a72 100644
--- a/webapp/components/do_verify_email.jsx
+++ b/webapp/components/do_verify_email.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import {FormattedMessage} from 'react-intl';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import LoadingScreen from './loading_screen.jsx';
import {browserHistory, Link} from 'react-router';
@@ -25,14 +25,14 @@ export default class DoVerifyEmail extends React.Component {
const email = this.props.location.query.email;
Client.verifyEmail(
+ uid,
+ hid,
() => {
browserHistory.push('/' + teamName + '/login?extra=verified&email=' + email);
},
(err) => {
this.setState({verifyStatus: 'failure', serverError: err.message});
- },
- uid,
- hid
+ }
);
}
render() {
diff --git a/webapp/components/edit_channel_header_modal.jsx b/webapp/components/edit_channel_header_modal.jsx
index 35a5fb9dc..6a7cccebb 100644
--- a/webapp/components/edit_channel_header_modal.jsx
+++ b/webapp/components/edit_channel_header_modal.jsx
@@ -3,7 +3,7 @@
import ReactDOM from 'react-dom';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import Constants from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx';
diff --git a/webapp/components/edit_channel_purpose_modal.jsx b/webapp/components/edit_channel_purpose_modal.jsx
index 31cbdd240..a4779d022 100644
--- a/webapp/components/edit_channel_purpose_modal.jsx
+++ b/webapp/components/edit_channel_purpose_modal.jsx
@@ -4,7 +4,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
import * as AsyncClient from 'utils/async_client.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import Constants from 'utils/constants.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
@@ -49,12 +49,9 @@ export default class EditChannelPurposeModal extends React.Component {
return;
}
- const data = {
- channel_id: this.props.channel.id,
- channel_purpose: ReactDOM.findDOMNode(this.refs.purpose).value.trim()
- };
-
- Client.updateChannelPurpose(data,
+ Client.updateChannelPurpose(
+ this.props.channel.id,
+ ReactDOM.findDOMNode(this.refs.purpose).value.trim(),
() => {
AsyncClient.getChannel(this.props.channel.id);
diff --git a/webapp/components/edit_post_modal.jsx b/webapp/components/edit_post_modal.jsx
index caf9a0ee5..bc67a34f9 100644
--- a/webapp/components/edit_post_modal.jsx
+++ b/webapp/components/edit_post_modal.jsx
@@ -3,7 +3,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
import Textbox from './textbox.jsx';
@@ -33,20 +33,25 @@ class EditPostModal extends React.Component {
this.handleEdit = this.handleEdit.bind(this);
this.handleEditInput = this.handleEditInput.bind(this);
this.handleEditKeyPress = this.handleEditKeyPress.bind(this);
- this.handleUserInput = this.handleUserInput.bind(this);
this.handleEditPostEvent = this.handleEditPostEvent.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.onPreferenceChange = this.onPreferenceChange.bind(this);
- this.state = {editText: '', title: '', post_id: '', channel_id: '', comments: 0, refocusId: ''};
+ this.state = {editText: '', originalText: '', title: '', post_id: '', channel_id: '', comments: 0, refocusId: ''};
}
handleEdit() {
var updatedPost = {};
updatedPost.message = this.state.editText.trim();
+ if (updatedPost.message === this.state.originalText.trim()) {
+ // no changes so just close the modal
+ $('#edit_post').modal('hide');
+ return;
+ }
+
if (updatedPost.message.length === 0) {
var tempState = this.state;
- delete tempState.editText;
+ Reflect.deleteProperty(tempState, 'editText');
BrowserStore.setItem('edit_state_transfer', tempState);
$('#edit_post').modal('hide');
GlobalActions.showDeletePostModal(PostStore.getPost(this.state.channel_id, this.state.post_id), this.state.comments);
@@ -79,12 +84,10 @@ class EditPostModal extends React.Component {
this.handleEdit(e);
}
}
- handleUserInput(e) {
- this.setState({editText: e.target.value});
- }
handleEditPostEvent(options) {
this.setState({
editText: options.message || '',
+ originalText: options.message || '',
title: options.title || '',
post_id: options.postId || '',
channel_id: options.channelId || '',
@@ -108,7 +111,7 @@ class EditPostModal extends React.Component {
var self = this;
$(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', () => {
- self.setState({editText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: ''});
+ self.setState({editText: '', originalText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: ''});
});
$(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', (e) => {
@@ -116,7 +119,15 @@ class EditPostModal extends React.Component {
if (!button) {
return;
}
- self.setState({editText: $(button).attr('data-message'), title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments'), refocusId: $(button).attr('data-refocusid')});
+ self.setState({
+ editText: $(button).attr('data-message'),
+ originalText: $(button).attr('data-message'),
+ title: $(button).attr('data-title'),
+ channel_id: $(button).attr('data-channelid'),
+ post_id: $(button).attr('data-postid'),
+ comments: $(button).attr('data-comments'),
+ refocusId: $(button).attr('data-refocusid')
+ });
});
$(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', () => {
@@ -163,17 +174,17 @@ class EditPostModal extends React.Component {
aria-label='Close'
onClick={this.handleEditClose}
>
- <span aria-hidden='true'>&times;</span>
+ <span aria-hidden='true'>{'×'}</span>
</button>
- <h4 className='modal-title'>
- <FormattedMessage
- id='edit_post.edit'
- defaultMessage='Edit {title}'
- values={{
- title: this.state.title
- }}
- />
- </h4>
+ <h4 className='modal-title'>
+ <FormattedMessage
+ id='edit_post.edit'
+ defaultMessage='Edit {title}'
+ values={{
+ title: this.state.title
+ }}
+ />
+ </h4>
</div>
<div className='edit-modal-body modal-body'>
<Textbox
diff --git a/webapp/components/error_bar.jsx b/webapp/components/error_bar.jsx
index 572f96e02..7257ffe94 100644
--- a/webapp/components/error_bar.jsx
+++ b/webapp/components/error_bar.jsx
@@ -29,8 +29,8 @@ export default class ErrorBar extends React.Component {
}
componentWillMount() {
- if (global.window.mm_config.SendEmailNotifications === 'false') {
- ErrorStore.storeLastError({message: Utils.localizeMessage('error_bar.preview_mode', 'Preview Mode: Email notifications have not been configured')});
+ if (!ErrorStore.getIgnoreEmailPreview() && global.window.mm_config.SendEmailNotifications === 'false') {
+ ErrorStore.storeLastError({email_preview: true, message: Utils.localizeMessage('error_bar.preview_mode', 'Preview Mode: Email notifications have not been configured')});
this.onErrorChange();
}
}
diff --git a/webapp/components/file_attachment.jsx b/webapp/components/file_attachment.jsx
index ccd4070aa..4a040a35b 100644
--- a/webapp/components/file_attachment.jsx
+++ b/webapp/components/file_attachment.jsx
@@ -4,7 +4,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
import * as utils from 'utils/utils.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import Constants from 'utils/constants.jsx';
import {intlShape, injectIntl, defineMessages} from 'react-intl';
@@ -100,7 +100,7 @@ class FileAttachment extends React.Component {
getFileInfoFromName(name) {
var fileInfo = utils.splitFileLocation(name);
- fileInfo.path = utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path;
+ fileInfo.path = utils.getWindowLocationOrigin() + Client.getFilesRoute() + '/get' + fileInfo.path;
return fileInfo;
}
diff --git a/webapp/components/file_upload.jsx b/webapp/components/file_upload.jsx
index 8d2ec5ac8..03a8a8128 100644
--- a/webapp/components/file_upload.jsx
+++ b/webapp/components/file_upload.jsx
@@ -4,7 +4,7 @@
import $ from 'jquery';
import 'jquery-dragster/jquery.dragster.js';
import ReactDOM from 'react-dom';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import Constants from 'utils/constants.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import * as Utils from 'utils/utils.jsx';
@@ -49,9 +49,9 @@ class FileUpload extends React.Component {
fileUploadSuccess(channelId, data) {
this.props.onFileUpload(data.filenames, data.client_ids, channelId);
- var requests = this.state.requests;
+ const requests = JSON.parse(JSON.stringify(this.state.requests));
for (var j = 0; j < data.client_ids.length; j++) {
- delete requests[data.client_ids[j]];
+ Reflect.deleteProperty(requests, data.client_ids[j]);
}
this.setState({requests});
}
@@ -64,13 +64,13 @@ class FileUpload extends React.Component {
// clear any existing errors
this.props.onUploadError(null);
- var channelId = this.props.channelId || ChannelStore.getCurrentId();
+ const channelId = this.props.channelId || ChannelStore.getCurrentId();
- var uploadsRemaining = Constants.MAX_UPLOAD_FILES - this.props.getFileCount(channelId);
- var numUploads = 0;
+ const uploadsRemaining = Constants.MAX_UPLOAD_FILES - this.props.getFileCount(channelId);
+ let numUploads = 0;
// keep track of how many files have been too large
- var tooLargeFiles = [];
+ const tooLargeFiles = [];
for (let i = 0; i < files.length && numUploads < uploadsRemaining; i++) {
if (files[i].size > Constants.MAX_FILE_SIZE) {
@@ -81,18 +81,15 @@ class FileUpload extends React.Component {
// generate a unique id that can be used by other components to refer back to this upload
const clientId = Utils.generateId();
- // prepare data to be uploaded
- var formData = new FormData();
- formData.append('channel_id', channelId);
- formData.append('files', files[i], files[i].name);
- formData.append('client_ids', clientId);
-
- var request = Client.uploadFile(formData,
+ const request = Client.uploadFile(files[i],
+ files[i].name,
+ channelId,
+ clientId,
this.fileUploadSuccess.bind(this, channelId),
this.fileUploadFail.bind(this, clientId)
);
- var requests = this.state.requests;
+ const requests = this.state.requests;
requests[clientId] = request;
this.setState({requests});
@@ -231,8 +228,6 @@ class FileUpload extends React.Component {
// generate a unique id that can be used by other components to refer back to this file upload
var clientId = Utils.generateId();
- var formData = new FormData();
- formData.append('channel_id', channelId);
var d = new Date();
var hour;
if (d.getHours() < 10) {
@@ -247,16 +242,17 @@ class FileUpload extends React.Component {
min = String(d.getMinutes());
}
- var name = formatMessage(holders.pasted) + d.getFullYear() + '-' + d.getMonth() + '-' + d.getDate() + ' ' + hour + '-' + min + '.' + ext;
- formData.append('files', file, name);
- formData.append('client_ids', clientId);
+ const name = formatMessage(holders.pasted) + d.getFullYear() + '-' + d.getMonth() + '-' + d.getDate() + ' ' + hour + '-' + min + '.' + ext;
- var request = Client.uploadFile(formData,
+ const request = Client.uploadFile(file,
+ name,
+ channelId,
+ clientId,
self.fileUploadSuccess.bind(self, channelId),
self.fileUploadFail.bind(self, clientId)
);
- var requests = self.state.requests;
+ const requests = self.state.requests;
requests[clientId] = request;
self.setState({requests});
@@ -280,13 +276,13 @@ class FileUpload extends React.Component {
}
cancelUpload(clientId) {
- var requests = this.state.requests;
- var request = requests[clientId];
+ const requests = JSON.parse(JSON.stringify(this.state.requests));
+ const request = requests[clientId];
if (request) {
request.abort();
- delete requests[clientId];
+ Reflect.deleteProperty(requests, clientId);
this.setState({requests});
}
}
diff --git a/webapp/components/filtered_user_list.jsx b/webapp/components/filtered_user_list.jsx
index bd6c49714..5aa0bd96c 100644
--- a/webapp/components/filtered_user_list.jsx
+++ b/webapp/components/filtered_user_list.jsx
@@ -113,6 +113,7 @@ class FilteredUserList extends React.Component {
>
<UserList
users={users}
+ teamMembers={this.props.teamMembers}
actions={this.props.actions}
actionProps={this.props.actionProps}
/>
@@ -124,6 +125,7 @@ class FilteredUserList extends React.Component {
FilteredUserList.defaultProps = {
users: [],
+ teamMembers: [],
actions: [],
actionProps: {}
};
@@ -131,6 +133,7 @@ FilteredUserList.defaultProps = {
FilteredUserList.propTypes = {
intl: intlShape.isRequired,
users: React.PropTypes.arrayOf(React.PropTypes.object),
+ teamMembers: React.PropTypes.arrayOf(React.PropTypes.object),
actions: React.PropTypes.arrayOf(React.PropTypes.func),
actionProps: React.PropTypes.object,
style: React.PropTypes.object
diff --git a/webapp/components/not_logged_in.jsx b/webapp/components/header_footer_template.jsx
index 4beee6259..ce2f59541 100644
--- a/webapp/components/not_logged_in.jsx
+++ b/webapp/components/header_footer_template.jsx
@@ -9,12 +9,12 @@ import {Link} from 'react-router';
export default class NotLoggedIn extends React.Component {
componentDidMount() {
- $('body').attr('class', 'sticky');
- $('#root').attr('class', 'container-fluid');
+ $('body').addClass('sticky');
+ $('#root').addClass('container-fluid');
}
componentWillUnmount() {
- $('body').attr('class', '');
- $('#root').attr('class', '');
+ $('body').removeClass('sticky');
+ $('#root').removeClass('container-fluid');
}
render() {
return (
diff --git a/webapp/components/invite_member_modal.jsx b/webapp/components/invite_member_modal.jsx
index 17cec7aad..4ac620f08 100644
--- a/webapp/components/invite_member_modal.jsx
+++ b/webapp/components/invite_member_modal.jsx
@@ -5,7 +5,7 @@ import ReactDOM from 'react-dom';
import * as utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
import ModalStore from 'stores/modal_store.jsx';
import UserStore from 'stores/user_store.jsx';
diff --git a/webapp/components/logged_in.jsx b/webapp/components/logged_in.jsx
index 1dcb6b0aa..3941dd12c 100644
--- a/webapp/components/logged_in.jsx
+++ b/webapp/components/logged_in.jsx
@@ -2,39 +2,17 @@
// See License.txt for license information.
import $ from 'jquery';
+import LoadingScreen from 'components/loading_screen.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
-import * as GlobalActions from 'action_creators/global_actions.jsx';
import UserStore from 'stores/user_store.jsx';
-import ChannelStore from 'stores/channel_store.jsx';
import BrowserStore from 'stores/browser_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-const TutorialSteps = Constants.TutorialSteps;
-const Preferences = Constants.Preferences;
-import ErrorBar from 'components/error_bar.jsx';
import * as Websockets from 'action_creators/websocket_actions.jsx';
-import LoadingScreen from 'components/loading_screen.jsx';
+import Constants from 'utils/constants.jsx';
import {browserHistory} from 'react-router';
-import SidebarRight from 'components/sidebar_right.jsx';
-import SidebarRightMenu from 'components/sidebar_right_menu.jsx';
-import Navbar from 'components/navbar.jsx';
-
-// Modals
-import GetPostLinkModal from 'components/get_post_link_modal.jsx';
-import GetTeamInviteLinkModal from 'components/get_team_invite_link_modal.jsx';
-import EditPostModal from 'components/edit_post_modal.jsx';
-import DeletePostModal from 'components/delete_post_modal.jsx';
-import MoreChannelsModal from 'components/more_channels.jsx';
-import TeamSettingsModal from 'components/team_settings_modal.jsx';
-import RemovedFromChannelModal from 'components/removed_from_channel_modal.jsx';
-import RegisterAppModal from 'components/register_app_modal.jsx';
-import ImportThemeModal from 'components/user_settings/import_theme_modal.jsx';
-import InviteMemberModal from 'components/invite_member_modal.jsx';
-import SelectTeamModal from 'components/admin_console/select_team_modal.jsx';
-
const CLIENT_STATUS_INTERVAL = 30000;
const BACKSPACE_CHAR = 8;
@@ -45,19 +23,22 @@ export default class LoggedIn extends React.Component {
super(params);
this.onUserChanged = this.onUserChanged.bind(this);
+ this.setupUser = this.setupUser.bind(this);
this.state = {
- user: null,
- profiles: null
+ user: UserStore.getCurrentUser()
};
+
+ if (this.state.user) {
+ this.setupUser(this.state.user);
+ }
}
+
isValidState() {
- return this.state.user != null && this.state.profiles != null;
+ return this.state.user != null;
}
- onUserChanged() {
- // Grab the current user
- const user = UserStore.getCurrentUser();
+ setupUser(user) {
// Update segment indentify
if (global.window.mm_config.SegmentDeveloperKey != null && global.window.mm_config.SegmentDeveloperKey !== '') {
global.window.analytics.identify(user.id, {
@@ -65,7 +46,6 @@ export default class LoggedIn extends React.Component {
email: user.email,
createdAt: user.create_at,
username: user.username,
- team_id: user.team_id,
id: user.id
});
}
@@ -78,25 +58,19 @@ export default class LoggedIn extends React.Component {
Utils.applyTheme(Constants.THEMES.default);
}
}
+ }
- // Go to tutorial if we are first arrivign
- const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
- if (tutorialStep <= TutorialSteps.INTRO_SCREENS) {
- browserHistory.push(Utils.getTeamURLFromAddressBar() + '/tutorial');
- }
-
- // Get profiles
- const profiles = UserStore.getProfiles();
+ onUserChanged() {
+ // Grab the current user
+ const user = UserStore.getCurrentUser();
+ this.setupUser(user);
this.setState({
- user,
- profiles
+ user
});
}
- componentWillMount() {
- // Emit view action
- GlobalActions.viewLoggedIn();
+ componentWillMount() {
// Listen for user
UserStore.addChangeListener(this.onUserChanged);
@@ -116,7 +90,7 @@ export default class LoggedIn extends React.Component {
}
console.log('detected logout from a different tab'); //eslint-disable-line no-console
- browserHistory.push('/' + this.props.params.team);
+ browserHistory.push('/');
}
if (e.originalEvent.key === '__login__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) {
@@ -170,18 +144,6 @@ export default class LoggedIn extends React.Component {
$('body').addClass('ios');
}
- // Set up tracking for whether the window is active
- window.isActive = true;
- $(window).on('focus', () => {
- AsyncClient.updateLastViewedAt();
- ChannelStore.resetCounts(ChannelStore.getCurrentId());
- ChannelStore.emitChange();
- window.isActive = true;
- });
- $(window).on('blur', () => {
- window.isActive = false;
- });
-
// if preferences have already been stored in local storage do not wait until preference store change is fired and handled in channel.jsx
const selectedFont = PreferenceStore.get(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'selected_font', Constants.DEFAULT_FONT);
Utils.applyFont(selectedFont);
@@ -193,13 +155,11 @@ export default class LoggedIn extends React.Component {
}
});
}
+
componentWillUnmount() {
$('#root').attr('class', '');
clearInterval(this.intervalId);
- $(window).off('focus');
- $(window).off('blur');
-
Websockets.close();
UserStore.removeChangeListener(this.onUserChanged);
@@ -211,75 +171,18 @@ export default class LoggedIn extends React.Component {
$(window).off('keydown.preventBackspace');
}
+
render() {
if (!this.isValidState()) {
return <LoadingScreen/>;
}
- let content = [];
- if (this.props.children) {
- content = this.props.children;
- } else {
- content.push(
- this.props.navbar
- );
- content.push(
- this.props.sidebar
- );
- content.push(
- <div
- key='inner-wrap'
- className='inner-wrap channel__wrap'
- >
- <div className='row header'>
- <div id='navbar'>
- <Navbar/>
- </div>
- </div>
- <div className='row main'>
- {React.cloneElement(this.props.center, {
- user: this.state.user,
- profiles: this.state.profiles
- })}
- </div>
- </div>
- );
- }
- return (
- <div className='channel-view'>
- <ErrorBar/>
- <div className='container-fluid'>
- <SidebarRight/>
- <SidebarRightMenu/>
- {content}
-
- <GetPostLinkModal/>
- <GetTeamInviteLinkModal/>
- <InviteMemberModal/>
- <ImportThemeModal/>
- <TeamSettingsModal/>
- <MoreChannelsModal/>
- <EditPostModal/>
- <DeletePostModal/>
- <RemovedFromChannelModal/>
- <RegisterAppModal/>
- <SelectTeamModal/>
- </div>
- </div>
- );
+ return React.cloneElement(this.props.children, {
+ user: this.state.user
+ });
}
}
-LoggedIn.defaultProps = {
-};
-
LoggedIn.propTypes = {
- children: React.PropTypes.oneOfType([
- React.PropTypes.arrayOf(React.PropTypes.element),
- React.PropTypes.element
- ]),
- navbar: React.PropTypes.element,
- sidebar: React.PropTypes.element,
- center: React.PropTypes.element,
- params: React.PropTypes.object
+ children: React.PropTypes.object
};
diff --git a/webapp/components/login/login.jsx b/webapp/components/login/login.jsx
index a3dadbf36..c5955a1f4 100644
--- a/webapp/components/login/login.jsx
+++ b/webapp/components/login/login.jsx
@@ -5,12 +5,14 @@ import LoginEmail from './components/login_email.jsx';
import LoginUsername from './components/login_username.jsx';
import LoginLdap from './components/login_ldap.jsx';
import LoginMfa from './components/login_mfa.jsx';
+import ErrorBar from 'components/error_bar.jsx';
-import TeamStore from 'stores/team_store.jsx';
+import * as GlobalActions from '../../action_creators/global_actions.jsx';
import UserStore from 'stores/user_store.jsx';
+import Client from 'utils/web_client.jsx';
import * as TextFormatting from 'utils/text_formatting.jsx';
-import * as Client from 'utils/client.jsx';
+
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
@@ -18,40 +20,24 @@ import {FormattedMessage} from 'react-intl';
import {browserHistory, Link} from 'react-router';
import React from 'react';
+import logoImage from 'images/logo.png';
export default class Login extends React.Component {
constructor(props) {
super(props);
- this.getStateFromStores = this.getStateFromStores.bind(this);
- this.onTeamChange = this.onTeamChange.bind(this);
this.preSubmit = this.preSubmit.bind(this);
this.submit = this.submit.bind(this);
+ this.finishSignin = this.finishSignin.bind(this);
- const state = this.getStateFromStores();
- state.doneCheckLogin = false;
+ const state = {};
+ state.showMfa = false;
this.state = state;
}
componentDidMount() {
- TeamStore.addChangeListener(this.onTeamChange);
- Client.getMeLoggedIn((data) => {
- if (data && data.logged_in !== 'false') {
- browserHistory.push('/' + this.props.params.team + '/channels/town-square');
- } else {
- this.setState({doneCheckLogin: true}); //eslint-disable-line react/no-did-mount-set-state
- }
- });
- }
- componentWillUnmount() {
- TeamStore.removeChangeListener(this.onTeamChange);
- }
- getStateFromStores() {
- return {
- currentTeam: TeamStore.getByName(this.props.params.team)
- };
- }
- onTeamChange() {
- this.setState(this.getStateFromStores());
+ if (UserStore.getCurrentUser()) {
+ browserHistory.push('/select_team');
+ }
}
preSubmit(method, loginId, password) {
if (global.window.mm_config.EnableMultifactorAuthentication !== 'true') {
@@ -59,7 +45,7 @@ export default class Login extends React.Component {
return;
}
- Client.checkMfa(method, this.state.currentTeam.name, loginId,
+ Client.checkMfa(method, loginId,
(data) => {
if (data.mfa_required === 'true') {
this.setState({showMfa: true, method, loginId, password});
@@ -78,27 +64,41 @@ export default class Login extends React.Component {
}
);
}
+ finishSignin() {
+ GlobalActions.emitInitialLoad(
+ () => {
+ browserHistory.push('/select_team');
+ }
+ );
+ }
+
submit(method, loginId, password, token) {
this.setState({showMfa: false, serverEmailError: null, serverUsernameError: null, serverLdapError: null});
- const team = this.state.currentTeam.name;
-
if (method === Constants.EMAIL_SERVICE) {
- Client.loginByEmail(team, loginId, password, token,
+ Client.webLogin(
+ loginId,
+ null,
+ password,
+ token,
() => {
UserStore.setLastEmail(loginId);
- browserHistory.push('/' + team + '/channels/town-square');
+ this.finishSignin();
},
(err) => {
if (err.id === 'api.user.login.not_verified.app_error') {
- browserHistory.push('/should_verify_email?teamname=' + encodeURIComponent(team) + '&email=' + encodeURIComponent(loginId));
+ browserHistory.push('/should_verify_email?&email=' + encodeURIComponent(loginId));
return;
}
this.setState({serverEmailError: err.message});
}
);
} else if (method === Constants.USERNAME_SERVICE) {
- Client.loginByUsername(team, loginId, password, token,
+ Client.webLogin(
+ null,
+ loginId,
+ password,
+ token,
() => {
UserStore.setLastUsername(loginId);
@@ -106,7 +106,7 @@ export default class Login extends React.Component {
if (redirect) {
browserHistory.push(decodeURIComponent(redirect));
} else {
- browserHistory.push('/' + team + '/channels/town-square');
+ this.finishSignin();
}
},
(err) => {
@@ -120,13 +120,16 @@ export default class Login extends React.Component {
}
);
} else if (method === Constants.LDAP_SERVICE) {
- Client.loginByLdap(team, loginId, password, token,
+ Client.loginByLdap(
+ loginId,
+ password,
+ token,
() => {
const redirect = Utils.getUrlParameter('redirect');
if (redirect) {
browserHistory.push(decodeURIComponent(redirect));
} else {
- browserHistory.push('/' + team + '/channels/town-square');
+ this.finishSignin();
}
},
(err) => {
@@ -144,7 +147,7 @@ export default class Login extends React.Component {
return (
<div>
<img
- src='/api/v1/admin/get_brand_image'
+ src={Client.getAdminRoute() + '/get_brand_image'}
/>
<p dangerouslySetInnerHTML={{__html: TextFormatting.formatText(text)}}/>
</div>
@@ -153,7 +156,7 @@ export default class Login extends React.Component {
return null;
}
- createLoginOptions(currentTeam) {
+ createLoginOptions() {
const extraParam = Utils.getUrlParameter('extra');
let extraBox = '';
if (extraParam) {
@@ -187,10 +190,19 @@ export default class Login extends React.Component {
/>
</div>
);
+ } else if (extraParam === Constants.PASSWORD_CHANGE) {
+ extraBox = (
+ <div className='alert alert-success'>
+ <i className='fa fa-check'/>
+ <FormattedMessage
+ id='login.passwordChanged'
+ defaultMessage=' Password updated successfully'
+ />
+ </div>
+ );
}
}
- const teamName = currentTeam.name;
const ldapEnabled = global.window.mm_config.EnableLdap === 'true';
const gitlabSigninEnabled = global.window.mm_config.EnableSignUpWithGitLab === 'true';
const googleSigninEnabled = global.window.mm_config.EnableSignUpWithGoogle === 'true';
@@ -200,10 +212,10 @@ export default class Login extends React.Component {
const oauthLogins = [];
if (gitlabSigninEnabled) {
oauthLogins.push(
- <Link
+ <a
className='btn btn-custom-login gitlab'
key='gitlab'
- to={'/api/v1/oauth/gitlab/login?team=' + encodeURIComponent(teamName)}
+ href={Client.getOAuthRoute() + '/gitlab/login'}
>
<span className='icon'/>
<span>
@@ -212,7 +224,7 @@ export default class Login extends React.Component {
defaultMessage='with GitLab'
/>
</span>
- </Link>
+ </a>
);
}
@@ -221,7 +233,7 @@ export default class Login extends React.Component {
<Link
className='btn btn-custom-login google'
key='google'
- to={'/api/v1/oauth/google/login?team=' + encodeURIComponent(teamName)}
+ to={Client.getOAuthRoute() + '/google/login'}
>
<span className='icon'/>
<span>
@@ -238,7 +250,6 @@ export default class Login extends React.Component {
if (emailSigninEnabled) {
emailLogin = (
<LoginEmail
- teamName={teamName}
serverError={this.state.serverEmailError}
submit={this.preSubmit}
/>
@@ -263,7 +274,6 @@ export default class Login extends React.Component {
if (usernameSigninEnabled) {
usernameLogin = (
<LoginUsername
- teamName={teamName}
serverError={this.state.serverUsernameError}
submit={this.preSubmit}
/>
@@ -288,7 +298,6 @@ export default class Login extends React.Component {
if (ldapEnabled) {
ldapLogin = (
<LoginLdap
- teamName={teamName}
serverError={this.state.serverLdapError}
submit={this.preSubmit}
/>
@@ -309,34 +318,31 @@ export default class Login extends React.Component {
}
}
- let userSignUp;
- if (currentTeam.allow_open_invite) {
- userSignUp = (
- <div>
- <span>
+ const userSignUp = (
+ <div>
+ <span>
+ <FormattedMessage
+ id='login.noAccount'
+ defaultMessage="Don't have an account? "
+ />
+ <Link
+ to={'/signup_user_complete'}
+ className='signup-team-login'
+ >
<FormattedMessage
- id='login.noAccount'
- defaultMessage="Don't have an account? "
+ id='login.create'
+ defaultMessage='Create one now'
/>
- <Link
- to={'/signup_user_complete/?id=' + currentTeam.invite_id}
- className='signup-team-login'
- >
- <FormattedMessage
- id='login.create'
- defaultMessage='Create one now'
- />
- </Link>
- </span>
- </div>
- );
- }
+ </Link>
+ </span>
+ </div>
+ );
let forgotPassword;
if (usernameSigninEnabled || emailSigninEnabled) {
forgotPassword = (
<div className='form-group'>
- <Link to={'/' + teamName + '/reset_password'}>
+ <Link to={'/reset_password'}>
<FormattedMessage
id='login.forgot'
defaultMessage='I forgot my password'
@@ -346,23 +352,6 @@ export default class Login extends React.Component {
);
}
- let teamSignUp;
- if (global.window.mm_config.EnableTeamCreation === 'true' && !Utils.isMobileApp()) {
- teamSignUp = (
- <div className='margin--extra'>
- <Link
- to='/'
- className='signup-team-login'
- >
- <FormattedMessage
- id='login.createTeam'
- defaultMessage='Create a new team'
- />
- </Link>
- </div>
- );
- }
-
return (
<div>
{extraBox}
@@ -372,16 +361,10 @@ export default class Login extends React.Component {
{ldapLogin}
{userSignUp}
{forgotPassword}
- {teamSignUp}
</div>
);
}
render() {
- const currentTeam = this.state.currentTeam;
- if (currentTeam == null || !this.state.doneCheckLogin) {
- return <div/>;
- }
-
let content;
let customContent;
let customClass;
@@ -395,7 +378,7 @@ export default class Login extends React.Component {
/>
);
} else {
- content = this.createLoginOptions(currentTeam);
+ content = this.createLoginOptions();
customContent = this.createCustomLogin();
if (customContent) {
customClass = 'branded';
@@ -404,36 +387,23 @@ export default class Login extends React.Component {
return (
<div>
- <div className='signup-header'>
- <Link to='/'>
- <span className='fa fa-chevron-left'/>
- <FormattedMessage
- id='web.header.back'
- />
- </Link>
- </div>
+ <ErrorBar/>
<div className='col-sm-12'>
<div className={'signup-team__container ' + customClass}>
<div className='signup__markdown'>
{customContent}
</div>
+ <img
+ className='signup-team-logo'
+ src={logoImage}
+ />
+ <h1>{global.window.mm_config.SiteName}</h1>
+ <h4 className='color--light'>
+ <FormattedMessage
+ id='web.root.singup_info'
+ />
+ </h4>
<div className='signup__content'>
- <h5 className='margin--less'>
- <FormattedMessage
- id='login.signTo'
- defaultMessage='Sign in to:'
- />
- </h5>
- <h2 className='signup-team__name'>{currentTeam.display_name}</h2>
- <h2 className='signup-team__subdomain'>
- <FormattedMessage
- id='login.on'
- defaultMessage='on {siteName}'
- values={{
- siteName: global.window.mm_config.SiteName
- }}
- />
- </h2>
{content}
</div>
</div>
diff --git a/webapp/components/member_list_team.jsx b/webapp/components/member_list_team.jsx
index bb5eee496..d0714e942 100644
--- a/webapp/components/member_list_team.jsx
+++ b/webapp/components/member_list_team.jsx
@@ -4,6 +4,8 @@
import FilteredUserList from './filtered_user_list.jsx';
import TeamMembersDropdown from './team_members_dropdown.jsx';
import UserStore from 'stores/user_store.jsx';
+import TeamStore from 'stores/team_store.jsx';
+import * as AsyncClient from 'utils/async_client.jsx';
import React from 'react';
@@ -13,18 +15,23 @@ export default class MemberListTeam extends React.Component {
this.getUsers = this.getUsers.bind(this);
this.onChange = this.onChange.bind(this);
+ this.onTeamChange = this.onTeamChange.bind(this);
this.state = {
- users: this.getUsers()
+ users: this.getUsers(),
+ teamMembers: TeamStore.getMembersForTeam()
};
}
componentDidMount() {
UserStore.addChangeListener(this.onChange);
+ TeamStore.addChangeListener(this.onTeamChange);
+ AsyncClient.getTeamMembers(TeamStore.getCurrentId());
}
componentWillUnmount() {
UserStore.removeChangeListener(this.onChange);
+ TeamStore.removeChangeListener(this.onTeamChange);
}
getUsers() {
@@ -46,11 +53,18 @@ export default class MemberListTeam extends React.Component {
});
}
+ onTeamChange() {
+ this.setState({
+ teamMembers: TeamStore.getMembersForTeam()
+ });
+ }
+
render() {
return (
<FilteredUserList
style={this.props.style}
users={this.state.users}
+ teamMembers={this.state.teamMembers}
actions={[TeamMembersDropdown]}
/>
);
diff --git a/webapp/components/more_channels.jsx b/webapp/components/more_channels.jsx
index 5ccf9c11e..3ab05341b 100644
--- a/webapp/components/more_channels.jsx
+++ b/webapp/components/more_channels.jsx
@@ -4,7 +4,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
import * as Utils from 'utils/utils.jsx';
-import * as client from 'utils/client.jsx';
+import client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import LoadingScreen from './loading_screen.jsx';
diff --git a/webapp/components/navbar.jsx b/webapp/components/navbar.jsx
index e4e64c12e..919a72d0a 100644
--- a/webapp/components/navbar.jsx
+++ b/webapp/components/navbar.jsx
@@ -18,7 +18,7 @@ import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import TeamStore from 'stores/team_store.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
diff --git a/webapp/components/navbar_dropdown.jsx b/webapp/components/navbar_dropdown.jsx
index da1ae237e..19b99a14d 100644
--- a/webapp/components/navbar_dropdown.jsx
+++ b/webapp/components/navbar_dropdown.jsx
@@ -6,6 +6,7 @@ import ReactDOM from 'react-dom';
import * as Utils from 'utils/utils.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
+import TeamStore from 'stores/team_store.jsx';
import AboutBuildModal from './about_build_modal.jsx';
import TeamMembersModal from './team_members_modal.jsx';
import ToggleModalButton from './toggle_modal_button.jsx';
@@ -25,10 +26,13 @@ export default class NavbarDropdown extends React.Component {
this.handleAboutModal = this.handleAboutModal.bind(this);
this.aboutModalDismissed = this.aboutModalDismissed.bind(this);
+ this.onTeamChange = this.onTeamChange.bind(this);
this.state = {
showUserSettingsModal: false,
- showAboutModal: false
+ showAboutModal: false,
+ teams: TeamStore.getAll(),
+ teamMembers: TeamStore.getTeamMembers()
};
}
handleAboutModal() {
@@ -37,6 +41,7 @@ export default class NavbarDropdown extends React.Component {
aboutModalDismissed() {
this.setState({showAboutModal: false});
}
+
componentDidMount() {
$(ReactDOM.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => {
$('.sidebar--left .dropdown-menu').scrollTop(0);
@@ -45,10 +50,22 @@ export default class NavbarDropdown extends React.Component {
this.blockToggle = false;
}, 100);
});
+
+ TeamStore.addChangeListener(this.onTeamChange);
+ }
+
+ onTeamChange() {
+ this.setState({
+ teams: TeamStore.getAll(),
+ teamMembers: TeamStore.getTeamMembers()
+ });
}
+
componentWillUnmount() {
$(ReactDOM.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown');
+ TeamStore.removeChangeListener(this.onTeamChange);
}
+
render() {
var teamLink = '';
var inviteLink = '';
@@ -130,7 +147,7 @@ export default class NavbarDropdown extends React.Component {
if (isAdmin || window.EnableAdminOnlyIntegrations !== 'true') {
integrationsLink = (
<li>
- <Link to={'/settings/integrations'}>
+ <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations'}>
<FormattedMessage
id='navbar_dropdown.integrations'
defaultMessage='Integrations'
@@ -163,8 +180,7 @@ export default class NavbarDropdown extends React.Component {
<li key='newTeam_li'>
<Link
key='newTeam_a'
- target='_blank'
- to={Utils.getWindowLocationOrigin() + '/signup_team'}
+ to='/create_team'
>
<FormattedMessage
id='navbar_dropdown.create'
@@ -175,6 +191,34 @@ export default class NavbarDropdown extends React.Component {
);
}
+ if (this.state.teamMembers && this.state.teamMembers.length > 0) {
+ teams.push(
+ <li
+ key='teamDiv'
+ className='divider'
+ ></li>
+ );
+
+ for (var index in this.state.teamMembers) {
+ if (this.state.teamMembers.hasOwnProperty(index)) {
+ var teamMember = this.state.teamMembers[index];
+ var team = this.state.teams[teamMember.team_id];
+
+ if (team.name !== this.props.teamName) {
+ teams.push(
+ <li key={'team_' + team.name}>
+ <Link
+ to={'/' + team.name + '/channels/town-square'}
+ >
+ {team.display_name}
+ </Link>
+ </li>
+ );
+ }
+ }
+ }
+ }
+
let helpLink = null;
if (global.window.mm_config.HelpLink) {
helpLink = (
@@ -245,12 +289,15 @@ export default class NavbarDropdown extends React.Component {
{inviteLink}
{teamLink}
<li>
- <Link to={'/' + this.props.teamName + '/logout'}>
+ <a
+ href='#'
+ onClick={GlobalActions.emitUserLoggedOutEvent}
+ >
<FormattedMessage
id='navbar_dropdown.logout'
defaultMessage='Logout'
/>
- </Link>
+ </a>
</li>
{adminDivider}
{teamSettings}
diff --git a/webapp/components/needs_team.jsx b/webapp/components/needs_team.jsx
index f624b1ebc..59797f086 100644
--- a/webapp/components/needs_team.jsx
+++ b/webapp/components/needs_team.jsx
@@ -1,22 +1,156 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import React from 'react';
+
+import $ from 'jquery';
+
+import {browserHistory} from 'react-router';
+import * as Utils from 'utils/utils.jsx';
+import * as AsyncClient from 'utils/async_client.jsx';
+import TeamStore from 'stores/team_store.jsx';
+import UserStore from 'stores/user_store.jsx';
+import PreferenceStore from 'stores/preference_store.jsx';
+import ChannelStore from 'stores/channel_store.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
+import Constants from 'utils/constants.jsx';
+const TutorialSteps = Constants.TutorialSteps;
+const Preferences = Constants.Preferences;
-import React from 'react';
+import ErrorBar from 'components/error_bar.jsx';
+import SidebarRight from 'components/sidebar_right.jsx';
+import SidebarRightMenu from 'components/sidebar_right_menu.jsx';
+import Navbar from 'components/navbar.jsx';
+
+// Modals
+import GetPostLinkModal from 'components/get_post_link_modal.jsx';
+import GetTeamInviteLinkModal from 'components/get_team_invite_link_modal.jsx';
+import EditPostModal from 'components/edit_post_modal.jsx';
+import DeletePostModal from 'components/delete_post_modal.jsx';
+import MoreChannelsModal from 'components/more_channels.jsx';
+import TeamSettingsModal from 'components/team_settings_modal.jsx';
+import RemovedFromChannelModal from 'components/removed_from_channel_modal.jsx';
+import RegisterAppModal from 'components/register_app_modal.jsx';
+import ImportThemeModal from 'components/user_settings/import_theme_modal.jsx';
+import InviteMemberModal from 'components/invite_member_modal.jsx';
+import SelectTeamModal from 'components/admin_console/select_team_modal.jsx';
export default class NeedsTeam extends React.Component {
+ constructor(params) {
+ super(params);
+
+ this.onChanged = this.onChanged.bind(this);
+
+ this.state = {
+ profiles: UserStore.getProfiles(),
+ team: TeamStore.getCurrent()
+ };
+ }
+
+ onChanged() {
+ this.setState({
+ profiles: UserStore.getProfiles(),
+ team: TeamStore.getCurrent()
+ });
+ }
+
componentWillMount() {
- GlobalActions.loadTeamRequiredPage();
+ UserStore.addChangeListener(this.onChanged);
+ TeamStore.addChangeListener(this.onChanged);
+
+ // Emit view action
+ GlobalActions.viewLoggedIn();
+
+ // Go to tutorial if we are first arrivign
+ const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
+ if (tutorialStep <= TutorialSteps.INTRO_SCREENS) {
+ browserHistory.push(Utils.getTeamURLFromAddressBar() + '/tutorial');
+ }
+
+ // Set up tracking for whether the window is active
+ window.isActive = true;
+ $(window).on('focus', () => {
+ AsyncClient.updateLastViewedAt();
+ ChannelStore.resetCounts(ChannelStore.getCurrentId());
+ ChannelStore.emitChange();
+ window.isActive = true;
+ });
+ $(window).on('blur', () => {
+ window.isActive = false;
+ });
+ }
+
+ componentWillUnmount() {
+ UserStore.removeChangeListener(this.onChanged);
+ TeamStore.addChangeListener(this.onChanged);
+ $(window).off('focus');
+ $(window).off('blur');
}
+
render() {
- return this.props.children;
+ let content = [];
+ if (this.props.children) {
+ content = this.props.children;
+ } else {
+ content.push(
+ this.props.navbar
+ );
+ content.push(
+ this.props.sidebar
+ );
+ content.push(
+ <div
+ key='inner-wrap'
+ className='inner-wrap channel__wrap'
+ >
+ <div className='row header'>
+ <div id='navbar'>
+ <Navbar/>
+ </div>
+ </div>
+ <div className='row main'>
+ {React.cloneElement(this.props.center, {
+ user: this.props.user,
+ profiles: this.state.profiles,
+ team: this.state.team
+ })}
+ </div>
+ </div>
+ );
+ }
+ return (
+ <div className='channel-view'>
+ <ErrorBar/>
+ <div className='container-fluid'>
+ <SidebarRight/>
+ <SidebarRightMenu/>
+ {content}
+
+ <GetPostLinkModal/>
+ <GetTeamInviteLinkModal/>
+ <InviteMemberModal/>
+ <ImportThemeModal/>
+ <TeamSettingsModal/>
+ <MoreChannelsModal/>
+ <EditPostModal/>
+ <DeletePostModal/>
+ <RemovedFromChannelModal/>
+ <RegisterAppModal/>
+ <SelectTeamModal/>
+ </div>
+ </div>
+ );
}
}
-NeedsTeam.defaultProps = {
-};
-
NeedsTeam.propTypes = {
- children: React.PropTypes.object
+ children: React.PropTypes.oneOfType([
+ React.PropTypes.arrayOf(React.PropTypes.element),
+ React.PropTypes.element
+ ]),
+ navbar: React.PropTypes.element,
+ sidebar: React.PropTypes.element,
+ center: React.PropTypes.element,
+ params: React.PropTypes.object,
+ user: React.PropTypes.object
};
diff --git a/webapp/components/new_channel_flow.jsx b/webapp/components/new_channel_flow.jsx
index 82494dac0..7019da4aa 100644
--- a/webapp/components/new_channel_flow.jsx
+++ b/webapp/components/new_channel_flow.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import * as Utils from 'utils/utils.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import UserStore from 'stores/user_store.jsx';
import NewChannelModal from './new_channel_modal.jsx';
@@ -106,11 +106,7 @@ class NewChannelFlow extends React.Component {
(data) => {
Client.getChannel(
data.id,
- (data2, textStatus, xhr) => {
- if (xhr.status === 304 || !data2) {
- return;
- }
-
+ (data2) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_CHANNEL,
channel: data2.channel,
diff --git a/webapp/components/notify_counts.jsx b/webapp/components/notify_counts.jsx
index acc64dfb0..9238c8736 100644
--- a/webapp/components/notify_counts.jsx
+++ b/webapp/components/notify_counts.jsx
@@ -32,17 +32,22 @@ export default class NotifyCounts extends React.Component {
this.onListenerChange = this.onListenerChange.bind(this);
this.state = getCountsStateFromStores();
+ this.mounted = false;
}
componentDidMount() {
+ this.mounted = true;
ChannelStore.addChangeListener(this.onListenerChange);
}
componentWillUnmount() {
+ this.mounted = false;
ChannelStore.removeChangeListener(this.onListenerChange);
}
onListenerChange() {
- var newState = getCountsStateFromStores();
- if (!utils.areObjectsEqual(newState, this.state)) {
- this.setState(newState);
+ if (this.mounted) {
+ var newState = getCountsStateFromStores();
+ if (!utils.areObjectsEqual(newState, this.state)) {
+ this.setState(newState);
+ }
}
}
render() {
diff --git a/webapp/components/password_reset_form.jsx b/webapp/components/password_reset_form.jsx
index 863420902..23b8952cc 100644
--- a/webapp/components/password_reset_form.jsx
+++ b/webapp/components/password_reset_form.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import ReactDOM from 'react-dom';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
@@ -40,16 +40,12 @@ class PasswordResetForm extends React.Component {
error: null
});
- const data = {};
- data.new_password = password;
- data.hash = this.props.location.query.h;
- data.data = this.props.location.query.d;
- data.name = this.props.params.team;
-
- Client.resetPassword(data,
+ Client.resetPassword(
+ this.props.location.query.code,
+ password,
() => {
this.setState({error: null});
- browserHistory.push('/' + this.props.params.team + '/login');
+ browserHistory.push('/login?extra=' + Constants.PASSWORD_CHANGE);
},
(err) => {
this.setState({error: err.message});
diff --git a/webapp/components/password_reset_send_link.jsx b/webapp/components/password_reset_send_link.jsx
index e3ab8949e..65d9439bd 100644
--- a/webapp/components/password_reset_send_link.jsx
+++ b/webapp/components/password_reset_send_link.jsx
@@ -4,7 +4,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
import * as Utils from 'utils/utils.jsx';
-import * as client from 'utils/client.jsx';
+import client from 'utils/web_client.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
@@ -43,10 +43,8 @@ class PasswordResetSendLink extends React.Component {
error: ''
});
- var data = {};
- data.email = email;
- data.name = this.props.params.team;
- client.sendPasswordReset(data,
+ client.sendPasswordReset(
+ email,
() => {
this.setState({
error: null,
diff --git a/webapp/components/popover_list_members.jsx b/webapp/components/popover_list_members.jsx
index 8c3c381af..387c37ab5 100644
--- a/webapp/components/popover_list_members.jsx
+++ b/webapp/components/popover_list_members.jsx
@@ -7,6 +7,7 @@ import UserStore from 'stores/user_store.jsx';
import {Popover, Overlay} from 'react-bootstrap';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
+import Client from 'utils/web_client.jsx';
import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router';
@@ -97,7 +98,7 @@ export default class PopoverListMembers extends React.Component {
className='more-modal__image'
width='26px'
height='26px'
- src={`/api/v1/users/${m.id}/image?time=${m.update_at}`}
+ src={`${Client.getUsersRoute()}/${m.id}/image?time=${m.update_at}`}
/>
<div className='more-modal__details'>
<div
diff --git a/webapp/components/post.jsx b/webapp/components/post.jsx
index 7fabec741..ae3fa9c98 100644
--- a/webapp/components/post.jsx
+++ b/webapp/components/post.jsx
@@ -10,7 +10,7 @@ import ChannelStore from 'stores/channel_store.jsx';
import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
@@ -48,7 +48,7 @@ export default class Post extends React.Component {
e.preventDefault();
var post = this.props.post;
- Client.createPost(post, post.channel_id,
+ Client.createPost(post,
(data) => {
AsyncClient.getPosts();
diff --git a/webapp/components/register_app_modal.jsx b/webapp/components/register_app_modal.jsx
index c632233cf..82a095c75 100644
--- a/webapp/components/register_app_modal.jsx
+++ b/webapp/components/register_app_modal.jsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import ModalStore from 'stores/modal_store.jsx';
import {Modal} from 'react-bootstrap';
diff --git a/webapp/components/rename_channel_modal.jsx b/webapp/components/rename_channel_modal.jsx
index 3e47847e7..ed045da91 100644
--- a/webapp/components/rename_channel_modal.jsx
+++ b/webapp/components/rename_channel_modal.jsx
@@ -3,7 +3,7 @@
import ReactDOM from 'react-dom';
import * as Utils from 'utils/utils.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import Constants from 'utils/constants.jsx';
diff --git a/webapp/components/rhs_comment.jsx b/webapp/components/rhs_comment.jsx
index 53170ee15..709865dc1 100644
--- a/webapp/components/rhs_comment.jsx
+++ b/webapp/components/rhs_comment.jsx
@@ -9,7 +9,7 @@ import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
import FileAttachmentList from './file_attachment_list.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
var ActionTypes = Constants.ActionTypes;
import * as TextFormatting from 'utils/text_formatting.jsx';
@@ -43,7 +43,7 @@ class RhsComment extends React.Component {
e.preventDefault();
var post = this.props.post;
- Client.createPost(post, post.channel_id,
+ Client.createPost(post,
(data) => {
AsyncClient.getPosts(post.channel_id);
@@ -261,7 +261,7 @@ class RhsComment extends React.Component {
<div className='post__content'>
<div className='post__img'>
<img
- src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp}
+ src={Client.getUsersRoute() + '/' + post.user_id + '/image?time=' + timestamp}
height='36'
width='36'
/>
diff --git a/webapp/components/rhs_header_post.jsx b/webapp/components/rhs_header_post.jsx
index 189ee0acb..493040800 100644
--- a/webapp/components/rhs_header_post.jsx
+++ b/webapp/components/rhs_header_post.jsx
@@ -3,6 +3,7 @@
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import Constants from 'utils/constants.jsx';
+import * as GlobalActions from 'action_creators/global_actions.jsx';
import {FormattedMessage} from 'react-intl';
@@ -21,16 +22,7 @@ export default class RhsHeaderPost extends React.Component {
}
handleClose(e) {
e.preventDefault();
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_SEARCH,
- results: null
- });
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_POST_SELECTED,
- postId: null
- });
+ GlobalActions.emitCloseRightHandSide();
}
handleBack(e) {
e.preventDefault();
diff --git a/webapp/components/root.jsx b/webapp/components/root.jsx
index 59364d085..0adbc7f04 100644
--- a/webapp/components/root.jsx
+++ b/webapp/components/root.jsx
@@ -1,10 +1,10 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import $ from 'jquery';
+//import $ from 'jquery';
+//import Client from 'utils/web_client.jsx';
+
import * as GlobalActions from 'action_creators/global_actions.jsx';
-import * as Client from 'utils/client.jsx';
-import BrowserStore from 'stores/browser_store.jsx';
import LocalizationStore from 'stores/localization_store.jsx';
import {IntlProvider} from 'react-intl';
@@ -14,6 +14,7 @@ import React from 'react';
import FastClick from 'fastclick';
import {browserHistory} from 'react-router';
+import UserStore from 'stores/user_store.jsx';
export default class Root extends React.Component {
constructor(props) {
@@ -29,15 +30,16 @@ export default class Root extends React.Component {
localizationChanged() {
this.setState({locale: LocalizationStore.getLocale(), translations: LocalizationStore.getTranslations()});
}
+
redirectIfNecessary(props) {
if (props.location.pathname === '/') {
- Client.getMeLoggedIn((data) => {
- if (!data || data.logged_in === 'false') {
- browserHistory.push('/signup_team');
- } else {
- browserHistory.push('/' + data.team_name + '/channels/town-square');
- }
- });
+ if (UserStore.getNoAccounts()) {
+ browserHistory.push('/signup_user_complete');
+ } else if (UserStore.getCurrentUser()) {
+ browserHistory.push('/select_team');
+ } else {
+ browserHistory.push('/login');
+ }
}
}
componentWillReceiveProps(newProps) {
@@ -47,28 +49,6 @@ export default class Root extends React.Component {
// Setup localization listener
LocalizationStore.addChangeListener(this.localizationChanged);
- // Browser store check version
- BrowserStore.checkVersion();
-
- window.onerror = (msg, url, line, column, stack) => {
- var l = {};
- l.level = 'ERROR';
- l.message = 'msg: ' + msg + ' row: ' + line + ' col: ' + column + ' stack: ' + stack + ' url: ' + url;
-
- $.ajax({
- url: '/api/v1/admin/log_client',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(l)
- });
-
- if (window.mm_config.EnableDeveloper === 'true') {
- window.ErrorStore.storeLastError({message: 'DEVELOPER MODE: A javascript error has occured. Please use the javascript console to capture and report the error (row: ' + line + ' col: ' + column + ').'});
- window.ErrorStore.emitChange();
- }
- };
-
// Ya....
/*eslint-disable */
if (window.mm_config.SegmentDeveloperKey != null && window.mm_config.SegmentDeveloperKey !== "") {
@@ -76,11 +56,7 @@ export default class Root extends React.Component {
analytics.load(window.mm_config.SegmentDeveloperKey);
analytics.page();
}}();
- } else {
- global.window.analytics = {};
- global.window.analytics.page = function(){};
- global.window.analytics.track = function(){};
- }
+ }
/*eslint-enable */
// Fastclick
diff --git a/webapp/components/search_bar.jsx b/webapp/components/search_bar.jsx
index caaf0f844..1156ac0f1 100644
--- a/webapp/components/search_bar.jsx
+++ b/webapp/components/search_bar.jsx
@@ -3,7 +3,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
-import * as client from 'utils/client.jsx';
+import client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import SearchStore from 'stores/search_store.jsx';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
diff --git a/webapp/components/select_team/select_team.jsx b/webapp/components/select_team/select_team.jsx
new file mode 100644
index 000000000..5804a641e
--- /dev/null
+++ b/webapp/components/select_team/select_team.jsx
@@ -0,0 +1,257 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import UserStore from 'stores/user_store.jsx';
+import TeamStore from 'stores/team_store.jsx';
+import * as Utils from 'utils/utils.jsx';
+import ErrorBar from 'components/error_bar.jsx';
+import LoadingScreen from 'components/loading_screen.jsx';
+import Client from 'utils/web_client.jsx';
+import * as AsyncClient from 'utils/async_client.jsx';
+import * as GlobalActions from 'action_creators/global_actions.jsx';
+
+import * as TextFormatting from 'utils/text_formatting.jsx';
+
+import {Link} from 'react-router';
+
+import {FormattedMessage} from 'react-intl';
+
+//import {browserHistory, Link} from 'react-router';
+
+import React from 'react';
+import logoImage from 'images/logo.png';
+
+export default class Login extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.onTeamChange = this.onTeamChange.bind(this);
+
+ const state = this.getStateFromStores(false);
+ this.state = state;
+ }
+
+ componentDidMount() {
+ TeamStore.addChangeListener(this.onTeamChange);
+ AsyncClient.getAllTeamListings();
+ }
+
+ componentWillUnmount() {
+ TeamStore.removeChangeListener(this.onTeamChange);
+ }
+
+ onTeamChange() {
+ this.setState(this.getStateFromStores(true));
+ }
+
+ getStateFromStores(loaded) {
+ return {
+ teams: TeamStore.getAll(),
+ teamMembers: TeamStore.getTeamMembers(),
+ teamListings: TeamStore.getTeamListings(),
+ loaded
+ };
+ }
+
+ createCustomLogin() {
+ if (global.window.mm_license.IsLicensed === 'true' &&
+ global.window.mm_license.CustomBrand === 'true' &&
+ global.window.mm_config.EnableCustomBrand === 'true') {
+ const text = global.window.mm_config.CustomBrandText || '';
+
+ return (
+ <div>
+ <img
+ src={Client.getAdminRoute() + '/get_brand_image'}
+ />
+ <p dangerouslySetInnerHTML={{__html: TextFormatting.formatText(text)}}/>
+ </div>
+ );
+ }
+
+ return null;
+ }
+
+ render() {
+ var content;
+
+ let customClass;
+ const customContent = this.createCustomLogin();
+ if (customContent) {
+ customClass = 'branded';
+ }
+
+ var teamContents = [];
+ var isAlreadyMember = new Map();
+
+ for (var index in this.state.teamMembers) {
+ if (this.state.teamMembers.hasOwnProperty(index)) {
+ var teamMember = this.state.teamMembers[index];
+ var team = this.state.teams[teamMember.team_id];
+ isAlreadyMember[teamMember.team_id] = true;
+ teamContents.push(
+ <div
+ key={'team_' + team.name}
+ className='signup-team-dir'
+ >
+ <Link
+ to={'/' + team.name + '/channels/town-square'}
+ >
+ <span className='signup-team-dir__name'>{team.display_name}</span>
+ <span
+ className='glyphicon glyphicon-menu-right right signup-team-dir__arrow'
+ aria-hidden='true'
+ />
+ </Link>
+ </div>
+ );
+ }
+ }
+
+ if (!teamContents || teamContents.length === 0) {
+ teamContents = (
+ <div className='signup-team-dir-err'>
+ <div>
+ <FormattedMessage
+ id='signup_team.no_teams'
+ defaultMessage='You do not appear to be a member of any team. Please ask your administrator for an invite, join an open team if one exists or possibly create a new team.'
+ />
+ </div>
+ </div>
+ );
+ }
+
+ content = (
+ <div className='signup__content'>
+ <h4>
+ <FormattedMessage
+ id='signup_team.choose'
+ defaultMessage='Teams you are a member of:'
+ />
+ </h4>
+ <div className='signup-team-all'>
+ {teamContents}
+ </div>
+ </div>
+ );
+
+ var openTeamContents = [];
+
+ for (var id in this.state.teamListings) {
+ if (this.state.teamListings.hasOwnProperty(id) && !isAlreadyMember[id]) {
+ var openTeam = this.state.teamListings[id];
+ openTeamContents.push(
+ <div
+ key={'team_' + openTeam.name}
+ className='signup-team-dir'
+ >
+ <Link
+ to={`/signup_user_complete/?id=${openTeam.invite_id}`}
+ >
+ <span className='signup-team-dir__name'>{openTeam.display_name}</span>
+ <span
+ className='glyphicon glyphicon-menu-right right signup-team-dir__arrow'
+ aria-hidden='true'
+ />
+ </Link>
+ </div>
+ );
+ }
+ }
+
+ var openContent;
+ if (openTeamContents.length > 0) {
+ openContent = (
+ <div className='signup__content'>
+ <h4>
+ <FormattedMessage
+ id='signup_team.join_open'
+ defaultMessage='Open teams you can join: '
+ />
+ </h4>
+ <div className='signup-team-all'>
+ {openTeamContents}
+ </div>
+ </div>
+ );
+ }
+
+ if (!this.state.loaded) {
+ openContent = <LoadingScreen/>;
+ }
+
+ var isSystemAdmin = Utils.isSystemAdmin(UserStore.getCurrentUser().roles);
+
+ let teamSignUp;
+ if (isSystemAdmin || (global.window.mm_config.EnableTeamCreation === 'true' && !Utils.isMobileApp())) {
+ teamSignUp = (
+ <div className='margin--extra'>
+ <Link
+ to='/create_team'
+ className='signup-team-login'
+ >
+ <FormattedMessage
+ id='login.createTeam'
+ defaultMessage='Create a new team'
+ />
+ </Link>
+ </div>
+ );
+ }
+
+ let adminConsoleLink;
+ if (isSystemAdmin) {
+ adminConsoleLink = (
+ <div className='margin--extra'>
+ <Link
+ to='/admin_console'
+ className='signup-team-login'
+ >
+ <FormattedMessage
+ id='navbar_dropdown.console'
+ defaultMessage='System Console'
+ />
+ </Link>
+ </div>
+ );
+ }
+
+ return (
+ <div>
+ <ErrorBar/>
+ <div className='signup-header'>
+ <a
+ href='#'
+ onClick={GlobalActions.emitUserLoggedOutEvent}
+ >
+ <span className='fa fa-chevron-left'/>
+ <FormattedMessage
+ id='navbar_dropdown.logout'
+ />
+ </a>
+ </div>
+ <div className='col-sm-12'>
+ <div className={'signup-team__container ' + customClass}>
+ <div className='signup__markdown'>
+ {customContent}
+ </div>
+ <img
+ className='signup-team-logo'
+ src={logoImage}
+ />
+ <h1>{global.window.mm_config.SiteName}</h1>
+ <h4 className='color--light'>
+ <FormattedMessage
+ id='web.root.singup_info'
+ />
+ </h4>
+ {content}
+ {openContent}
+ {teamSignUp}
+ {adminConsoleLink}
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/webapp/components/should_verify_email.jsx b/webapp/components/should_verify_email.jsx
index 5103452b0..a95101ba1 100644
--- a/webapp/components/should_verify_email.jsx
+++ b/webapp/components/should_verify_email.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import {FormattedMessage} from 'react-intl';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import React from 'react';
import {Link} from 'react-router';
@@ -18,19 +18,19 @@ export default class ShouldVerifyEmail extends React.Component {
};
}
handleResend() {
- const teamName = this.props.location.query.teamname;
const email = this.props.location.query.email;
this.setState({resendStatus: 'sending'});
- Client.resendVerification(() => {
- this.setState({resendStatus: 'success'});
- },
- () => {
- this.setState({resendStatus: 'failure'});
- },
- teamName,
- email);
+ Client.resendVerification(
+ email,
+ () => {
+ this.setState({resendStatus: 'success'});
+ },
+ () => {
+ this.setState({resendStatus: 'failure'});
+ }
+ );
}
render() {
let resendConfirm = '';
diff --git a/webapp/components/sidebar_header.jsx b/webapp/components/sidebar_header.jsx
index ec3a03d17..143a3458a 100644
--- a/webapp/components/sidebar_header.jsx
+++ b/webapp/components/sidebar_header.jsx
@@ -7,6 +7,7 @@ import NavbarDropdown from './navbar_dropdown.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import * as Utils from 'utils/utils.jsx';
+import Client from 'utils/web_client.jsx';
import Constants from 'utils/constants.jsx';
const Preferences = Constants.Preferences;
@@ -61,7 +62,7 @@ export default class SidebarHeader extends React.Component {
profilePicture = (
<img
className='user__picture'
- src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at}
+ src={Client.getUsersRoute() + '/' + me.id + '/image?time=' + me.update_at}
/>
);
}
diff --git a/webapp/components/sidebar_right_menu.jsx b/webapp/components/sidebar_right_menu.jsx
index c7e6577fc..42bc7ce53 100644
--- a/webapp/components/sidebar_right_menu.jsx
+++ b/webapp/components/sidebar_right_menu.jsx
@@ -228,13 +228,16 @@ export default class SidebarRightMenu extends React.Component {
{manageLink}
{consoleLink}
<li>
- <Link to={Utils.getTeamURLFromAddressBar() + '/logout'}>
+ <a
+ href='#'
+ onClick={GlobalActions.emitUserLoggedOutEvent}
+ >
<i className='fa fa-sign-out'></i>
<FormattedMessage
id='sidebar_right_menu.logout'
defaultMessage='Logout'
/>
- </Link>
+ </a>
</li>
<li className='divider'></li>
{helpLink}
diff --git a/webapp/components/signup_team.jsx b/webapp/components/signup_team.jsx
deleted file mode 100644
index 634aa6e68..000000000
--- a/webapp/components/signup_team.jsx
+++ /dev/null
@@ -1,235 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ChoosePage from './team_signup_choose_auth.jsx';
-import EmailSignUpPage from './team_signup_with_email.jsx';
-import SSOSignupPage from './team_signup_with_sso.jsx';
-import LdapSignUpPage from './team_signup_with_ldap.jsx';
-import Constants from 'utils/constants.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-import React from 'react';
-import {Link} from 'react-router';
-
-import logoImage from 'images/logo.png';
-
-export default class TeamSignUp extends React.Component {
- constructor(props) {
- super(props);
-
- this.updatePage = this.updatePage.bind(this);
- this.onTeamUpdate = this.onTeamUpdate.bind(this);
-
- var count = 0;
-
- if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
- count = count + 1;
- }
-
- if (global.window.mm_config.EnableSignUpWithGitLab === 'true') {
- count = count + 1;
- }
-
- if (global.window.mm_config.EnableLdap === 'true') {
- count = count + 1;
- }
-
- if (count > 1) {
- this.state = {page: 'choose'};
- } else if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
- this.state = {page: 'email'};
- } else if (global.window.mm_config.EnableSignUpWithGitLab === 'true') {
- this.state = {page: 'gitlab'};
- } else if (global.window.mm_config.EnableLdap === 'true') {
- this.state = {page: 'ldap'};
- } else {
- this.state = {page: 'none'};
- }
- }
-
- updatePage(page) {
- this.setState({page});
- }
-
- componentWillMount() {
- if (global.window.mm_config.EnableTeamListing === 'true') {
- AsyncClient.getAllTeams();
- this.onTeamUpdate();
- }
- }
-
- componentDidMount() {
- TeamStore.addChangeListener(this.onTeamUpdate);
- }
-
- componentWillUnmount() {
- TeamStore.removeChangeListener(this.onTeamUpdate);
- }
-
- onTeamUpdate() {
- this.setState({
- teams: TeamStore.getAll()
- });
- }
-
- render() {
- let teamListing = null;
-
- if (global.window.mm_config.EnableTeamListing === 'true') {
- if (this.state.teams == null) {
- teamListing = (<div/>);
- } else if (this.state.teams.length === 0) {
- if (global.window.mm_config.EnableTeamCreation !== 'true') {
- teamListing = (
- <div>
- <FormattedMessage
- id='signup_team.noTeams'
- defaultMessage='There are no teams included in the Team Directory and team creation has been disabled.'
- />
- </div>
- );
- }
- } else {
- teamListing = (
- <div>
- <h4>
- <FormattedMessage
- id='signup_team.choose'
- defaultMessage='Choose a Team'
- />
- </h4>
- <div className='signup-team-all'>
- {
- Object.values(this.state.teams).map((team) => {
- if (team.allow_team_listing) {
- return (
- <div
- key={'team_' + team.name}
- className='signup-team-dir'
- >
- <Link
- to={'/' + team.name}
- >
- <span className='signup-team-dir__name'>{team.display_name}</span>
- <span
- className='glyphicon glyphicon-menu-right right signup-team-dir__arrow'
- aria-hidden='true'
- />
- </Link>
- </div>
- );
- }
- return null;
- })
- }
- </div>
- <h4>
- <FormattedMessage
- id='signup_team.createTeam'
- defaultMessage='Or Create a Team'
- />
- </h4>
- </div>
- );
- }
- }
-
- let signupMethod = null;
- let goBack = (
- <div className='signup-header'>
- <a
- href='#'
- onClick={
- (e) => {
- e.preventDefault();
- this.updatePage('choose');
- }
- }
- >
- <span className='fa fa-chevron-left'/>
- <FormattedMessage
- id='web.header.back'
- />
- </a>
- </div>
- );
-
- if (global.window.mm_config.EnableTeamCreation !== 'true') {
- if (teamListing == null) {
- signupMethod = (
- <FormattedMessage
- id='signup_team.disabled'
- defaultMessage='Team creation has been disabled. Please contact an administrator for access.'
- />
- );
- }
- } else if (this.state.page === 'choose') {
- signupMethod = (
- <ChoosePage
- updatePage={this.updatePage}
- />
- );
- goBack = null;
- } else if (this.state.page === 'email') {
- signupMethod = (
- <div>
- <EmailSignUpPage/>
- </div>
- );
- } else if (this.state.page === 'ldap') {
- return (
- <div>
- {teamListing}
- <LdapSignUpPage/>
- </div>
- );
- } else if (this.state.page === 'gitlab') {
- signupMethod = (
- <SSOSignupPage service={Constants.GITLAB_SERVICE}/>
- );
- } else if (this.state.page === 'google') {
- signupMethod = (
- <SSOSignupPage service={Constants.GOOGLE_SERVICE}/>
- );
- } else if (this.state.page === 'none') {
- signupMethod = (
- <FormattedMessage
- id='signup_team.none'
- defaultMessage='No team creation method has been enabled. Please contact an administrator for access.'
- />
- );
- goBack = null;
- }
-
- return (
- <div>
- {goBack}
- <div className='col-sm-12'>
- <div className='signup-team__container'>
- <img
- className='signup-team-logo'
- src={logoImage}
- />
- <h1>{global.window.mm_config.SiteName}</h1>
- <h4 className='color--light'>
- <FormattedMessage
- id='web.root.singup_info'
- />
- </h4>
- <div id='signup-team'>
- {teamListing}
- {signupMethod}
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
-
-TeamSignUp.propTypes = {
-};
-
diff --git a/webapp/components/signup_team_complete/components/signup_team_complete.jsx b/webapp/components/signup_team_complete/components/signup_team_complete.jsx
index 95b41dbde..00fdafe5f 100644
--- a/webapp/components/signup_team_complete/components/signup_team_complete.jsx
+++ b/webapp/components/signup_team_complete/components/signup_team_complete.jsx
@@ -74,6 +74,7 @@ export default class SignupTeamComplete extends React.Component {
SignupTeamComplete.defaultProps = {
};
+
SignupTeamComplete.propTypes = {
location: React.PropTypes.object,
children: React.PropTypes.node
diff --git a/webapp/components/signup_team_complete/components/team_signup_email_item.jsx b/webapp/components/signup_team_complete/components/team_signup_email_item.jsx
deleted file mode 100644
index c3903ca85..000000000
--- a/webapp/components/signup_team_complete/components/team_signup_email_item.jsx
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ReactDOM from 'react-dom';
-import * as Utils from 'utils/utils.jsx';
-
-import {intlShape, injectIntl, defineMessages} from 'react-intl';
-
-const holders = defineMessages({
- validEmail: {
- id: 'team_signup_email.validEmail',
- defaultMessage: 'Please enter a valid email address'
- },
- different: {
- id: 'team_signup_email.different',
- defaultMessage: 'Please use a different email than the one used at signup'
- },
- address: {
- id: 'team_signup_email.address',
- defaultMessage: 'Email Address'
- }
-});
-
-import React from 'react';
-
-class TeamSignupEmailItem extends React.Component {
- constructor(props) {
- super(props);
-
- this.getValue = this.getValue.bind(this);
- this.validate = this.validate.bind(this);
-
- this.state = {};
- }
- getValue() {
- return ReactDOM.findDOMNode(this.refs.email).value.trim();
- }
- validate(teamEmail) {
- const {formatMessage} = this.props.intl;
- const email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
-
- if (!email) {
- return true;
- }
-
- if (!Utils.isEmail(email)) {
- this.setState({emailError: formatMessage(holders.validEmail)});
- return false;
- } else if (email === teamEmail) {
- this.setState({emailError: formatMessage(holders.different)});
- return false;
- }
-
- this.setState({emailError: ''});
- return true;
- }
- render() {
- let emailError = null;
- let emailDivClass = 'form-group';
- if (this.state.emailError) {
- emailError = <label className='control-label'>{this.state.emailError}</label>;
- emailDivClass += ' has-error';
- }
-
- return (
- <div className={emailDivClass}>
- <input
- autoFocus={this.props.focus}
- type='email'
- ref='email'
- className='form-control'
- placeholder={this.props.intl.formatMessage(holders.address)}
- defaultValue={this.props.email}
- maxLength='128'
- spellCheck='false'
- />
- {emailError}
- </div>
- );
- }
-}
-
-TeamSignupEmailItem.propTypes = {
- intl: intlShape.isRequired,
- focus: React.PropTypes.bool,
- email: React.PropTypes.string
-};
-
-export default injectIntl(TeamSignupEmailItem, {withRef: true});
diff --git a/webapp/components/signup_team_complete/components/team_signup_finished.jsx b/webapp/components/signup_team_complete/components/team_signup_finished.jsx
deleted file mode 100644
index 9fbb8473a..000000000
--- a/webapp/components/signup_team_complete/components/team_signup_finished.jsx
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {FormattedMessage} from 'react-intl';
-
-import React from 'react';
-
-export default class FinishedPage extends React.Component {
- render() {
- return (
- <FormattedMessage
- id='signup_team_complete.completed'
- defaultMessage="You've already completed the signup process for this invitation or this invitation has expired."
- />
- );
- }
-}
diff --git a/webapp/components/signup_team_complete/components/team_signup_password_page.jsx b/webapp/components/signup_team_complete/components/team_signup_password_page.jsx
deleted file mode 100644
index 7b8b49e0c..000000000
--- a/webapp/components/signup_team_complete/components/team_signup_password_page.jsx
+++ /dev/null
@@ -1,225 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import * as Client from 'utils/client.jsx';
-import BrowserStore from 'stores/browser_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import Constants from 'utils/constants.jsx';
-
-import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import {browserHistory} from 'react-router';
-
-import logoImage from 'images/logo.png';
-
-const holders = defineMessages({
- passwordError: {
- id: 'team_signup_password.passwordError',
- defaultMessage: 'Please enter at least {chars} characters'
- },
- creating: {
- id: 'team_signup_password.creating',
- defaultMessage: 'Creating team...'
- }
-});
-
-import React from 'react';
-
-class TeamSignupPasswordPage extends React.Component {
- constructor(props) {
- super(props);
-
- this.submitBack = this.submitBack.bind(this);
- this.submitNext = this.submitNext.bind(this);
-
- this.state = {};
- }
- submitBack(e) {
- e.preventDefault();
- this.props.state.wizard = 'username';
- this.props.updateParent(this.props.state);
- }
- submitNext(e) {
- e.preventDefault();
-
- var password = ReactDOM.findDOMNode(this.refs.password).value.trim();
- if (!password || password.length < Constants.MIN_PASSWORD_LENGTH) {
- this.setState({passwordError: this.props.intl.formatMessage(holders.passwordError, {chars: Constants.MIN_PASSWORD_LENGTH})});
- return;
- }
-
- this.setState({passwordError: null, serverError: null});
- $('#finish-button').button('loading');
- var teamSignup = JSON.parse(JSON.stringify(this.props.state));
- teamSignup.user.password = password;
- teamSignup.user.allow_marketing = true;
- delete teamSignup.wizard;
-
- Client.createTeamFromSignup(teamSignup,
- () => {
- Client.track('signup', 'signup_team_08_complete');
-
- var props = this.props;
-
- Client.loginByEmail(
- teamSignup.team.name,
- teamSignup.team.email,
- teamSignup.user.password,
- '', // No MFA Token
- () => {
- UserStore.setLastEmail(teamSignup.team.email);
- if (this.props.hash > 0) {
- BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: 'finished'}));
- }
-
- $('#sign-up-button').button('reset');
- props.state.wizard = 'finished';
- props.updateParent(props.state, true);
-
- browserHistory.push('/' + teamSignup.team.name + '/channels/town-square');
- },
- (err) => {
- if (err.id === 'api.user.login.not_verified.app_error') {
- browserHistory.push('/should_verify_email?email=' + encodeURIComponent(teamSignup.team.email) + '&teamname=' + encodeURIComponent(teamSignup.team.name));
- } else {
- this.setState({serverError: err.message});
- $('#finish-button').button('reset');
- }
- }
- );
- },
- (err) => {
- this.setState({serverError: err.message});
- $('#finish-button').button('reset');
- }
- );
- }
- render() {
- Client.track('signup', 'signup_team_07_password');
-
- var passwordError = null;
- var passwordDivStyle = 'form-group';
- if (this.state.passwordError) {
- passwordError = <div className='form-group has-error'><label className='control-label'>{this.state.passwordError}</label></div>;
- passwordDivStyle = ' has-error';
- }
-
- var serverError = null;
- if (this.state.serverError) {
- serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
- }
-
- return (
- <div>
- <form>
- <img
- className='signup-team-logo'
- src={logoImage}
- />
- <h2 className='margin--less'>
- <FormattedMessage
- id='team_signup_password.yourPassword'
- defaultMessage='Your password'
- />
- </h2>
- <h5 className='color--light'>
- <FormattedMessage
- id='team_signup_password.selectPassword'
- defaultMessage="Select a password that you'll use to login with your email address:"
- />
- </h5>
- <div className='inner__content margin--extra'>
- <h5><strong>
- <FormattedMessage
- id='team_signup_password.email'
- defaultMessage='Email'
- />
- </strong></h5>
- <div className='block--gray form-group'>{this.props.state.team.email}</div>
- <div className={passwordDivStyle}>
- <div className='row'>
- <div className='col-sm-11'>
- <h5><strong>
- <FormattedMessage
- id='team_signup_password.choosePwd'
- defaultMessage='Choose your password'
- />
- </strong></h5>
- <input
- autoFocus={true}
- type='password'
- ref='password'
- className='form-control'
- placeholder=''
- maxLength='128'
- spellCheck='false'
- />
- <span className='color--light help-block'>
- <FormattedMessage
- id='team_signup_password.hint'
- defaultMessage='Passwords must contain {min} to {max} characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.'
- values={{
- min: Constants.MIN_PASSWORD_LENGTH,
- max: Constants.MAX_PASSWORD_LENGTH
- }}
- />
- </span>
- </div>
- </div>
- {passwordError}
- {serverError}
- </div>
- </div>
- <div className='form-group'>
- <button
- type='submit'
- className='btn btn-primary margin--extra'
- id='finish-button'
- data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + this.props.intl.formatMessage(holders.creating)}
- onClick={this.submitNext}
- >
- <FormattedMessage
- id='team_signup_password.finish'
- defaultMessage='Finish'
- />
- </button>
- </div>
- <p>
- <FormattedHTMLMessage
- id='team_signup_password.agreement'
- defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}."
- values={{
- siteName: global.window.mm_config.SiteName
- }}
- />
- </p>
- <div className='margin--extra'>
- <a
- href='#'
- onClick={this.submitBack}
- >
- <FormattedMessage
- id='team_signup_password.back'
- defaultMessage='Back to previous step'
- />
- </a>
- </div>
- </form>
- </div>
- );
- }
-}
-
-TeamSignupPasswordPage.defaultProps = {
- state: {},
- hash: ''
-};
-TeamSignupPasswordPage.propTypes = {
- intl: intlShape.isRequired,
- state: React.PropTypes.object,
- hash: React.PropTypes.string,
- updateParent: React.PropTypes.func
-};
-
-export default injectIntl(TeamSignupPasswordPage);
diff --git a/webapp/components/signup_team_complete/components/team_signup_send_invites_page.jsx b/webapp/components/signup_team_complete/components/team_signup_send_invites_page.jsx
deleted file mode 100644
index db060b6b9..000000000
--- a/webapp/components/signup_team_complete/components/team_signup_send_invites_page.jsx
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import EmailItem from './team_signup_email_item.jsx';
-import * as Client from 'utils/client.jsx';
-
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-import logoImage from 'images/logo.png';
-
-import React from 'react';
-
-export default class TeamSignupSendInvitesPage extends React.Component {
- constructor(props) {
- super(props);
- this.submitBack = this.submitBack.bind(this);
- this.submitNext = this.submitNext.bind(this);
- this.submitAddInvite = this.submitAddInvite.bind(this);
- this.submitSkip = this.submitSkip.bind(this);
- this.keySubmit = this.keySubmit.bind(this);
- this.state = {
- emailEnabled: global.window.mm_config.SendEmailNotifications === 'true'
- };
- }
- submitBack(e) {
- e.preventDefault();
- this.props.state.wizard = 'team_url';
-
- this.props.updateParent(this.props.state);
- }
- submitNext(e) {
- e.preventDefault();
-
- var valid = true;
-
- if (this.state.emailEnabled) {
- var emails = [];
-
- for (var i = 0; i < this.props.state.invites.length; i++) {
- if (this.refs['email_' + i].getWrappedInstance().validate(this.props.state.team.email)) {
- emails.push(this.refs['email_' + i].getWrappedInstance().getValue());
- } else {
- valid = false;
- }
- }
-
- if (valid) {
- this.props.state.invites = emails;
- }
- }
-
- if (valid) {
- this.props.state.wizard = 'username';
- this.props.updateParent(this.props.state);
- }
- }
- submitAddInvite(e) {
- e.preventDefault();
- this.props.state.wizard = 'send_invites';
- if (!this.props.state.invites) {
- this.props.state.invites = [];
- }
- this.props.state.invites.push('');
- this.props.updateParent(this.props.state);
- }
- submitSkip(e) {
- e.preventDefault();
- this.props.state.wizard = 'username';
- this.props.updateParent(this.props.state);
- }
- keySubmit(e) {
- if (e && e.keyCode === 13) {
- this.submitNext(e);
- }
- }
- componentDidMount() {
- if (!this.state.emailEnabled) {
- // Must use keypress not keyup due to event chain of pressing enter
- $('body').keypress(this.keySubmit);
- }
- }
- componentWillUnmount() {
- if (!this.state.emailEnabled) {
- $('body').off('keypress', this.keySubmit);
- }
- }
- render() {
- Client.track('signup', 'signup_team_05_send_invites');
-
- var content = null;
- var bottomContent = null;
-
- if (this.state.emailEnabled) {
- var emails = [];
-
- for (var i = 0; i < this.props.state.invites.length; i++) {
- if (i === 0) {
- emails.push(
- <EmailItem
- focus={true}
- key={i}
- ref={'email_' + i}
- email={this.props.state.invites[i]}
- />
- );
- } else {
- emails.push(
- <EmailItem
- focus={false}
- key={i}
- ref={'email_' + i}
- email={this.props.state.invites[i]}
- />
- );
- }
- }
-
- content = (
- <div>
- {emails}
- <div className='form-group text-right'>
- <a
- href='#'
- onClick={this.submitAddInvite}
- >
- <FormattedMessage
- id='team_signup_send_invites.addInvitation'
- defaultMessage='Add Invitation'
- />
- </a>
- </div>
- </div>
- );
-
- bottomContent = (
- <p className='color--light'>
- <FormattedHTMLMessage
- id='team_signup_send_invites.prefer'
- defaultMessage='if you prefer, you can invite team members later<br /> and '
- />
- <a
- href='#'
- onClick={this.submitSkip}
- >
- <FormattedMessage
- id='team_signup_send_invites.skip'
- defaultMessage='skip this step '
- />
- </a>
- <FormattedMessage
- id='team_signup_send_invites.forNow'
- defaultMessage='for now.'
- />
- </p>
- );
- } else {
- content = (
- <div className='form-group color--light'>
- <FormattedMessage
- id='team_signup_send_invites.disabled'
- defaultMessage='Email is currently disabled for your team, and emails cannot be sent. Contact your system administrator to enable email and email invitations.'
- />
- </div>
- );
- }
-
- return (
- <div>
- <form>
- <img
- className='signup-team-logo'
- src={logoImage}
- />
- <h2>
- <FormattedMessage
- id='team_signup_send_invites.title'
- defaultMessage='Invite Team Members'
- />
- </h2>
- {content}
- <div className='form-group'>
- <button
- type='submit'
- className='btn-primary btn'
- onClick={this.submitNext}
- >
- <FormattedMessage
- id='team_signup_send_invites.next'
- defaultMessage='Next'
- /><i className='glyphicon glyphicon-chevron-right'/>
- </button>
- </div>
- </form>
- {bottomContent}
- <div className='margin--extra'>
- <a
- href='#'
- onClick={this.submitBack}
- >
- <FormattedMessage
- id='team_signup_send_invites.back'
- defaultMessage='Back to previous step'
- />
- </a>
- </div>
- </div>
- );
- }
-}
-
-TeamSignupSendInvitesPage.propTypes = {
- state: React.PropTypes.object.isRequired,
- updateParent: React.PropTypes.func.isRequired
-};
diff --git a/webapp/components/signup_team_complete/components/team_signup_username_page.jsx b/webapp/components/signup_team_complete/components/team_signup_username_page.jsx
deleted file mode 100644
index b79c1179f..000000000
--- a/webapp/components/signup_team_complete/components/team_signup_username_page.jsx
+++ /dev/null
@@ -1,169 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ReactDOM from 'react-dom';
-import * as Utils from 'utils/utils.jsx';
-import * as Client from 'utils/client.jsx';
-import Constants from 'utils/constants.jsx';
-
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
-
-import logoImage from 'images/logo.png';
-
-const holders = defineMessages({
- reserved: {
- id: 'team_signup_username.reserved',
- defaultMessage: 'This username is reserved, please choose a new one.'
- },
- invalid: {
- id: 'team_signup_username.invalid',
- defaultMessage: 'Username must begin with a letter, and contain between {min} to {max} characters in total, which may be numbers, lowercase letters, or any of the symbols \'.\', \'-\', or \'_\''
- }
-});
-
-import React from 'react';
-
-class TeamSignupUsernamePage extends React.Component {
- constructor(props) {
- super(props);
-
- this.submitBack = this.submitBack.bind(this);
- this.submitNext = this.submitNext.bind(this);
-
- this.state = {};
- }
- submitBack(e) {
- e.preventDefault();
- if (global.window.mm_config.SendEmailNotifications === 'true') {
- this.props.state.wizard = 'send_invites';
- } else {
- this.props.state.wizard = 'team_url';
- }
-
- this.props.updateParent(this.props.state);
- }
- submitNext(e) {
- e.preventDefault();
-
- const {formatMessage} = this.props.intl;
- var name = ReactDOM.findDOMNode(this.refs.name).value.trim().toLowerCase();
-
- var usernameError = Utils.isValidUsername(name);
- if (usernameError === 'Cannot use a reserved word as a username.') { //this should be change to some kind of ID
- this.setState({nameError: formatMessage(holders.reserved)});
- return;
- } else if (usernameError) {
- this.setState({nameError: formatMessage(holders.invalid, {min: Constants.MIN_USERNAME_LENGTH, max: Constants.MAX_USERNAME_LENGTH})});
- return;
- }
-
- this.props.state.wizard = 'password';
- this.props.state.user.username = name;
- this.props.updateParent(this.props.state);
- }
- render() {
- Client.track('signup', 'signup_team_06_username');
-
- var nameError = null;
- var nameHelpText = (
- <span className='color--light help-block'>
- <FormattedMessage
- id='team_signup_username.hint'
- defaultMessage="Usernames must begin with a letter and contain between {min} to {max} characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'"
- values={{
- min: Constants.MIN_USERNAME_LENGTH,
- max: Constants.MAX_USERNAME_LENGTH
- }}
- />
- </span>
- );
- var nameDivClass = 'form-group';
- if (this.state.nameError) {
- nameError = <label className='control-label'>{this.state.nameError}</label>;
- nameHelpText = '';
- nameDivClass += ' has-error';
- }
-
- return (
- <div>
- <form>
- <img
- className='signup-team-logo'
- src={logoImage}
- />
- <h2 className='margin--less'>
- <FormattedMessage
- id='team_signup_username.username'
- defaultMessage='Your username'
- />
- </h2>
- <h5 className='color--light'>
- <FormattedMessage
- id='team_signup_username.memorable'
- defaultMessage='Select a memorable username that makes it easy for teammates to identify you:'
- />
- </h5>
- <div className='inner__content margin--extra'>
- <div className={nameDivClass}>
- <div className='row'>
- <div className='col-sm-11'>
- <h5><strong>
- <FormattedMessage
- id='team_signup_username.chooseUsername'
- defaultMessage='Choose your username'
- />
- </strong></h5>
- <input
- autoFocus={true}
- type='text'
- ref='name'
- className='form-control'
- placeholder=''
- defaultValue={this.props.state.user.username}
- maxLength={Constants.MAX_USERNAME_LENGTH}
- spellCheck='false'
- />
- {nameHelpText}
- </div>
- </div>
- {nameError}
- </div>
- </div>
- <button
- type='submit'
- className='btn btn-primary margin--extra'
- onClick={this.submitNext}
- >
- <FormattedMessage
- id='team_signup_username.next'
- defaultMessage='Next'
- />
- <i className='glyphicon glyphicon-chevron-right'></i>
- </button>
- <div className='margin--extra'>
- <a
- href='#'
- onClick={this.submitBack}
- >
- <FormattedMessage
- id='team_signup_username.back'
- defaultMessage='Back to previous step'
- />
- </a>
- </div>
- </form>
- </div>
- );
- }
-}
-
-TeamSignupUsernamePage.defaultProps = {
- state: null
-};
-TeamSignupUsernamePage.propTypes = {
- intl: intlShape.isRequired,
- state: React.PropTypes.object,
- updateParent: React.PropTypes.func
-};
-
-export default injectIntl(TeamSignupUsernamePage);
diff --git a/webapp/components/signup_team_complete/components/team_signup_welcome_page.jsx b/webapp/components/signup_team_complete/components/team_signup_welcome_page.jsx
deleted file mode 100644
index 15b708128..000000000
--- a/webapp/components/signup_team_complete/components/team_signup_welcome_page.jsx
+++ /dev/null
@@ -1,243 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ReactDOM from 'react-dom';
-import * as Utils from 'utils/utils.jsx';
-import * as Client from 'utils/client.jsx';
-import BrowserStore from 'stores/browser_store.jsx';
-
-import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-import {browserHistory} from 'react-router';
-
-import logoImage from 'images/logo.png';
-
-const holders = defineMessages({
- storageError: {
- id: 'team_signup_welcome.storageError',
- defaultMessage: 'This service requires local storage to be enabled. Please enable it or exit private browsing.'
- },
- validEmailError: {
- id: 'team_signup_welcome.validEmailError',
- defaultMessage: 'Please enter a valid email address'
- },
- address: {
- id: 'team_signup_welcome.address',
- defaultMessage: 'Email Address'
- }
-});
-
-import React from 'react';
-
-class TeamSignupWelcomePage extends React.Component {
- constructor(props) {
- super(props);
-
- this.submitNext = this.submitNext.bind(this);
- this.handleDiffEmail = this.handleDiffEmail.bind(this);
- this.handleDiffSubmit = this.handleDiffSubmit.bind(this);
- this.handleKeyPress = this.handleKeyPress.bind(this);
-
- this.state = {useDiff: false};
-
- document.addEventListener('keyup', this.handleKeyPress, false);
- }
- submitNext(e) {
- if (!BrowserStore.isLocalStorageSupported()) {
- this.setState({storageError: this.props.intl.formatMessage(holders.storageError)});
- return;
- }
- e.preventDefault();
- this.props.state.wizard = 'team_display_name';
- this.props.updateParent(this.props.state);
- }
- handleDiffEmail(e) {
- e.preventDefault();
- this.setState({useDiff: true});
- }
- handleDiffSubmit(e) {
- e.preventDefault();
-
- const {formatMessage} = this.props.intl;
- var state = {useDiff: true, serverError: ''};
-
- var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
- if (!email || !Utils.isEmail(email)) {
- state.emailError = formatMessage(holders.validEmailError);
- this.setState(state);
- return;
- } else if (!BrowserStore.isLocalStorageSupported()) {
- state.emailError = formatMessage(holders.storageError);
- this.setState(state);
- return;
- }
- state.emailError = '';
-
- Client.signupTeam(email,
- function success(data) {
- if (data.follow_link) {
- browserHistory.push(data.follow_link);
- } else {
- this.props.state.wizard = 'finished';
- this.props.updateParent(this.props.state);
- browserHistory.push('/signup_team_confirm/?email=' + encodeURIComponent(email));
- }
- }.bind(this),
- function error(err) {
- let errorMsg = err.message;
-
- if (err.detailed_error.indexOf('Invalid RCPT TO address provided') >= 0) {
- errorMsg = formatMessage(holders.validEmailError);
- }
-
- this.setState({emailError: '', serverError: errorMsg});
- }.bind(this)
- );
- }
- handleKeyPress(event) {
- if (event.keyCode === 13) {
- if (this.state.useDiff) {
- this.handleDiffSubmit(event);
- } else {
- this.submitNext(event);
- }
- }
- }
- componentWillUnmount() {
- document.removeEventListener('keyup', this.handleKeyPress, false);
- }
- render() {
- Client.track('signup', 'signup_team_01_welcome');
-
- var storageError = null;
- if (this.state.storageError) {
- storageError = <label className='control-label'>{this.state.storageError}</label>;
- }
-
- var emailError = null;
- var emailDivClass = 'form-group';
- if (this.state.emailError) {
- emailError = <label className='control-label'>{this.state.emailError}</label>;
- emailDivClass += ' has-error';
- }
-
- var serverError = null;
- if (this.state.serverError) {
- serverError = (
- <div className='form-group has-error'>
- <label className='control-label'>{this.state.serverError}</label>
- </div>
- );
- }
-
- var differentEmailLinkClass = '';
- var emailDivContainerClass = 'hidden';
- if (this.state.useDiff) {
- differentEmailLinkClass = 'hidden';
- emailDivContainerClass = '';
- }
-
- return (
- <div>
- <img
- className='signup-team-logo'
- src={logoImage}
- />
- <h3 className='sub-heading'>
- <FormattedMessage
- id='team_signup_welcome.welcome'
- defaultMessage='Welcome to:'
- />
- </h3>
- <h1 className='margin--top-none'>{global.window.mm_config.SiteName}</h1>
- <p className='margin--less'>
- <FormattedMessage
- id='team_signup_welcome.lets'
- defaultMessage="Let's set up your new team"
- />
- </p>
- <div>
- <FormattedMessage
- id='team_signup_welcome.confirm'
- defaultMessage='Please confirm your email address:'
- />
- <br/>
- <div className='inner__content'>
- <div className='block--gray'>{this.props.state.team.email}</div>
- </div>
- </div>
- <p className='margin--extra color--light'>
- <FormattedHTMLMessage
- id='team_signup_welcome.admin'
- defaultMessage='Your account will administer the new team site. <br />
- You can add other administrators later.'
- />
- </p>
- <div className='form-group'>
- <button
- className='btn-primary btn form-group'
- type='submit'
- onClick={this.submitNext}
- >
- <i className='glyphicon glyphicon-ok'></i>
- <FormattedMessage
- id='team_signup_welcome.yes'
- defaultMessage='Yes, this address is correct'
- />
- </button>
- {storageError}
- </div>
- <hr/>
- <div className={emailDivContainerClass}>
- <div className={emailDivClass}>
- <div className='row'>
- <div className='col-sm-9'>
- <input
- type='email'
- ref='email'
- className='form-control'
- placeholder={this.props.intl.formatMessage(holders.address)}
- maxLength='128'
- spellCheck='false'
- />
- </div>
- </div>
- {emailError}
- </div>
- {serverError}
- <button
- className='btn btn-md btn-primary'
- type='button'
- onClick={this.handleDiffSubmit}
- >
- <FormattedMessage
- id='team_signup_welcome.instead'
- defaultMessage='Use this instead'
- />
- </button>
- </div>
- <a
- href='#'
- onClick={this.handleDiffEmail}
- className={differentEmailLinkClass}
- >
- <FormattedMessage
- id='team_signup_welcome.different'
- defaultMessage='Use a different email'
- />
- </a>
- </div>
- );
- }
-}
-
-TeamSignupWelcomePage.defaultProps = {
- state: {}
-};
-TeamSignupWelcomePage.propTypes = {
- intl: intlShape.isRequired,
- updateParent: React.PropTypes.func.isRequired,
- state: React.PropTypes.object
-};
-
-export default injectIntl(TeamSignupWelcomePage);
diff --git a/webapp/components/signup_team_confirm.jsx b/webapp/components/signup_team_confirm.jsx
deleted file mode 100644
index 117a0b068..000000000
--- a/webapp/components/signup_team_confirm.jsx
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import {Link} from 'react-router';
-
-import React from 'react';
-
-export default class SignupTeamConfirm extends React.Component {
- render() {
- return (
- <div>
- <div className='signup-header'>
- <Link to='/'>
- <span className='fa fa-chevron-left'/>
- <FormattedMessage
- id='web.header.back'
- />
- </Link>
- </div>
- <div className='col-sm-12'>
- <div classNameName='signup-team__container'>
- <h3>
- <FormattedMessage
- id='signup_team_confirm.title'
- defaultMessage='Sign up Complete'
- />
- </h3>
- <p>
- <FormattedHTMLMessage
- id='signup_team_confirm.checkEmail'
- defaultMessage='Please check your email: <strong>{email}</strong><br />Your email contains a link to set up your team'
- values={{
- email: this.props.location.query.email
- }}
- />
- </p>
- </div>
- </div>
- </div>
- );
- }
-}
-
-SignupTeamConfirm.defaultProps = {
-};
-SignupTeamConfirm.propTypes = {
- location: React.PropTypes.object
-};
diff --git a/webapp/components/signup_user_complete.jsx b/webapp/components/signup_user_complete.jsx
index 9e821289b..666e72e13 100644
--- a/webapp/components/signup_user_complete.jsx
+++ b/webapp/components/signup_user_complete.jsx
@@ -3,12 +3,13 @@
import LoadingScreen from 'components/loading_screen.jsx';
import LoginLdap from 'components/login/components/login_ldap.jsx';
+import * as GlobalActions from 'action_creators/global_actions.jsx';
import BrowserStore from 'stores/browser_store.jsx';
import UserStore from 'stores/user_store.jsx';
import * as Utils from 'utils/utils.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import Constants from 'utils/constants.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
@@ -24,7 +25,6 @@ class SignupUserComplete extends React.Component {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
- this.inviteInfoRecieved = this.inviteInfoRecieved.bind(this);
this.handleLdapSignup = this.handleLdapSignup.bind(this);
this.state = {
@@ -34,7 +34,10 @@ class SignupUserComplete extends React.Component {
email: '',
teamDisplayName: '',
teamName: '',
- teamId: ''
+ teamId: '',
+ openServer: false,
+ loading: true,
+ inviteId: ''
};
}
componentWillMount() {
@@ -46,19 +49,91 @@ class SignupUserComplete extends React.Component {
let teamDisplayName = '';
let teamName = '';
let teamId = '';
+ let openServer = false;
+ let loading = true;
+
+ if ((inviteId && inviteId.length > 0) || (hash && hash.length > 0)) {
+ // if we are already logged in then attempt to just join the team
+ if (UserStore.getCurrentUser()) {
+ loading = true;
+ Client.addUserToTeamFromInvite(
+ data,
+ hash,
+ inviteId,
+ () => {
+ GlobalActions.emitInitialLoad(
+ () => {
+ browserHistory.push('/select_team');
+ }
+ );
+ },
+ (err) => {
+ this.setState({
+ noOpenServerError: true,
+ serverError: err.message,
+ loading: false
+ });
+ }
+ );
+ } else if (hash) {
+ // If we have a hash in the url then we are attempting to access a private team
+ const parsedData = JSON.parse(data);
+ usedBefore = BrowserStore.getGlobalItem(hash);
+ email = parsedData.email;
+ teamDisplayName = parsedData.display_name;
+ teamName = parsedData.name;
+ teamId = parsedData.id;
+ loading = false;
+ } else {
+ loading = true;
+ Client.getInviteInfo(
+ inviteId,
+ (inviteData) => {
+ if (!inviteData) {
+ return;
+ }
- // If we have a hash in the url then we are attempting to access a private team
- if (hash) {
- const parsedData = JSON.parse(data);
- usedBefore = BrowserStore.getGlobalItem(hash);
- email = parsedData.email;
- teamDisplayName = parsedData.display_name;
- teamName = parsedData.name;
- teamId = parsedData.id;
+ this.setState({
+ serverError: null,
+ teamDisplayName: inviteData.display_name,
+ teamName: inviteData.name,
+ teamId: inviteData.id,
+ loading: false
+ });
+ },
+ () => {
+ this.setState({
+ noOpenServerError: true,
+ loading: false,
+ serverError:
+ <FormattedMessage
+ id='signup_user_completed.invalid_invite'
+ defaultMessage='The invite link was invalid. Please speak with your Administrator to receive an invitation.'
+ />
+ });
+ }
+ );
+
+ data = '';
+ hash = '';
+ }
+ } else if (global.window.mm_config.EnableOpenServer === 'true' || UserStore.getNoAccounts()) {
+ // If this is the first account then let them create an account anyway.
+ // The server will verify it's the first account before allowing creation.
+ // Of if the server is open then we don't care.
+ openServer = true;
+ loading = false;
} else {
- Client.getInviteInfo(this.inviteInfoRecieved, null, inviteId);
- data = '';
- hash = '';
+ loading = false;
+ this.setState({
+ noOpenServerError: true,
+ serverError:
+ <FormattedMessage
+ id='signup_user_completed.no_open_server'
+ defaultMessage='This server does not allow open signups. Please speak with your Administrator to receive an invitation.'
+ />,
+ loading: false
+ });
}
this.setState({
@@ -68,29 +143,25 @@ class SignupUserComplete extends React.Component {
email,
teamDisplayName,
teamName,
- teamId
- });
- }
- inviteInfoRecieved(data) {
- if (!data) {
- return;
- }
-
- this.setState({
- teamDisplayName: data.display_name,
- teamName: data.name,
- teamId: data.id
+ teamId,
+ openServer,
+ inviteId,
+ loading
});
}
handleLdapSignup(method, loginId, password, token) {
- Client.loginByLdap(this.state.teamName, loginId, password, token,
+ Client.loginByLdap(loginId, password, token,
() => {
const redirect = Utils.getUrlParameter('redirect');
if (redirect) {
browserHistory.push(decodeURIComponent(redirect));
} else {
- browserHistory.push('/' + this.state.teamName + '/channels/town-square');
+ GlobalActions.emitInitialLoad(
+ () => {
+ browserHistory.push('/select_team');
+ }
+ );
}
},
(err) => {
@@ -187,28 +258,34 @@ class SignupUserComplete extends React.Component {
});
const user = {
- team_id: this.state.teamId,
email: providedEmail,
username: providedUsername,
password: providedPassword,
allow_marketing: true
};
- Client.createUser(user, this.state.data, this.state.hash,
+ Client.createUserWithInvite(user,
+ this.state.data,
+ this.state.hash,
+ this.state.inviteId,
() => {
Client.track('signup', 'signup_user_02_complete');
-
- Client.loginByEmail(
- this.state.teamName,
+ Client.login(
user.email,
+ null,
user.password,
- '', // No MFA Token
+ '',
() => {
UserStore.setLastEmail(user.email);
if (this.state.hash > 0) {
BrowserStore.setGlobalItem(this.state.hash, JSON.stringify({usedBefore: true}));
}
- browserHistory.push('/' + this.state.teamName + '/channels/town-square');
+
+ GlobalActions.emitInitialLoad(
+ () => {
+ browserHistory.push('/select_team');
+ }
+ );
},
(err) => {
if (err.id === 'api.user.login.not_verified.app_error') {
@@ -239,9 +316,7 @@ class SignupUserComplete extends React.Component {
);
}
- // If we haven't got a team id yet we are waiting for
- // the client so just show the standard loading screen
- if (this.state.teamId === '') {
+ if (this.state.loading) {
return (<LoadingScreen/>);
}
@@ -349,7 +424,7 @@ class SignupUserComplete extends React.Component {
<a
className='btn btn-custom-login gitlab'
key='gitlab'
- href={'/api/v1/oauth/gitlab/signup' + window.location.search + '&team=' + encodeURIComponent(this.state.teamName)}
+ href={Client.getOAuthRoute() + '/gitlab/signup' + window.location.search}
>
<span className='icon'/>
<span>
@@ -367,7 +442,7 @@ class SignupUserComplete extends React.Component {
<a
className='btn btn-custom-login google'
key='google'
- href={'/api/v1/oauth/google/signup' + window.location.search + '&team=' + encodeURIComponent(this.state.teamName)}
+ href={Client.getOAuthRoute() + '/google/signup' + window.location.search + '&team=' + encodeURIComponent(this.state.teamName)}
>
<span className='icon'/>
<span>
@@ -497,12 +572,33 @@ class SignupUserComplete extends React.Component {
);
}
+ let terms = (
+ <p>
+ <FormattedHTMLMessage
+ id='create_team.agreement'
+ defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}."
+ values={{
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ </p>
+ );
+
+ if (this.state.noOpenServerError) {
+ signupMessage = null;
+ ldapSignup = null;
+ emailSignup = null;
+ terms = null;
+ }
+
return (
<div>
<div className='signup-header'>
<Link to='/'>
- <span classNameNameName='fa fa-chevron-left'/>
- <FormattedMessage id='web.header.back'/>
+ <span className='fa fa-chevron-left'/>
+ <FormattedMessage
+ id='web.header.back'
+ />
</Link>
</div>
<div className='col-sm-12'>
@@ -511,22 +607,12 @@ class SignupUserComplete extends React.Component {
className='signup-team-logo'
src={logoImage}
/>
- <h5 className='margin--less'>
- <FormattedMessage
- id='signup_user_completed.welcome'
- defaultMessage='Welcome to:'
- />
- </h5>
- <h2 className='signup-team__name'>{this.state.teamName}</h2>
- <h2 className='signup-team__subdomain'>
+ <h1>{global.window.mm_config.SiteName}</h1>
+ <h4 className='color--light'>
<FormattedMessage
- id='signup_user_completed.onSite'
- defaultMessage='on {siteName}'
- values={{
- siteName: global.window.mm_config.SiteName
- }}
+ id='web.root.singup_info'
/>
- </h2>
+ </h4>
<h4 className='color--light'>
<FormattedMessage
id='signup_user_completed.lets'
@@ -537,6 +623,7 @@ class SignupUserComplete extends React.Component {
{ldapSignup}
{emailSignup}
{serverError}
+ {terms}
</div>
</div>
</div>
diff --git a/webapp/components/suggestion/at_mention_provider.jsx b/webapp/components/suggestion/at_mention_provider.jsx
index 90ec6e660..79ac8aaf1 100644
--- a/webapp/components/suggestion/at_mention_provider.jsx
+++ b/webapp/components/suggestion/at_mention_provider.jsx
@@ -4,6 +4,7 @@
import SuggestionStore from 'stores/suggestion_store.jsx';
import UserStore from 'stores/user_store.jsx';
import * as Utils from 'utils/utils.jsx';
+import Client from 'utils/web_client.jsx';
import {FormattedMessage} from 'react-intl';
@@ -42,7 +43,7 @@ class AtMentionSuggestion extends React.Component {
icon = (
<img
className='mention__image'
- src={'/api/v1/users/' + item.id + '/image?time=' + item.update_at}
+ src={Client.getUsersRoute() + '/' + item.id + '/image?time=' + item.update_at}
/>
);
}
diff --git a/webapp/components/suggestion/search_user_provider.jsx b/webapp/components/suggestion/search_user_provider.jsx
index eeaee68a7..b7234469a 100644
--- a/webapp/components/suggestion/search_user_provider.jsx
+++ b/webapp/components/suggestion/search_user_provider.jsx
@@ -3,6 +3,7 @@
import SuggestionStore from 'stores/suggestion_store.jsx';
import UserStore from 'stores/user_store.jsx';
+import Client from 'utils/web_client.jsx';
import React from 'react';
@@ -22,7 +23,7 @@ class SearchUserSuggestion extends React.Component {
>
<img
className='profile-img rounded'
- src={'/api/v1/users/' + item.id + '/image?time=' + item.update_at}
+ src={Client.getUsersRoute() + '/' + item.id + '/image?time=' + item.update_at}
/>
<i className='fa fa fa-plus-square'></i>{item.username}
</div>
diff --git a/webapp/components/team_export_tab.jsx b/webapp/components/team_export_tab.jsx
index 9bd5785a0..37f886aab 100644
--- a/webapp/components/team_export_tab.jsx
+++ b/webapp/components/team_export_tab.jsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import {FormattedMessage} from 'react-intl';
diff --git a/webapp/components/team_general_tab.jsx b/webapp/components/team_general_tab.jsx
index c27e8ca59..1f783fe9f 100644
--- a/webapp/components/team_general_tab.jsx
+++ b/webapp/components/team_general_tab.jsx
@@ -5,7 +5,7 @@ import $ from 'jquery';
import SettingItemMin from './setting_item_min.jsx';
import SettingItemMax from './setting_item_max.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import TeamStore from 'stores/team_store.jsx';
@@ -42,7 +42,7 @@ const holders = defineMessages({
},
openInviteTitle: {
id: 'general_tab.openInviteTitle',
- defaultMessage: 'Allow anyone to sign-up from login page'
+ defaultMessage: 'Allow anyone to join this team'
},
codeTitle: {
id: 'general_tab.codeTitle',
@@ -68,7 +68,6 @@ class GeneralTab extends React.Component {
this.handleNameSubmit = this.handleNameSubmit.bind(this);
this.handleInviteIdSubmit = this.handleInviteIdSubmit.bind(this);
this.handleOpenInviteSubmit = this.handleOpenInviteSubmit.bind(this);
- this.handleTeamListingSubmit = this.handleTeamListingSubmit.bind(this);
this.handleClose = this.handleClose.bind(this);
this.onUpdateNameSection = this.onUpdateNameSection.bind(this);
this.updateName = this.updateName.bind(this);
@@ -76,8 +75,6 @@ class GeneralTab extends React.Component {
this.updateInviteId = this.updateInviteId.bind(this);
this.onUpdateOpenInviteSection = this.onUpdateOpenInviteSection.bind(this);
this.handleOpenInviteRadio = this.handleOpenInviteRadio.bind(this);
- this.onUpdateTeamListingSection = this.onUpdateTeamListingSection.bind(this);
- this.handleTeamListingRadio = this.handleTeamListingRadio.bind(this);
this.handleGenerateInviteId = this.handleGenerateInviteId.bind(this);
this.state = this.setupInitialState(props);
@@ -96,12 +93,19 @@ class GeneralTab extends React.Component {
name: team.display_name,
invite_id: team.invite_id,
allow_open_invite: team.allow_open_invite,
- allow_team_listing: team.allow_team_listing,
serverError: '',
clientError: ''
};
}
+ componentWillReceiveProps(nextProps) {
+ this.setState({
+ name: nextProps.team.display_name,
+ invite_id: nextProps.team.invite_id,
+ allow_open_invite: nextProps.team.allow_open_invite
+ });
+ }
+
handleGenerateInviteId(e) {
e.preventDefault();
@@ -117,14 +121,6 @@ class GeneralTab extends React.Component {
this.setState({allow_open_invite: openInvite});
}
- handleTeamListingRadio(listing) {
- if (global.window.mm_config.EnableTeamListing !== 'true' && listing) {
- this.setState({clientError: this.props.intl.formatMessage(holders.dirDisabled)});
- } else {
- this.setState({allow_team_listing: listing});
- }
- }
-
handleOpenInviteSubmit(e) {
e.preventDefault();
@@ -145,26 +141,6 @@ class GeneralTab extends React.Component {
);
}
- handleTeamListingSubmit(e) {
- e.preventDefault();
-
- var state = {serverError: '', clientError: ''};
-
- var data = this.props.team;
- data.allow_team_listing = this.state.allow_team_listing;
- Client.updateTeam(data,
- (team) => {
- TeamStore.saveTeam(team);
- TeamStore.emitChange();
- this.updateSection('');
- },
- (err) => {
- state.serverError = err.message;
- this.setState(state);
- }
- );
- }
-
handleNameSubmit(e) {
e.preventDefault();
@@ -239,12 +215,6 @@ class GeneralTab extends React.Component {
);
}
- componentWillReceiveProps(newProps) {
- if (newProps.team && newProps.teamDisplayName) {
- this.setState({name: newProps.teamDisplayName});
- }
- }
-
handleClose() {
this.updateSection('');
}
@@ -284,15 +254,6 @@ class GeneralTab extends React.Component {
}
}
- onUpdateTeamListingSection(e) {
- e.preventDefault();
- if (this.props.activeSection === 'team_listing') {
- this.updateSection('');
- } else {
- this.updateSection('team_listing');
- }
- }
-
updateName(e) {
e.preventDefault();
this.setState({name: e.target.value});
@@ -313,105 +274,8 @@ class GeneralTab extends React.Component {
serverError = this.state.serverError;
}
- const enableTeamListing = global.window.mm_config.EnableTeamListing === 'true';
const {formatMessage} = this.props.intl;
- let teamListingSection;
- if (this.props.activeSection === 'team_listing') {
- const inputs = [];
- let submitHandle = null;
-
- if (enableTeamListing) {
- submitHandle = this.handleTeamListingSubmit;
-
- inputs.push(
- <div key='userTeamListingOptions'>
- <div className='radio'>
- <label>
- <input
- name='userTeamListingOptions'
- type='radio'
- defaultChecked={this.state.allow_team_listing}
- onChange={this.handleTeamListingRadio.bind(this, true)}
- />
- <FormattedMessage
- id='general_tab.yes'
- defaultMessage='Yes'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- ref='teamListingRadioNo'
- name='userTeamListingOptions'
- type='radio'
- defaultChecked={!this.state.allow_team_listing}
- onChange={this.handleTeamListingRadio.bind(this, false)}
- />
- <FormattedMessage
- id='general_tab.no'
- defaultMessage='No'
- />
- </label>
- <br/>
- </div>
- <div>
- <br/>
- <FormattedMessage
- id='general_tab.includeDirDesc'
- defaultMessage='Including this team will display the team name from the Team Directory section of the Home Page, and provide a link to the sign-in page.'
- />
- </div>
- </div>
- );
- } else {
- inputs.push(
- <div key='userTeamListingOptions'>
- <div>
- <br/>
- <FormattedMessage
- id='general_tab.dirContact'
- defaultMessage='Contact your system administrator to turn on the team directory on the system home page.'
- />
- </div>
- </div>
- );
- }
-
- teamListingSection = (
- <SettingItemMax
- title={formatMessage(holders.includeDirTitle)}
- inputs={inputs}
- submit={submitHandle}
- server_error={serverError}
- client_error={clientError}
- updateSection={this.onUpdateTeamListingSection}
- />
- );
- } else {
- let describe = '';
-
- if (enableTeamListing) {
- if (this.state.allow_team_listing === true) {
- describe = formatMessage(holders.yes);
- } else {
- describe = formatMessage(holders.no);
- }
- } else {
- describe = formatMessage(holders.dirOff);
- }
-
- teamListingSection = (
- <SettingItemMin
- title={formatMessage(holders.includeDirTitle)}
- describe={describe}
- updateSection={this.onUpdateTeamListingSection}
- />
- );
- }
-
let openInviteSection;
if (this.props.activeSection === 'open_invite') {
const inputs = [
@@ -450,7 +314,7 @@ class GeneralTab extends React.Component {
<br/>
<FormattedMessage
id='general_tab.openInviteDesc'
- defaultMessage='When allowed, a link to account creation will be included on the sign-in page of this team and allow any visitor to sign-up.'
+ defaultMessage='When allowed, a link to this team will be including on the landing page allowing anyone with an account to join this team.'
/>
</div>
</div>
@@ -639,8 +503,6 @@ class GeneralTab extends React.Component {
<div className='divider-light'/>
{openInviteSection}
<div className='divider-light'/>
- {teamListingSection}
- <div className='divider-light'/>
{inviteSection}
<div className='divider-dark'/>
</div>
diff --git a/webapp/components/team_members_dropdown.jsx b/webapp/components/team_members_dropdown.jsx
index ad82a2280..251c2ce3b 100644
--- a/webapp/components/team_members_dropdown.jsx
+++ b/webapp/components/team_members_dropdown.jsx
@@ -3,7 +3,7 @@
import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import ConfirmModal from './confirm_modal.jsx';
@@ -38,11 +38,9 @@ export default class TeamMembersDropdown extends React.Component {
if (this.props.user.id === me.id) {
this.handleDemote(this.props.user, '');
} else {
- const data = {
- user_id: this.props.user.id,
- new_roles: ''
- };
- Client.updateRoles(data,
+ Client.updateRoles(
+ this.props.user.id,
+ '',
() => {
AsyncClient.getProfiles();
},
@@ -79,12 +77,9 @@ export default class TeamMembersDropdown extends React.Component {
if (this.props.user.id === me.id) {
this.handleDemote(this.props.user, 'admin');
} else {
- const data = {
- user_id: this.props.user.id,
- new_roles: 'admin'
- };
-
- Client.updateRoles(data,
+ Client.updateRoles(
+ this.props.user.id,
+ 'admin',
() => {
AsyncClient.getProfiles();
},
@@ -94,12 +89,13 @@ export default class TeamMembersDropdown extends React.Component {
);
}
}
- handleDemote(user, role) {
+ handleDemote(user, role, newRole) {
this.setState({
serverError: this.state.serverError,
showDemoteModal: true,
user,
- role
+ role,
+ newRole
});
}
handleDemoteCancel() {
@@ -107,16 +103,14 @@ export default class TeamMembersDropdown extends React.Component {
serverError: null,
showDemoteModal: false,
user: null,
- role: null
+ role: null,
+ newRole: null
});
}
handleDemoteSubmit() {
- const data = {
- user_id: this.props.user.id,
- new_roles: this.state.role
- };
-
- Client.updateRoles(data,
+ Client.updateRoles(
+ this.props.user.id,
+ this.state.newRole,
() => {
const teamUrl = TeamStore.getCurrentTeamUrl();
if (teamUrl) {
@@ -140,6 +134,7 @@ export default class TeamMembersDropdown extends React.Component {
);
}
+ const teamMember = this.props.teamMember;
const user = this.props.user;
let currentRoles = (
<FormattedMessage
@@ -168,8 +163,10 @@ export default class TeamMembersDropdown extends React.Component {
}
}
- let showMakeMember = user.roles === 'admin' || user.roles === 'system_admin';
- let showMakeAdmin = user.roles === '' || user.roles === 'system_admin';
+ let showMakeMember = teamMember.roles === 'admin' || user.roles === 'system_admin';
+
+ //let showMakeAdmin = teamMember.roles === '' && user.roles !== 'system_admin';
+ let showMakeAdmin = false;
let showMakeActive = false;
let showMakeNotActive = user.roles !== 'system_admin';
@@ -331,5 +328,6 @@ export default class TeamMembersDropdown extends React.Component {
}
TeamMembersDropdown.propTypes = {
- user: React.PropTypes.object.isRequired
+ user: React.PropTypes.object.isRequired,
+ teamMember: React.PropTypes.object.isRequired
};
diff --git a/webapp/components/team_settings.jsx b/webapp/components/team_settings.jsx
index dc303059d..210d1f541 100644
--- a/webapp/components/team_settings.jsx
+++ b/webapp/components/team_settings.jsx
@@ -25,6 +25,7 @@ export default class TeamSettings extends React.Component {
}
onChange() {
var team = TeamStore.getCurrent();
+
if (!Utils.areObjectsEqual(this.state.team, team)) {
this.setState({team});
}
diff --git a/webapp/components/team_signup_choose_auth.jsx b/webapp/components/team_signup_choose_auth.jsx
deleted file mode 100644
index 7a4ce972a..000000000
--- a/webapp/components/team_signup_choose_auth.jsx
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {FormattedMessage} from 'react-intl';
-
-import React from 'react';
-
-export default class ChooseAuthPage extends React.Component {
- constructor(props) {
- super(props);
- this.state = {};
- }
- render() {
- var buttons = [];
- if (global.window.mm_config.EnableSignUpWithGitLab === 'true') {
- buttons.push(
- <a
- className='btn btn-custom-login gitlab btn-full'
- key='gitlab'
- href='#'
- onClick={
- function clickGit(e) {
- e.preventDefault();
- this.props.updatePage('gitlab');
- }.bind(this)
- }
- >
- <span className='icon'/>
- <span>
- <FormattedMessage
- id='choose_auth_page.gitlabCreate'
- defaultMessage='Create new team with GitLab Account'
- />
- </span>
- </a>
- );
- }
-
- if (global.window.mm_config.EnableSignUpWithGoogle === 'true') {
- buttons.push(
- <a
- className='btn btn-custom-login google btn-full'
- key='google'
- href='#'
- onClick={
- (e) => {
- e.preventDefault();
- this.props.updatePage('google');
- }
- }
- >
- <span className='icon'/>
- <span>
- <FormattedMessage
- id='choose_auth_page.googleCreate'
- defaultMessage='Create new team with Google Apps Account'
- />
- </span>
- </a>
- );
- }
-
- if (global.window.mm_config.EnableLdap === 'true') {
- buttons.push(
- <a
- className='btn btn-custom-login ldap btn-full'
- key='ldap'
- href='#'
- onClick={
- (e) => {
- e.preventDefault();
- this.props.updatePage('ldap');
- }
- }
- >
- <span className='icon'/>
- <span>
- <FormattedMessage
- id='choose_auth_page.ldapCreate'
- defaultMessage='Create new team with LDAP Account'
- />
- </span>
- </a>
- );
- }
-
- if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
- buttons.push(
- <a
- className='btn btn-custom-login email btn-full'
- key='email'
- href='#'
- onClick={
- function clickEmail(e) {
- e.preventDefault();
- this.props.updatePage('email');
- }.bind(this)
- }
- >
- <span className='fa fa-envelope'/>
- <span>
- <FormattedMessage
- id='choose_auth_page.emailCreate'
- defaultMessage='Create new team with email address'
- />
- </span>
- </a>
- );
- }
-
- if (buttons.length === 0) {
- buttons = (
- <span>
- <FormattedMessage
- id='choose_auth_page.noSignup'
- defaultMessage='No sign-up methods configured, please contact your system administrator.'
- />
- </span>
- );
- }
-
- return (
- <div>
- {buttons}
- </div>
- );
- }
-}
-
-ChooseAuthPage.propTypes = {
- updatePage: React.PropTypes.func
-};
diff --git a/webapp/components/team_signup_with_email.jsx b/webapp/components/team_signup_with_email.jsx
deleted file mode 100644
index 90a6e9773..000000000
--- a/webapp/components/team_signup_with_email.jsx
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ReactDOM from 'react-dom';
-import * as Utils from 'utils/utils.jsx';
-import * as Client from 'utils/client.jsx';
-
-import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
-import {browserHistory} from 'react-router';
-
-const holders = defineMessages({
- emailError: {
- id: 'email_signup.emailError',
- defaultMessage: 'Please enter a valid email address'
- },
- address: {
- id: 'email_signup.address',
- defaultMessage: 'Email Address'
- }
-});
-
-import React from 'react';
-
-class EmailSignUpPage extends React.Component {
- constructor() {
- super();
-
- this.handleSubmit = this.handleSubmit.bind(this);
-
- this.state = {};
- }
- handleSubmit(e) {
- e.preventDefault();
- const team = {};
- const state = {serverError: null};
- let isValid = true;
-
- team.email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
- if (!team.email || !Utils.isEmail(team.email)) {
- state.emailError = this.props.intl.formatMessage(holders.emailError);
- isValid = false;
- } else {
- state.emailError = null;
- }
-
- if (!isValid) {
- this.setState(state);
- return;
- }
-
- Client.signupTeam(team.email,
- (data) => {
- if (data.follow_link) {
- browserHistory.push(data.follow_link);
- } else {
- browserHistory.push(`/signup_team_confirm/?email=${encodeURIComponent(team.email)}`);
- }
- },
- (err) => {
- state.serverError = err.message;
- this.setState(state);
- }
- );
- }
- render() {
- let serverError = null;
- if (this.state.serverError) {
- serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
- }
-
- let emailError = null;
- if (this.state.emailError) {
- emailError = <div className='form-group has-error'><label className='control-label'>{this.state.emailError}</label></div>;
- }
-
- return (
- <form
- role='form'
- onSubmit={this.handleSubmit}
- >
- <div className='form-group'>
- <input
- autoFocus={true}
- type='email'
- ref='email'
- className='form-control'
- placeholder={this.props.intl.formatMessage(holders.address)}
- maxLength='128'
- spellCheck='false'
- />
- {emailError}
- </div>
- <div className='form-group'>
- <button
- className='btn btn-md btn-primary'
- type='submit'
- >
- <FormattedMessage
- id='email_signup.createTeam'
- defaultMessage='Create Team'
- />
- </button>
- {serverError}
- </div>
- </form>
- );
- }
-}
-
-EmailSignUpPage.defaultProps = {
-};
-EmailSignUpPage.propTypes = {
- intl: intlShape.isRequired
-};
-
-export default injectIntl(EmailSignUpPage);
diff --git a/webapp/components/team_signup_with_ldap.jsx b/webapp/components/team_signup_with_ldap.jsx
deleted file mode 100644
index 9d812e8ee..000000000
--- a/webapp/components/team_signup_with_ldap.jsx
+++ /dev/null
@@ -1,231 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import * as utils from 'utils/utils.jsx';
-import * as Client from 'utils/client.jsx';
-
-import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
-import {browserHistory} from 'react-router';
-
-const holders = defineMessages({
- team_error: {
- id: 'ldap_signup.team_error',
- defaultMessage: 'Please enter a team name'
- },
- length_error: {
- id: 'ldap_signup.length_error',
- defaultMessage: 'Name must be 3 or more characters up to a maximum of 15'
- },
- teamName: {
- id: 'ldap_signup.teamName',
- defaultMessage: 'Enter name of new team'
- },
- badTeam: {
- id: 'login_ldap.badTeam',
- defaultMessage: 'Bad team name'
- },
- idReq: {
- id: 'login_ldap.idlReq',
- defaultMessage: 'An LDAP ID is required'
- },
- pwdReq: {
- id: 'login_ldap.pwdReq',
- defaultMessage: 'An LDAP password is required'
- },
- username: {
- id: 'login_ldap.username',
- defaultMessage: 'LDAP Username'
- },
- pwd: {
- id: 'login_ldap.pwd',
- defaultMessage: 'LDAP Password'
- }
-});
-
-import React from 'react';
-
-class LdapSignUpPage extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleSubmit = this.handleSubmit.bind(this);
- this.valueChange = this.valueChange.bind(this);
-
- this.state = {name: '', id: '', password: ''};
- }
-
- handleSubmit(e) {
- e.preventDefault();
- const {formatMessage} = this.props.intl;
- var teamSignup = {};
- teamSignup.user = {};
- teamSignup.team = {};
- var state = this.state;
- state.serverError = null;
-
- teamSignup.team.display_name = this.state.name;
-
- if (!teamSignup.team.display_name) {
- state.serverError = formatMessage(holders.team_error);
- this.setState(state);
- return;
- }
-
- if (teamSignup.team.display_name.length <= 2) {
- state.serverError = formatMessage(holders.length_error);
- this.setState(state);
- return;
- }
-
- const id = this.refs.id.value.trim();
- if (!id) {
- state.serverError = formatMessage(holders.idReq);
- this.setState(state);
- return;
- }
-
- const password = this.refs.password.value.trim();
- if (!password) {
- state.serverError = formatMessage(holders.pwdReq);
- this.setState(state);
- return;
- }
-
- state.serverError = '';
- this.setState(state);
-
- teamSignup.team.name = utils.cleanUpUrlable(teamSignup.team.display_name);
- teamSignup.team.type = 'O';
-
- teamSignup.user.username = ReactDOM.findDOMNode(this.refs.id).value.trim();
- teamSignup.user.password = ReactDOM.findDOMNode(this.refs.password).value.trim();
- teamSignup.user.allow_marketing = true;
- teamSignup.user.ldap = true;
- teamSignup.user.auth_service = 'ldap';
-
- $('#ldap-button').button('loading');
-
- Client.createTeamWithLdap(teamSignup,
- () => {
- Client.track('signup', 'signup_team_ldap_complete');
-
- Client.loginByLdap(teamSignup.team.name, id, password,
- () => {
- browserHistory.push('/' + teamSignup.team.name + '/channels/town-square');
- },
- (err) => {
- $('#ldap-button').button('reset');
- this.setState({serverError: err.message});
- }
- );
- },
- (err) => {
- $('#ldap-button').button('reset');
- this.setState({serverError: err.message});
- }
- );
- }
-
- valueChange() {
- this.setState({
- name: ReactDOM.findDOMNode(this.refs.teamname).value.trim(),
- id: ReactDOM.findDOMNode(this.refs.id).value.trim(),
- password: ReactDOM.findDOMNode(this.refs.password).value.trim()
- });
- }
-
- render() {
- const {formatMessage} = this.props.intl;
- var nameError = null;
- var nameDivClass = 'form-group';
- if (this.state.nameError) {
- nameError = <label className='control-label'>{this.state.nameError}</label>;
- nameDivClass += ' has-error';
- }
-
- var serverError = null;
- if (this.state.serverError) {
- serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
- }
-
- var disabled = false;
- if (this.state.name.length <= 2) {
- disabled = true;
- }
-
- if (this.state.id.length <= 1) {
- disabled = true;
- }
-
- if (this.state.password.length <= 1) {
- disabled = true;
- }
-
- return (
- <form
- role='form'
- onSubmit={this.handleSubmit}
- >
- <div className={nameDivClass}>
- <input
- autoFocus={true}
- type='text'
- ref='teamname'
- className='form-control'
- placeholder={this.props.intl.formatMessage(holders.teamName)}
- maxLength='128'
- onChange={this.valueChange}
- spellCheck='false'
- />
- {nameError}
- </div>
- <div className={nameDivClass}>
- <input
- className='form-control'
- ref='id'
- placeholder={formatMessage(holders.username)}
- spellCheck='false'
- onChange={this.valueChange}
- />
- </div>
- <div className={nameDivClass}>
- <input
- type='password'
- className='form-control'
- ref='password'
- placeholder={formatMessage(holders.pwd)}
- spellCheck='false'
- onChange={this.valueChange}
- />
- </div>
- <div className='form-group'>
- <a
- className='btn btn-custom-login ldap btn-full'
- key='ldap'
- id='ldap-button'
- href='#'
- onClick={this.handleSubmit}
- disabled={disabled}
- >
- <span className='icon'/>
- <span>
- <FormattedMessage
- id='ldap_signup.ldap'
- defaultMessage='Create team with LDAP Account'
- />
- </span>
- </a>
- {serverError}
- </div>
- </form>
- );
- }
-}
-
-LdapSignUpPage.propTypes = {
- intl: intlShape.isRequired
-};
-
-export default injectIntl(LdapSignUpPage);
diff --git a/webapp/components/team_signup_with_sso.jsx b/webapp/components/team_signup_with_sso.jsx
deleted file mode 100644
index 78396eea8..000000000
--- a/webapp/components/team_signup_with_sso.jsx
+++ /dev/null
@@ -1,174 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ReactDOM from 'react-dom';
-import * as utils from 'utils/utils.jsx';
-import * as client from 'utils/client.jsx';
-import Constants from 'utils/constants.jsx';
-
-import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
-
-const holders = defineMessages({
- team_error: {
- id: 'sso_signup.team_error',
- defaultMessage: 'Please enter a team name'
- },
- length_error: {
- id: 'sso_signup.length_error',
- defaultMessage: 'Name must be 3 or more characters up to a maximum of 15'
- },
- teamName: {
- id: 'sso_signup.teamName',
- defaultMessage: 'Enter name of new team'
- }
-});
-
-import React from 'react';
-import {browserHistory} from 'react-router';
-
-class SSOSignUpPage extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleSubmit = this.handleSubmit.bind(this);
- this.nameChange = this.nameChange.bind(this);
-
- this.state = {name: ''};
- }
- handleSubmit(e) {
- e.preventDefault();
- const {formatMessage} = this.props.intl;
- var team = {};
- var state = this.state;
- state.nameError = null;
- state.serverError = null;
-
- team.display_name = this.state.name;
-
- if (!team.display_name) {
- state.nameError = formatMessage(holders.team_error);
- this.setState(state);
- return;
- }
-
- if (team.display_name.length <= 2) {
- state.nameError = formatMessage(holders.length_error);
- this.setState(state);
- return;
- }
-
- team.name = utils.cleanUpUrlable(team.display_name);
- team.type = 'O';
-
- client.createTeamWithSSO(team,
- this.props.service,
- (data) => {
- if (data.follow_link) {
- window.location.href = data.follow_link;
- } else {
- browserHistory.push('/' + team.name + '/channels/town-square');
- }
- },
- (err) => {
- state.serverError = err.message;
- this.setState(state);
- }
- );
- }
- nameChange() {
- this.setState({name: ReactDOM.findDOMNode(this.refs.teamname).value.trim()});
- }
- render() {
- var nameError = null;
- var nameDivClass = 'form-group';
- if (this.state.nameError) {
- nameError = <label className='control-label'>{this.state.nameError}</label>;
- nameDivClass += ' has-error';
- }
-
- var serverError = null;
- if (this.state.serverError) {
- serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
- }
-
- var disabled = false;
- if (this.state.name.length <= 2) {
- disabled = true;
- }
-
- var button = null;
-
- if (this.props.service === Constants.GITLAB_SERVICE) {
- button = (
- <a
- className='btn btn-custom-login gitlab btn-full'
- key='gitlab'
- href='#'
- onClick={this.handleSubmit}
- disabled={disabled}
- >
- <span className='icon'/>
- <span>
- <FormattedMessage
- id='sso_signup.gitlab'
- defaultMessage='Create team with GitLab Account'
- />
- </span>
- </a>
- );
- } else if (this.props.service === Constants.GOOGLE_SERVICE) {
- button = (
- <a
- className='btn btn-custom-login google btn-full'
- key='google'
- href='#'
- onClick={this.handleSubmit}
- disabled={disabled}
- >
- <span className='icon'/>
- <span>
- <FormattedMessage
- id='sso_signup.google'
- defaultMessage='Create team with Google Apps Account'
- />
- </span>
- </a>
- );
- }
-
- return (
- <form
- role='form'
- onSubmit={this.handleSubmit}
- >
- <div className={nameDivClass}>
- <input
- autoFocus={true}
- type='text'
- ref='teamname'
- className='form-control'
- placeholder={this.props.intl.formatMessage(holders.teamName)}
- maxLength='128'
- onChange={this.nameChange}
- spellCheck='false'
- />
- {nameError}
- </div>
- <div className='form-group'>
- {button}
- {serverError}
- </div>
- </form>
- );
- }
-}
-
-SSOSignUpPage.defaultProps = {
- service: ''
-};
-SSOSignUpPage.propTypes = {
- intl: intlShape.isRequired,
- service: React.PropTypes.string
-};
-
-export default injectIntl(SSOSignUpPage);
diff --git a/webapp/components/toggle_modal_button.jsx b/webapp/components/toggle_modal_button.jsx
index de225493c..69bdbbda0 100644
--- a/webapp/components/toggle_modal_button.jsx
+++ b/webapp/components/toggle_modal_button.jsx
@@ -15,7 +15,8 @@ export default class ModalToggleButton extends React.Component {
};
}
- show() {
+ show(e) {
+ e.preventDefault();
this.setState({show: true});
}
@@ -29,10 +30,10 @@ export default class ModalToggleButton extends React.Component {
// allow callers to provide an onClick which will be called before the modal is shown
let clickHandler = this.show;
if (onClick) {
- clickHandler = () => {
+ clickHandler = (e) => {
onClick();
- this.show();
+ this.show(e);
};
}
diff --git a/webapp/components/user_list.jsx b/webapp/components/user_list.jsx
index 3652723be..626cb3cf5 100644
--- a/webapp/components/user_list.jsx
+++ b/webapp/components/user_list.jsx
@@ -13,10 +13,18 @@ export default class UserList extends React.Component {
let content;
if (users.length > 0) {
content = users.map((user) => {
+ var teamMember;
+ for (var index in this.props.teamMembers) {
+ if (this.props.teamMembers[index].user_id === user.id) {
+ teamMember = this.props.teamMembers[index];
+ }
+ }
+
return (
<UserListRow
key={user.id}
user={user}
+ teamMember={teamMember}
actions={this.props.actions}
actionProps={this.props.actionProps}
/>
@@ -48,12 +56,14 @@ export default class UserList extends React.Component {
UserList.defaultProps = {
users: [],
+ teamMembers: [],
actions: [],
actionProps: {}
};
UserList.propTypes = {
users: React.PropTypes.arrayOf(React.PropTypes.object),
+ teamMembers: React.PropTypes.arrayOf(React.PropTypes.object),
actions: React.PropTypes.arrayOf(React.PropTypes.func),
actionProps: React.PropTypes.object
};
diff --git a/webapp/components/user_list_row.jsx b/webapp/components/user_list_row.jsx
index f6fd91688..a7838ec32 100644
--- a/webapp/components/user_list_row.jsx
+++ b/webapp/components/user_list_row.jsx
@@ -4,9 +4,10 @@
import Constants from 'utils/constants.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import * as Utils from 'utils/utils.jsx';
+import Client from 'utils/web_client.jsx';
import React from 'react';
-export default function UserListRow({user, actions, actionProps}) {
+export default function UserListRow({user, teamMember, actions, actionProps}) {
const nameFormat = PreferenceStore.get(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', '');
let name = user.username;
@@ -21,6 +22,7 @@ export default function UserListRow({user, actions, actionProps}) {
<Action
key={index.toString()}
user={user}
+ teamMember={teamMember}
{...actionProps}
/>
);
@@ -35,7 +37,7 @@ export default function UserListRow({user, actions, actionProps}) {
className='more-modal__image'
width='38'
height='38'
- src={`/api/v1/users/${user.id}/image?time=${user.update_at}`}
+ src={`${Client.getUsersRoute()}/${user.id}/image?time=${user.update_at}`}
/>
<div
className='more-modal__details'
@@ -57,12 +59,17 @@ export default function UserListRow({user, actions, actionProps}) {
}
UserListRow.defaultProps = {
+ teamMember: {
+ team_id: '',
+ roles: ''
+ },
actions: [],
actionProps: {}
};
UserListRow.propTypes = {
user: React.PropTypes.object.isRequired,
+ teamMember: React.PropTypes.object.isRequired,
actions: React.PropTypes.arrayOf(React.PropTypes.func),
actionProps: React.PropTypes.object
};
diff --git a/webapp/components/user_profile.jsx b/webapp/components/user_profile.jsx
index d83ab7454..04b4f99a4 100644
--- a/webapp/components/user_profile.jsx
+++ b/webapp/components/user_profile.jsx
@@ -2,6 +2,7 @@
// See License.txt for license information.
import * as Utils from 'utils/utils.jsx';
+import Client from 'utils/web_client.jsx';
import {FormattedMessage} from 'react-intl';
@@ -28,7 +29,7 @@ export default class UserProfile extends React.Component {
if (this.props.user) {
name = Utils.displayUsername(this.props.user.id);
email = this.props.user.email;
- profileImg = '/api/v1/users/' + this.props.user.id + '/image?time=' + this.props.user.update_at;
+ profileImg = Client.getUsersRoute() + '/' + this.props.user.id + '/image?time=' + this.props.user.update_at;
}
if (this.props.overwriteName) {
diff --git a/webapp/components/user_settings/import_theme_modal.jsx b/webapp/components/user_settings/import_theme_modal.jsx
index 2fc75ca13..32da296bf 100644
--- a/webapp/components/user_settings/import_theme_modal.jsx
+++ b/webapp/components/user_settings/import_theme_modal.jsx
@@ -5,7 +5,7 @@ import ReactDOM from 'react-dom';
import ModalStore from 'stores/modal_store.jsx';
import UserStore from 'stores/user_store.jsx';
import * as Utils from 'utils/utils.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import {Modal} from 'react-bootstrap';
import AppDispatcher from '../../dispatcher/app_dispatcher.jsx';
diff --git a/webapp/components/user_settings/manage_languages.jsx b/webapp/components/user_settings/manage_languages.jsx
index 094eaa127..bbf3a2e40 100644
--- a/webapp/components/user_settings/manage_languages.jsx
+++ b/webapp/components/user_settings/manage_languages.jsx
@@ -3,7 +3,7 @@
import SettingItemMax from '../setting_item_max.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as I18n from 'i18n/i18n.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
diff --git a/webapp/components/user_settings/user_settings_general.jsx b/webapp/components/user_settings/user_settings_general.jsx
index eddbc1efe..c6a05d1ee 100644
--- a/webapp/components/user_settings/user_settings_general.jsx
+++ b/webapp/components/user_settings/user_settings_general.jsx
@@ -9,7 +9,7 @@ import SettingPicture from '../setting_picture.jsx';
import UserStore from 'stores/user_store.jsx';
import ErrorStore from 'stores/error_store.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import Constants from 'utils/constants.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
@@ -225,11 +225,9 @@ class UserSettingsGeneralTab extends React.Component {
return;
}
- var formData = new FormData();
- formData.append('image', picture, picture.name);
this.setState({loadingPicture: true});
- Client.uploadProfileImage(formData,
+ Client.uploadProfileImage(picture,
() => {
this.submitActive = false;
AsyncClient.getMe();
@@ -781,7 +779,7 @@ class UserSettingsGeneralTab extends React.Component {
<SettingPicture
title={formatMessage(holders.profilePicture)}
submit={this.submitPicture}
- src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update}
+ src={Client.getUsersRoute() + '/' + user.id + '/image?time=' + user.last_picture_update}
server_error={serverError}
client_error={clientError}
updateSection={(e) => {
diff --git a/webapp/components/user_settings/user_settings_notifications.jsx b/webapp/components/user_settings/user_settings_notifications.jsx
index b119c42f9..fa84ce2d6 100644
--- a/webapp/components/user_settings/user_settings_notifications.jsx
+++ b/webapp/components/user_settings/user_settings_notifications.jsx
@@ -8,7 +8,7 @@ import SettingItemMax from '../setting_item_max.jsx';
import UserStore from 'stores/user_store.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
diff --git a/webapp/components/user_settings/user_settings_security.jsx b/webapp/components/user_settings/user_settings_security.jsx
index ff5a898a9..f28e34197 100644
--- a/webapp/components/user_settings/user_settings_security.jsx
+++ b/webapp/components/user_settings/user_settings_security.jsx
@@ -10,7 +10,7 @@ import ToggleModalButton from '../toggle_modal_button.jsx';
import TeamStore from 'stores/team_store.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
@@ -96,12 +96,10 @@ class SecurityTab extends React.Component {
return;
}
- var data = {};
- data.user_id = user.id;
- data.current_password = currentPassword;
- data.new_password = newPassword;
-
- Client.updatePassword(data,
+ Client.updatePassword(
+ user.id,
+ currentPassword,
+ newPassword,
() => {
this.props.updateSection('');
AsyncClient.getMe();
@@ -120,11 +118,9 @@ class SecurityTab extends React.Component {
);
}
activateMfa() {
- const data = {};
- data.activate = true;
- data.token = this.state.mfaToken;
-
- Client.updateMfa(data,
+ Client.updateMfa(
+ this.state.mfaToken,
+ true,
() => {
this.props.updateSection('');
AsyncClient.getMe();
@@ -224,7 +220,7 @@ class SecurityTab extends React.Component {
<div className='col-sm-7'>
<img
className='qr-code-img'
- src={'/api/v1/users/generate_mfa_qr?time=' + this.props.user.update_at}
+ src={Client.getUsersRoute() + '/generate_mfa_qr?time=' + this.props.user.update_at}
/>
</div>
<br/>
@@ -531,9 +527,9 @@ class SecurityTab extends React.Component {
if (global.window.mm_config.EnableSignUpWithEmail === 'true' && user.auth_service !== '') {
let link;
if (user.auth_service === Constants.LDAP_SERVICE) {
- link = '/' + teamName + '/claim/ldap_to_email?email=' + encodeURIComponent(user.email);
+ link = '/claim/ldap_to_email?email=' + encodeURIComponent(user.email);
} else {
- link = '/' + teamName + '/claim/oauth_to_email?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service;
+ link = '/claim/oauth_to_email?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service;
}
emailOption = (
@@ -558,7 +554,7 @@ class SecurityTab extends React.Component {
<div>
<Link
className='btn btn-primary'
- to={'/' + teamName + '/claim/email_to_oauth?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.GITLAB_SERVICE}
+ to={'/claim/email_to_oauth?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.GITLAB_SERVICE}
>
<FormattedMessage
id='user.settings.security.switchGitlab'
@@ -594,7 +590,7 @@ class SecurityTab extends React.Component {
<div>
<Link
className='btn btn-primary'
- to={'/' + teamName + '/claim/email_to_ldap?email=' + encodeURIComponent(user.email)}
+ to={'/claim/email_to_ldap?email=' + encodeURIComponent(user.email)}
>
<FormattedMessage
id='user.settings.security.switchLdap'
@@ -660,6 +656,13 @@ class SecurityTab extends React.Component {
defaultMessage='GitLab SSO'
/>
);
+ } else if (this.props.user.auth_service === Constants.LDAP_SERVICE) {
+ describe = (
+ <FormattedMessage
+ id='user.settings.security.ldap'
+ defaultMessage='LDAP'
+ />
+ );
}
return (
diff --git a/webapp/components/user_settings/user_settings_theme.jsx b/webapp/components/user_settings/user_settings_theme.jsx
index 14991037d..f19538f71 100644
--- a/webapp/components/user_settings/user_settings_theme.jsx
+++ b/webapp/components/user_settings/user_settings_theme.jsx
@@ -11,7 +11,7 @@ import SettingItemMax from '../setting_item_max.jsx';
import UserStore from 'stores/user_store.jsx';
import AppDispatcher from '../../dispatcher/app_dispatcher.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
diff --git a/webapp/components/view_image.jsx b/webapp/components/view_image.jsx
index 3d3107d92..bd4aeaa41 100644
--- a/webapp/components/view_image.jsx
+++ b/webapp/components/view_image.jsx
@@ -3,7 +3,7 @@
import $ from 'jquery';
import * as AsyncClient from 'utils/async_client.jsx';
-import * as Client from 'utils/client.jsx';
+import Client from 'utils/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import AudioVideoPreview from './audio_video_preview.jsx';
import Constants from 'utils/constants.jsx';
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index da69aba74..6d4f4c287 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -271,6 +271,9 @@
"admin.ldap.lastnameAttrDesc": "The attribute in the LDAP server that will be used to populate the last name of users in Mattermost.",
"admin.ldap.lastnameAttrEx": "Ex \"sn\"",
"admin.ldap.lastnameAttrTitle": "Last Name Attribute:",
+ "admin.ldap.nicknameAttrDesc": "(Optional) The attribute in the LDAP server that will be used to populate the nickname of users in Mattermost.",
+ "admin.ldap.nicknameAttrEx": "Ex \"nickname\"",
+ "admin.ldap.nicknameAttrTitle": "Nickname Attribute:",
"admin.ldap.noLicense": "<h4 class=\"banner__heading\">Note:</h4><p>LDAP is an enterprise feature. Your current license does not support LDAP. Click <a href=\"http://mattermost.com\"target=\"_blank\">here</a> for information and pricing on enterprise licenses.</p>",
"admin.ldap.portDesc": "The port Mattermost will use to connect to the LDAP server. Default is 389.",
"admin.ldap.portEx": "Ex \"389\"",
@@ -526,10 +529,18 @@
"admin.team.uploading": "Uploading..",
"admin.team.userCreationDescription": "When false, the ability to create accounts is disabled. The create account button displays error when pressed.",
"admin.team.userCreationTitle": "Enable User Creation: ",
+ "admin.team.openServerDescription": "When true, anyone can signup for a user account on this server without the need to be invited.",
+ "admin.team.openServerTitle": "Enable Open Server: ",
"admin.team_analytics.activeUsers": "Active Users With Posts",
"admin.team_analytics.totalPosts": "Total Posts",
"admin.userList.title": "Users for {team}",
"admin.userList.title2": "Users for {team} ({count})",
+ "admin.user_item.resetMfa": "Remove MFA",
+ "admin.user_item.mfaYes": ", <strong>MFA</strong>: Yes",
+ "admin.user_item.mfaNo": ", <strong>MFA</strong>: No",
+ "admin.user_item.authServiceNotEmail": ", <strong>Sign-in Method:</strong> {service}",
+ "admin.user_item.authServiceEmail": ", <strong>Sign-in Method:</strong> Email",
+ "admin.user_item.emailTitle": "<strong>Email:</strong> {email}",
"admin.user_item.confirmDemoteDescription": "If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you'll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command.",
"admin.user_item.confirmDemoteRoleTitle": "Confirm demotion from System Admin role",
"admin.user_item.confirmDemotion": "Confirm Demotion",
@@ -721,7 +732,7 @@
"choose_auth_page.noSignup": "No sign-up methods configured, please contact your system administrator.",
"claim.account.noEmail": "No email specified",
"claim.email_to_ldap.enterLdapPwd": "Enter the ID and password for your LDAP account",
- "claim.email_to_ldap.enterPwd": "Enter the password for your {team} {site} email account",
+ "claim.email_to_ldap.enterPwd": "Enter the password for your {site} email account",
"claim.email_to_ldap.ldapId": "LDAP ID",
"claim.email_to_ldap.ldapIdError": "Please enter your LDAP ID.",
"claim.email_to_ldap.ldapPasswordError": "Please enter your LDAP password.",
@@ -732,7 +743,7 @@
"claim.email_to_ldap.ssoType": "Upon claiming your account, you will only be able to login with LDAP",
"claim.email_to_ldap.switchTo": "Switch account to LDAP",
"claim.email_to_ldap.title": "Switch Email/Password Account to LDAP",
- "claim.email_to_oauth.enterPwd": "Enter the password for your {team} {site} account",
+ "claim.email_to_oauth.enterPwd": "Enter the password for your {site} account",
"claim.email_to_oauth.pwd": "Password",
"claim.email_to_oauth.pwdError": "Please enter your password.",
"claim.email_to_oauth.ssoNote": "You must already have a valid {type} account",
@@ -741,7 +752,7 @@
"claim.email_to_oauth.title": "Switch Email/Password Account to {uiType}",
"claim.ldap_to_email.confirm": "Confirm Password",
"claim.ldap_to_email.email": "You will use the email {email} to login",
- "claim.ldap_to_email.enterLdapPwd": "Enter your LDAP password for your {team} {site} email account",
+ "claim.ldap_to_email.enterLdapPwd": "Enter your {ldapPassword} for your {site} email account",
"claim.ldap_to_email.enterPwd": "Enter a new password for your email account",
"claim.ldap_to_email.ldapPasswordError": "Please enter your LDAP password.",
"claim.ldap_to_email.ldapPwd": "LDAP Password",
@@ -758,7 +769,7 @@
"claim.oauth_to_email.pwdNotMatch": "Password do not match.",
"claim.oauth_to_email.switchTo": "Switch {type} to email and password",
"claim.oauth_to_email.title": "Switch {type} Account to Email",
- "claim.oauth_to_email_newPwd": "Enter a new password for your {team} {site} account",
+ "claim.oauth_to_email.enterNewPwd": "Enter a new password for your {site} account",
"confirm_modal.cancel": "Cancel",
"create_comment.addComment": "Add a comment...",
"create_comment.comment": "Add Comment",
@@ -841,8 +852,8 @@
"general_tab.includeDirDesc": "Including this team will display the team name from the Team Directory section of the Home Page, and provide a link to the sign-in page.",
"general_tab.includeDirTitle": "Include this team in the Team Directory",
"general_tab.no": "No",
- "general_tab.openInviteDesc": "When allowed, a link to account creation will be included on the sign-in page of this team and allow any visitor to sign-up.",
- "general_tab.openInviteTitle": "Allow anyone to sign-up from login page",
+ "general_tab.openInviteDesc": "When allowed, a link to this team will be including on the landing page allowing anyone with an account to join this team.",
+ "general_tab.openInviteTitle": "Allow anyone to join this team",
"general_tab.regenerate": "Re-Generate",
"general_tab.required": "This field is required",
"general_tab.teamName": "Team Name",
@@ -866,6 +877,7 @@
"installed_integrations.regenToken": "Regen Token",
"installed_integrations.search": "Search Integrations",
"installed_integrations.token": "Token: {token}",
+ "installed_integrations.url": "URL: {url}",
"installed_outgoing_webhooks.add": "Add Outgoing Webhook",
"installed_outgoing_webhooks.header": "Outgoing Webhooks",
"integrations.command.description": "Slash commands send events to external integrations",
@@ -913,6 +925,7 @@
"ldap_signup.team_error": "Please enter a team name",
"loading_screen.loading": "Loading",
"login.changed": " Sign-in method changed successfully",
+ "login.passwordChanged": " Password updated successfully",
"login.create": "Create one now",
"login.createTeam": "Create a new team",
"login.find": "Find your other teams",
@@ -1111,7 +1124,9 @@
"sidebar_right_menu.report": "Report a Problem",
"sidebar_right_menu.teamLink": "Get Team Invite Link",
"sidebar_right_menu.teamSettings": "Team Settings",
- "signup_team.choose": "Choose a Team",
+ "signup_team.no_teams": "You do not appear to be a member of any team. Please ask your administrator for an invite, join an open team if one exists or possibly create a new team.",
+ "signup_team.choose": "Teams you are a member of: ",
+ "signup_team.join_open": "Open teams you can join: ",
"signup_team.createTeam": "Or Create a Team",
"signup_team.disabled": "Team creation has been disabled. Please contact an administrator for access.",
"signup_team.noTeams": "There are no teams included in the Team Directory and team creation has been disabled.",
@@ -1119,6 +1134,8 @@
"signup_team_complete.completed": "You've already completed the signup process for this invitation or this invitation has expired.",
"signup_team_confirm.checkEmail": "Please check your email: <strong>{email}</strong><br />Your email contains a link to set up your team",
"signup_team_confirm.title": "Sign up Complete",
+ "signup_user_completed.no_open_server": "This server does not allow open signups. Please speak with your Administrator to receive an invitation.",
+ "signup_user_completed.invalid_invite": "The invite link was invalid. Please speak with your Administrator to receive an invitation.",
"signup_user_completed.choosePwd": "Choose your password",
"signup_user_completed.chooseUser": "Choose your username",
"signup_user_completed.create": "Create Account",
@@ -1181,61 +1198,23 @@
"team_settings_modal.generalTab": "General",
"team_settings_modal.importTab": "Import",
"team_settings_modal.title": "Team Settings",
- "team_signup_display_name.back": "Back to previous step",
- "team_signup_display_name.charLength": "Name must be 4 or more characters up to a maximum of 15",
- "team_signup_display_name.nameHelp": "Name your team in any language. Your team name shows in menus and headings.",
- "team_signup_display_name.next": "Next",
- "team_signup_display_name.required": "This field is required",
- "team_signup_display_name.teamName": "Team Name",
- "team_signup_email.address": "Email Address",
- "team_signup_email.different": "Please use a different email than the one used at signup",
- "team_signup_email.validEmail": "Please enter a valid email address",
- "team_signup_password.agreement": "By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}.",
- "team_signup_password.back": "Back to previous step",
- "team_signup_password.choosePwd": "Choose your password",
- "team_signup_password.creating": "Creating team...",
- "team_signup_password.email": "Email",
- "team_signup_password.finish": "Finish",
- "team_signup_password.hint": "Passwords must contain {min} to {max} characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.",
- "team_signup_password.passwordError": "Please enter at least {chars} characters",
- "team_signup_password.selectPassword": "Select a password that you'll use to login with your email address:",
- "team_signup_password.yourPassword": "Your password",
- "team_signup_send_invites.addInvitation": "Add Invitation",
- "team_signup_send_invites.back": "Back to previous step",
- "team_signup_send_invites.disabled": "Email is currently disabled for your team, and emails cannot be sent. Contact your system administrator to enable email and email invitations.",
- "team_signup_send_invites.forNow": "for now.",
- "team_signup_send_invites.next": "Next",
- "team_signup_send_invites.prefer": "if you prefer, you can invite team members later<br /> and ",
- "team_signup_send_invites.skip": "skip this step ",
- "team_signup_send_invites.title": "Invite Team Members",
- "team_signup_url.back": "Back to previous step",
- "team_signup_url.charLength": "Name must be 4 or more characters up to a maximum of 15",
- "team_signup_url.hint": "<li>Short and memorable is best</li><li>Use lowercase letters, numbers and dashes</li><li>Must start with a letter and can't end in a dash</li>",
- "team_signup_url.next": "Next",
- "team_signup_url.regex": "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash.",
- "team_signup_url.required": "This field is required",
- "team_signup_url.taken": "URL is taken or contains a reserved word",
- "team_signup_url.teamUrl": "Team URL",
- "team_signup_url.unavailable": "This URL is unavailable. Please try another.",
- "team_signup_url.webAddress": "Choose the web address of your new team:",
- "team_signup_username.back": "Back to previous step",
- "team_signup_username.chooseUsername": "Choose your username",
- "team_signup_username.hint": "Usernames must begin with a letter and contain between {min} to {max} characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'",
- "team_signup_username.invalid": "Username must begin with a letter, and contain between {min} to {max} characters in total, which may be numbers, lowercase letters, or any of the symbols '.', '-', or '_'",
- "team_signup_username.memorable": "Select a memorable username that makes it easy for teammates to identify you:",
- "team_signup_username.next": "Next",
- "team_signup_username.reserved": "This username is reserved, please choose a new one.",
- "team_signup_username.username": "Your username",
- "team_signup_welcome.address": "Email Address",
- "team_signup_welcome.admin": "Your account will administer the new team site. <br />You can add other administrators later.",
- "team_signup_welcome.confirm": "Please confirm your email address:",
- "team_signup_welcome.different": "Use a different email",
- "team_signup_welcome.instead": "Use this instead",
- "team_signup_welcome.lets": "Let's set up your new team",
- "team_signup_welcome.storageError": "This service requires local storage to be enabled. Please enable it or exit private browsing.",
- "team_signup_welcome.validEmailError": "Please enter a valid email address",
- "team_signup_welcome.welcome": "Welcome to:",
- "team_signup_welcome.yes": "Yes, this address is correct",
+ "create_team.agreement": "By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}.",
+ "create_team.display_name.teamName": "Team Name",
+ "create_team.display_name.nameHelp": "Name your team in any language. Your team name shows in menus and headings.",
+ "create_team.display_name.next": "Next",
+ "create_team.display_name.back": "Back to previous step",
+ "create_team.display_name.required": "This field is required",
+ "create_team.display_name.charLength": "Name must be 4 or more characters up to a maximum of 15",
+ "create_team.team_url.required": "This field is required",
+ "create_team.team_url.regex": "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash.",
+ "create_team.team_url.charLength": "Name must be 4 or more characters up to a maximum of 15",
+ "create_team.team_url.taken": "URL is taken or contains a reserved word",
+ "create_team.team_url.unavailable": "This URL is unavailable. Please try another.",
+ "create_team.team_url.teamUrl": "Team URL",
+ "create_team.team_url.webAddress": "Choose the web address of your new team:",
+ "create_team.team_url.hint": "<li>Short and memorable is best</li><li>Use lowercase letters, numbers and dashes</li><li>Must start with a letter and can't end in a dash</li>",
+ "create_team.team_url.finish": "Finish",
+ "create_team.team_url.back": "Back to previous step",
"textbox.bold": "**bold**",
"textbox.edit": "Edit message",
"textbox.help": "Help",
@@ -1406,6 +1385,7 @@
"user.settings.security.currentPasswordError": "Please enter your current password",
"user.settings.security.emailPwd": "Email and Password",
"user.settings.security.gitlab": "GitLab SSO",
+ "user.settings.security.ldap": "LDAP",
"user.settings.security.lastUpdated": "Last updated {date} at {time}",
"user.settings.security.loginGitlab": "Login done through Gitlab",
"user.settings.security.loginLdap": "Login done through LDAP",
diff --git a/webapp/i18n/es.json b/webapp/i18n/es.json
index 23f1d5a45..7d429b629 100644
--- a/webapp/i18n/es.json
+++ b/webapp/i18n/es.json
@@ -721,7 +721,7 @@
"choose_auth_page.noSignup": "No hay métodos de inicio de sesión configurad, por favor contacte al administrador de sistemasos",
"claim.account.noEmail": "No se especifico un correo electrónico.",
"claim.email_to_ldap.enterLdapPwd": "Ingresa el ID y la contraseña de tu cuenta LDAP",
- "claim.email_to_ldap.enterPwd": "Ingresa la contraseña para tu cuenta de correo en {team} {site}",
+ "claim.email_to_ldap.enterPwd": "Ingresa la contraseña para tu cuenta de correo en {site}",
"claim.email_to_ldap.ldapId": "LDAP ID",
"claim.email_to_ldap.ldapIdError": "Por favor ingresa tu ID de LDAP.",
"claim.email_to_ldap.ldapPasswordError": "Por favor ingresa tu contraseña de LDAP.",
@@ -732,7 +732,7 @@
"claim.email_to_ldap.ssoType": "Al reclamar tu cuenta, sólo podrás iniciar sesión con LDAP",
"claim.email_to_ldap.switchTo": "Cambiar cuenta a LDAP",
"claim.email_to_ldap.title": "Cambiar Cuenta de Correo/Contraseña a LDAP",
- "claim.email_to_oauth.enterPwd": "Ingresa la contraseña para tu cuenta para {team} {site}",
+ "claim.email_to_oauth.enterPwd": "Ingresa la contraseña para tu cuenta para {site}",
"claim.email_to_oauth.pwd": "Contraseña",
"claim.email_to_oauth.pwdError": "Por favor introduce tu contraseña.",
"claim.email_to_oauth.ssoNote": "Debes tener una cuenta válida con {type}",
@@ -741,7 +741,6 @@
"claim.email_to_oauth.title": "Cambiar Cuenta de Correo/Contraseña a {uiType}",
"claim.ldap_to_email.confirm": "Confirmar Contraseña",
"claim.ldap_to_email.email": "Para iniciar sesión debes utilizar el correo electrónico {email}",
- "claim.ldap_to_email.enterLdapPwd": "Ingresa tu contraseña de LDAP para tu cuenta de correo en {team} {site}",
"claim.ldap_to_email.enterPwd": "Ingresa una nueva contraseña para tu cuenta de correo",
"claim.ldap_to_email.ldapPasswordError": "Por favor ingresa tu contraseña LDAP.",
"claim.ldap_to_email.ldapPwd": "Contraseña LDAP",
@@ -758,7 +757,6 @@
"claim.oauth_to_email.pwdNotMatch": "Las contraseñas no coinciden.",
"claim.oauth_to_email.switchTo": "Cambiar {type} a correo electrónico y contraseña",
"claim.oauth_to_email.title": "Cambiar la cuenta de {type} a Correo Electrónico",
- "claim.oauth_to_email_newPwd": "Ingresa una nueva contraseña para tu cuenta de {team} en {site}",
"confirm_modal.cancel": "Cancelar",
"create_comment.addComment": "Agregar un comentario...",
"create_comment.comment": "Agregar Comentario",
@@ -841,8 +839,6 @@
"general_tab.includeDirDesc": "Incluir este equipo mostrará el nombre del equipo en la sección de Directorio de Equipos en la página de inicio, y proveerá un enlace para la página de inicio de sesión.",
"general_tab.includeDirTitle": "Incluir este Equipo en el Directorio de Equipos",
"general_tab.no": "No",
- "general_tab.openInviteDesc": "Cuando está permitido, un enlace para la creación de cuentas será incluido en la página de registro de este equipo y permitirá a cualquier visitante registrarse.",
- "general_tab.openInviteTitle": "Permitir a cualquiera a inscribirse desde la página de inicio de sesión",
"general_tab.regenerate": "Regenerar",
"general_tab.required": "Este campo es obligatorio",
"general_tab.teamName": "Nombre del Equipo",
@@ -1111,7 +1107,6 @@
"sidebar_right_menu.report": "Reporta un Problema",
"sidebar_right_menu.teamLink": "Enlace Invitación al Equipo",
"sidebar_right_menu.teamSettings": "Configurar Equipo",
- "signup_team.choose": "Selecciona un Equipo",
"signup_team.createTeam": "O Crea un Equipo",
"signup_team.disabled": "La creación de Equipos ha sido deshabilitada.",
"signup_team.noTeams": "No hay equipos en el Directorio de Equipos y la creación de equipos ha sido deshabilitada.",
@@ -1181,61 +1176,6 @@
"team_settings_modal.generalTab": "General",
"team_settings_modal.importTab": "Importar",
"team_settings_modal.title": "Configuración del Equipo",
- "team_signup_display_name.back": "Volver al paso previo",
- "team_signup_display_name.charLength": "El nombre debe tener 4 o más caracteres hasta un máximo de 15",
- "team_signup_display_name.nameHelp": "Nombre tu equipo en cualquier idioma. El nombre de tu equipo aparecerá en menús y en inicios",
- "team_signup_display_name.next": "Siguiente",
- "team_signup_display_name.required": "Este campo es obligatorio",
- "team_signup_display_name.teamName": "Nombre del Equipo",
- "team_signup_email.address": "Dirección de correo electrónico",
- "team_signup_email.different": "Please use a different email than the one used at signup",
- "team_signup_email.validEmail": "Por favor ingresa una dirección de correo electrónico válida",
- "team_signup_password.agreement": "Procediendo a crear tu cuenta y el uso de {siteName}, indicas que estás de acuerdo con nuestros <a href='/static/help/terms.html'>Términos de Servicio</a> y <a href='/static/help/privacy.html'>Políticas de Privacidad</a>. Si no estás de acuerdo, no debes utilizar {siteName}.",
- "team_signup_password.back": "Volver al paso previo",
- "team_signup_password.choosePwd": "Escoge tu contraseña",
- "team_signup_password.creating": "Creando equipo...",
- "team_signup_password.email": "Correo electrónico",
- "team_signup_password.finish": "Finalizar",
- "team_signup_password.hint": "Las contraseñas deben contener de {min} a {max} caracteres. Su contraseña será más fuerte si contiene una mezcla de símbolos, números y caracteres en mayúsculas y minúsculas.",
- "team_signup_password.passwordError": "Por favor ingrese al menos {chars} caracteres",
- "team_signup_password.selectPassword": "Selecciona la contraseña que estás usando con tu dirección de correos:",
- "team_signup_password.yourPassword": "Tu contraseña",
- "team_signup_send_invites.addInvitation": "Agrega una Invitación",
- "team_signup_send_invites.back": "Volver al paso previo",
- "team_signup_send_invites.disabled": "Este correo electrónico está actualmente deshabilitado para tu equipo, y los correos no podrán ser enviados. Contacta a tu administrador de sistemas",
- "team_signup_send_invites.forNow": "por ahora.",
- "team_signup_send_invites.next": "Siguiente",
- "team_signup_send_invites.prefer": "Si prefieres, puedes invitar a miembros de equipo más tarde <br /> y ",
- "team_signup_send_invites.skip": "saltarte este paso ",
- "team_signup_send_invites.title": "Invita Miembros al Equipo",
- "team_signup_url.back": "Volver al paso previo",
- "team_signup_url.charLength": "El nombre debe tener 4 o más caracteres hasta un máximo de 15",
- "team_signup_url.hint": "<li>Corto y memorizable es mejor</li><li>Use letras en minúsculas, números y guiones</li><li>Debe empezar con una letra y no puede finalizar con un guión</li>",
- "team_signup_url.next": "Siguiente",
- "team_signup_url.regex": "Sólo utiliza letras en minúsculas, numeros y guiones. Debe comenzar con una letra y no puede terminar en un guión.",
- "team_signup_url.required": "Este campo es obligatorio",
- "team_signup_url.taken": "Este URL ya fue asignado o contiene una palabra reservada",
- "team_signup_url.teamUrl": "URL de Equipo",
- "team_signup_url.unavailable": "Este URL no está disponible. Por favor intenta con otro.",
- "team_signup_url.webAddress": "Escoge la dirección web de tu nuevo equipo:",
- "team_signup_username.back": "Volver al paso previo",
- "team_signup_username.chooseUsername": "Escoge un nombre de usuario",
- "team_signup_username.hint": "El nombre de usuario debe empezar con una letra, y contener entre {min} a {max} caracteres en minúscula con números, letras, y los símbolos '.', '-' y '_'.",
- "team_signup_username.invalid": "El nombre de usuario debe comenzar con una letra, y tener entre {min} y {max} de caracteres en total, los cuales pueden ser numeros, letras en minúsculas, o cualquiera de los simbolos '.', '-', o '_'",
- "team_signup_username.memorable": "Selecciona un nombre de usuario sencillo de recordar y que sea fácil para a tus compañeros de equipo identificarte:",
- "team_signup_username.next": "Siguiente",
- "team_signup_username.reserved": "Este nombre de usuario está reservado. Por favor escoge otro.",
- "team_signup_username.username": "Tu nombre de usuario",
- "team_signup_welcome.address": "Dirección de correo",
- "team_signup_welcome.admin": "Tu cuenta administrará un nuevo sitio del equipo. <br />Puedes agregar otros administradores más adelante.",
- "team_signup_welcome.confirm": "Por favor confirma tu dirección de correos:",
- "team_signup_welcome.different": "Usa un correo diferente",
- "team_signup_welcome.instead": "Usa este en vez de",
- "team_signup_welcome.lets": "permítenos setear tu nuevo equipo",
- "team_signup_welcome.storageError": "Este servicio requiere de almacenamiento local para ser habilitado. Por favor habilítalo o sale de la navegación privad.",
- "team_signup_welcome.validEmailError": "Por favor ingresa una dirección de correo electrónico válida",
- "team_signup_welcome.welcome": "Bienvenido a:",
- "team_signup_welcome.yes": "Sí, esta dirección es correcta",
"textbox.bold": "**negritas**",
"textbox.edit": "Editar mensaje",
"textbox.help": "Ayuda",
diff --git a/webapp/i18n/fr.json b/webapp/i18n/fr.json
index 44bf36ef6..1e2ea0e9b 100644
--- a/webapp/i18n/fr.json
+++ b/webapp/i18n/fr.json
@@ -647,7 +647,7 @@
"choose_auth_page.noSignup": "Aucune méthode configurée pour s'inscrive, veuillez contacter votre administrateur système.",
"claim.account.noEmail": "Aucune adresse électronique indiquée",
"claim.email_to_ldap.enterLdapPwd": "Saisissez l'identifiant et le mode de passe de votre compte LDAP",
- "claim.email_to_ldap.enterPwd": "Saisissez le mot de passe pour votre compte {team} {site}",
+ "claim.email_to_ldap.enterPwd": "Saisissez le mot de passe pour votre compte {site}",
"claim.email_to_ldap.ldapId": "Identifiant LDAP",
"claim.email_to_ldap.ldapIdError": "Veuillez saisir votre identifiant LDAP.",
"claim.email_to_ldap.ldapPasswordError": "Veuillez saisir votre mot de passe LDAP.",
@@ -658,7 +658,7 @@
"claim.email_to_ldap.ssoType": "Une fois votre compte configuré, vous ne pourrez vous connectez qu'avec LDAP",
"claim.email_to_ldap.switchTo": "Basculer le compte vers LDAP",
"claim.email_to_ldap.title": "Transférer le login par courriel/mot de passe en LDAP",
- "claim.email_to_oauth.enterPwd": "Saisissez le mot de passe pour votre compte {team} {site}",
+ "claim.email_to_oauth.enterPwd": "Saisissez le mot de passe pour votre compte {site}",
"claim.email_to_oauth.pwd": "Mot de passe",
"claim.email_to_oauth.pwdError": "Veuillez saisir votre mot de passe.",
"claim.email_to_oauth.ssoNote": "Vous devez déjà avoir un compte {type} valide",
@@ -667,7 +667,6 @@
"claim.email_to_oauth.title": "Changer l'adresse électronique/mot de passe pour {uiType}",
"claim.ldap_to_email.confirm": "Confirmer le mot de passe",
"claim.ldap_to_email.email": "Vous devrez utiliser l'adresse électronique {email} pour vous connecter.",
- "claim.ldap_to_email.enterLdapPwd": "Saisissez votre mot de passe LDAP pour votre compte {team} {site}",
"claim.ldap_to_email.enterPwd": "Saisissez un nouveau mot de passe pour votre compte",
"claim.ldap_to_email.ldapPasswordError": "Veuillez saisir votre mot de passe LDAP.",
"claim.ldap_to_email.ldapPwd": "Mot de passe LDAP",
@@ -680,7 +679,7 @@
"claim.oauth_to_email.confirm": "Confirmez le mot de passe",
"claim.oauth_to_email.description": "Une fois votre compte modifié, vous ne pourrez plus vous connecter qu'à l'aide de votre adresse électronique et votre mot de passe.",
"claim.oauth_to_email.enterPwd": "Veuillez saisir un mot de passe.",
- "claim.oauth_to_email.newPwd": "Saisissez un nouveau mot de passe pour votre compte {team} {site}",
+ "claim.oauth_to_email.newPwd": "Saisissez un nouveau mot de passe pour votre compte {site}",
"claim.oauth_to_email.pwdNotMatch": "Le mot de passe ne correspond pas.",
"claim.oauth_to_email.switchTo": "Basculer de {type} vers adresse électronique et mot de passe",
"claim.oauth_to_email.title": "Basculer du compte {type} vers l'adresse électronique",
@@ -765,8 +764,6 @@
"general_tab.includeDirDesc": "Inclure cette équipe affichera le nom de l'équipe dans l'annuaire sur la page d'accueil, ainsi qu'un lien pour rejoindre cette équipe.",
"general_tab.includeDirTitle": "Afficher cette équipe dans l'annuaire",
"general_tab.no": "Non",
- "general_tab.openInviteDesc": "Si activé, un lien pour créer un compte est affiché sur la page de connexion de l'équipe, et permet à n'importe qui de rejoindre l'équipe.",
- "general_tab.openInviteTitle": "Permettre à tout le monde de s'inscrire",
"general_tab.regenerate": "Générer de nouveau",
"general_tab.required": "Ce champ est obligatoire",
"general_tab.teamName": "Nom de l'équipe",
@@ -1012,7 +1009,6 @@
"sidebar_right_menu.report": "Signaler un problème",
"sidebar_right_menu.teamLink": "Obtenir un lien d'invitation d'équipe",
"sidebar_right_menu.teamSettings": "Configuration de l'équipe",
- "signup_team.choose": "Choisir une équipe",
"signup_team.createTeam": "Ou créez une équipe",
"signup_team.disabled": "Aucune méthode de création d'utilisateur n'est disponible. Veuillez contacter votre administrateur système pour obtenir un accès.",
"signup_team.noTeams": "Il n'y a aucune équipe dans l'annuaire, et la création d'équipe n'est pas autorisée.",
@@ -1081,61 +1077,6 @@
"team_settings_modal.generalTab": "Général",
"team_settings_modal.importTab": "Importer",
"team_settings_modal.title": "Configuration de l'équipe",
- "team_signup_display_name.back": "Revenir à l'étape précédente",
- "team_signup_display_name.charLength": "Le nom doit être composé de 4 à 15 caractères",
- "team_signup_display_name.nameHelp": "Donnez un nom à votre équipe. Le nom de votre équipe apparait dans les menus et les en-têtes.",
- "team_signup_display_name.next": "Suivant",
- "team_signup_display_name.required": "Ce champ est obligatoire",
- "team_signup_display_name.teamName": "Nom de l'équipe",
- "team_signup_email.address": "Adresse électronique",
- "team_signup_email.different": "Veuillez utiliser une autre adresse électronique que celle utilisée pour votre inscription",
- "team_signup_email.validEmail": "Veuillez saisir une adresse électronique valide",
- "team_signup_password.agreement": "En créant ce compte et en utilisant {siteName}, vous consentez à nos <a href='/static/help/terms.html'>Conditions d'utilisation</a> et <a href='/static/help/privacy.html'>Politique de Confidentialité</a>. Si vous n'y consentez pas, vous ne pouvez pas utiliser {siteName}.",
- "team_signup_password.back": "Retour à l'étape précédente",
- "team_signup_password.choosePwd": "Choisissez votre mot de passe",
- "team_signup_password.creating": "Création de l'équipe...",
- "team_signup_password.email": "Adresse électronique",
- "team_signup_password.finish": "Terminé",
- "team_signup_password.hint": "Les mots de passe doivent contenir entre {min} et {max} caractère. Votre mot de passe sera plus sécurisé s'il contient un mélange de symboles, de chiffres, et de lettres majuscules et minuscules",
- "team_signup_password.passwordError": "Veuillez saisir au moins {chars} caractères",
- "team_signup_password.selectPassword": "Choisissez le mot de passe que vous utiliserez pour vous connecter avec votre adresse électronique :",
- "team_signup_password.yourPassword": "Votre mot de passe",
- "team_signup_send_invites.addInvitation": "Ajouter une invitation",
- "team_signup_send_invites.back": "Retour à l'étape précédente",
- "team_signup_send_invites.disabled": "Les courriels sont désactivés pour votre équipe et ne peuvent pas être envoyés. Contactez votre administrateur système pour activer les courriels et les invitations par courriel.",
- "team_signup_send_invites.forNow": "pour l'instant.",
- "team_signup_send_invites.next": "Suivant",
- "team_signup_send_invites.prefer": "Vous pouvez aussi inviter des membres plus tard<br /> et",
- "team_signup_send_invites.skip": "passer cette étape",
- "team_signup_send_invites.title": "Inviter des membres",
- "team_signup_url.back": "Retour à l'étape précédente",
- "team_signup_url.charLength": "Le nom doit contenir entre 4 et 15 caractères",
- "team_signup_url.hint": "<li>Court et facile à retenir, c'est mieux !</li><li>Utilisez des lettres minuscules, des chiffres et des tirets</li><li>Doit commencer par une lettre et ne peut pas finir par un tiret.</li>",
- "team_signup_url.next": "Suivant",
- "team_signup_url.regex": "Utilisez seulement des lettres minuscules, des chiffres et des tirets. Doit commencer par une lettre et ne doit pas finir par un tiret.",
- "team_signup_url.required": "Champ obligatoire",
- "team_signup_url.taken": "Cette URL est indisponible ou contient un mot réservé",
- "team_signup_url.teamUrl": "URL de l'équipe",
- "team_signup_url.unavailable": "Cette URL est indisponible. Veuillez essayer une autre URL.",
- "team_signup_url.webAddress": "Choisissez l'adresse internet de votre nouvelle équipe :",
- "team_signup_username.back": "Retour à l'étape précédente",
- "team_signup_username.chooseUsername": "Choisissez votre nom d'utilisateur",
- "team_signup_username.hint": "Les noms d'utilisateurs doivent commencer par une lettre et contenir entre {min} et {max} caractères composés de lettres minuscules, de chiffres et des symboles '.', '-' et '_'.",
- "team_signup_username.invalid": "Les nomes d'utilisateur doivent commencer par une lettre et contenir entre {min} et {max} caractères, qui peuvent être des chiffres, des lettres minuscules ou les symboles '.', '-' ou '_'.",
- "team_signup_username.memorable": "Choisissez un nom d'utilisateur facile à retenir qui permettra aux autres membres de l'équipe de vous identifier facilement :",
- "team_signup_username.next": "Suivant",
- "team_signup_username.reserved": "Ce nom d'utilisateur est réservé, veuilles en choisir un autre.",
- "team_signup_username.username": "Votre nom d'utilisateur",
- "team_signup_welcome.address": "Adresse électronique",
- "team_signup_welcome.admin": "Votre compte sera administrateur de votre nouvelle équipe. <br />Vous pourrez ajouter d'autres administrateurs par la suite.",
- "team_signup_welcome.confirm": "Veuillez confirmer votre adresse électronique :",
- "team_signup_welcome.different": "Utiliser une autre adresse électronique",
- "team_signup_welcome.instead": "Utiliser plutôt ceci",
- "team_signup_welcome.lets": "Configurons ensemble votre nouvelle équipe",
- "team_signup_welcome.storageError": "Ce service nécessite l'utilisation des cookies. Veuillez permettre le stockage des cookies ou quitter la navigation privée.",
- "team_signup_welcome.validEmailError": "Veuillez entrer une adresse électronique valide",
- "team_signup_welcome.welcome": "Bienvenue sur :",
- "team_signup_welcome.yes": "Oui, cette adresse est correcte",
"textbox.bold": "**gras**",
"textbox.edit": "Modifier le message",
"textbox.help": "Aide",
diff --git a/webapp/i18n/ja.json b/webapp/i18n/ja.json
index ea951235e..eab97673e 100644
--- a/webapp/i18n/ja.json
+++ b/webapp/i18n/ja.json
@@ -1181,61 +1181,6 @@
"team_settings_modal.generalTab": "全般",
"team_settings_modal.importTab": "インポート",
"team_settings_modal.title": "チームの設定",
- "team_signup_display_name.back": "前のステップに戻る",
- "team_signup_display_name.charLength": "名前は4文字以上の15文字以下にしてください",
- "team_signup_display_name.nameHelp": "チーム名はどんな言語でも使うことができます。チーム名はメニューと画面上部に表示されます。",
- "team_signup_display_name.next": "次へ",
- "team_signup_display_name.required": "この項目は必須です",
- "team_signup_display_name.teamName": "チーム名",
- "team_signup_email.address": "電子メールアドレス",
- "team_signup_email.different": "利用登録で使用した電子メールアドレスとは別の電子メールアドレスを使ってください",
- "team_signup_email.validEmail": "有効な電子メールアドレスを入力してください",
- "team_signup_password.agreement": "{siteName}にアカウントを作成し利用する前に<a href='/static/help/terms.html'>使用条件</a>と<a href='/static/help/privacy.html'>プライバシーポリシー</a>に同意してください。同意できない場合は{siteName}は使用できません。",
- "team_signup_password.back": "前のステップに戻る",
- "team_signup_password.choosePwd": "パスワードを入力してください",
- "team_signup_password.creating": "チームを作成しています…",
- "team_signup_password.email": "電子メールアドレス",
- "team_signup_password.finish": "完了する",
- "team_signup_password.hint": "パスワードは{min}から{max}文字にしてください。パスワードを強固にするには、記号、数字、大文字と小文字の英字が混在するようにしてください。",
- "team_signup_password.passwordError": "少なくとも{chars}文字を入力してください。",
- "team_signup_password.selectPassword": "電子メールアドレスでログインする場合のパスワードを入力してください:",
- "team_signup_password.yourPassword": "あなたのパスワード",
- "team_signup_send_invites.addInvitation": "招待する",
- "team_signup_send_invites.back": "前のステップに戻る",
- "team_signup_send_invites.disabled": "あなたのチームでは電子メールは有効になっていません。電子メールによる招待状は送信できません。システム管理者に電子メールと電子メールによる招待を有効にするように連絡してください。",
- "team_signup_send_invites.forNow": "いますぐ。",
- "team_signup_send_invites.next": "次へ",
- "team_signup_send_invites.prefer": "後ほどチームメンバーを招待することもできます<br />また ",
- "team_signup_send_invites.skip": "このステップはスキップできます ",
- "team_signup_send_invites.title": "チームメンバーを招待する",
- "team_signup_url.back": "前のステップに戻る",
- "team_signup_url.charLength": "名前は4文字以上の15文字以下にしてください",
- "team_signup_url.hint": "<li>短く覚えやすいものがベストです</li><li>英小文字、数字、ダッシュを使ってください</li><li>英小文字で始めてください。ダッシュで終わることはできません</li>",
- "team_signup_url.next": "次へ",
- "team_signup_url.regex": "英小文字、数字、ダッシュのみ使用できます。英小文字で始めてください。ダッシュで終わることはできません。",
- "team_signup_url.required": "この項目は必須です",
- "team_signup_url.taken": "URLが取得済みか、予約された単語を含んでいます",
- "team_signup_url.teamUrl": "チームURL",
- "team_signup_url.unavailable": "このURLは使用できません。他のものを試してください。",
- "team_signup_url.webAddress": "あなたの新しいチームのウェブアドレスを選択してください。",
- "team_signup_username.back": "前のステップに戻る",
- "team_signup_username.chooseUsername": "ユーザー名を入力してください",
- "team_signup_username.hint": "ユーザー名は英小文字で始めてください。また{min}から{max} 文字の英数字と'.'、'-'、'_'の記号だけで構成してください。",
- "team_signup_username.invalid": "ユーザー名は英小文字で始めてください。また{min}から{max} 文字の英数字と'.'、'-'、'_'の記号だけで構成してください。",
- "team_signup_username.memorable": "チームメイトが認識しやすいように覚えやすいユーザー名を選択してください:",
- "team_signup_username.next": "次へ",
- "team_signup_username.reserved": "このユーザー名は予約されています。他のユーザー名を使ってください。",
- "team_signup_username.username": "あなたのユーザー名",
- "team_signup_welcome.address": "電子メールアドレス",
- "team_signup_welcome.admin": "あなたは新しいチームサイトの管理者になります。<br />後ほど他の人を管理者として追加することができます。",
- "team_signup_welcome.confirm": "あなたの電子メールアドレスを確認してください:",
- "team_signup_welcome.different": "違う電子メールアドレスを使う",
- "team_signup_welcome.instead": "これを代わりに使用する",
- "team_signup_welcome.lets": "新しいチームを作りましょう",
- "team_signup_welcome.storageError": "このサービスを使用するにはローカルストレージを有効にしてください。有効にするかプライベートブラウジングを止めてください。",
- "team_signup_welcome.validEmailError": "有効な電子メールアドレスを入力してください",
- "team_signup_welcome.welcome": "ようこそ:",
- "team_signup_welcome.yes": "この電子メールアドレスは正しいです",
"textbox.bold": "**太字**",
"textbox.edit": "メッセージを編集する",
"textbox.help": "ヘルプ",
diff --git a/webapp/i18n/pt.json b/webapp/i18n/pt.json
index 97fea8396..0fcb958c2 100644
--- a/webapp/i18n/pt.json
+++ b/webapp/i18n/pt.json
@@ -721,7 +721,7 @@
"choose_auth_page.noSignup": "Nenhum método de inscrição configurado, por favor contate seu administrador do sistema.",
"claim.account.noEmail": "Nenhum email específicado",
"claim.email_to_ldap.enterLdapPwd": "Entre o ID e a senha para sua conta LDAP",
- "claim.email_to_ldap.enterPwd": "Entre a senha para o sua conta com email {team} {site}",
+ "claim.email_to_ldap.enterPwd": "Entre a senha para o sua conta com email {site}",
"claim.email_to_ldap.ldapId": "LDAP ID",
"claim.email_to_ldap.ldapIdError": "Por favor digite seu ID LDAP.",
"claim.email_to_ldap.ldapPasswordError": "Por favor digite a sua senha LDAP.",
@@ -732,7 +732,7 @@
"claim.email_to_ldap.ssoType": "Ao retirar a sua conta, você só vai ser capaz de logar com LDAP",
"claim.email_to_ldap.switchTo": "Trocar a conta para LDAP",
"claim.email_to_ldap.title": "Trocar E-mail/Senha da Conta para LDAP",
- "claim.email_to_oauth.enterPwd": "Entre a senha para o sua conta {team} {site}",
+ "claim.email_to_oauth.enterPwd": "Entre a senha para o sua conta {site}",
"claim.email_to_oauth.pwd": "Senha",
"claim.email_to_oauth.pwdError": "Por favor digite a sua senha.",
"claim.email_to_oauth.ssoNote": "Você precisa já ter uma conta {type} válida",
@@ -741,7 +741,6 @@
"claim.email_to_oauth.title": "Trocar E-mail/Senha da Conta para {uiType}",
"claim.ldap_to_email.confirm": "Confirmar senha",
"claim.ldap_to_email.email": "Você vai usar o email {email} para logar",
- "claim.ldap_to_email.enterLdapPwd": "Entre a sua senha LDAP para o sua conta {team} {site}",
"claim.ldap_to_email.enterPwd": "Entre a nova senha para o sua conta com email.",
"claim.ldap_to_email.ldapPasswordError": "Por favor digite a sua senha LDAP.",
"claim.ldap_to_email.ldapPwd": "Senha LDAP",
@@ -758,7 +757,6 @@
"claim.oauth_to_email.pwdNotMatch": "As senha não correspondem.",
"claim.oauth_to_email.switchTo": "Trocar {type} para email e senha",
"claim.oauth_to_email.title": "Trocar Conta {type} para E-mail",
- "claim.oauth_to_email_newPwd": "Entre a nova senha para o sua conta {team} {site}",
"confirm_modal.cancel": "Cancelar",
"create_comment.addComment": "Adicionar um comentário...",
"create_comment.comment": "Adicionar Comentário",
@@ -841,8 +839,6 @@
"general_tab.includeDirDesc": "Incluindo esta equipe irá exibir o nome da equipe da seção Diretório Equipe da página inicial, e fornecer um link para a página de login.",
"general_tab.includeDirTitle": "Incluir esta equipe no Diretório de Equipe",
"general_tab.no": "Não",
- "general_tab.openInviteDesc": "Quando permitido, um link para a criação da conta será incluído na página de login da equipe e permitir que qualquer visitante inscreva-se.",
- "general_tab.openInviteTitle": "Permitir que qualquer pessoa se inscreva a partir da página de login",
"general_tab.regenerate": "Re-Gerar",
"general_tab.required": "Este campo é obrigatório",
"general_tab.teamName": "Nome da Equipe",
@@ -1111,7 +1107,6 @@
"sidebar_right_menu.report": "Relatar um Problema",
"sidebar_right_menu.teamLink": "Obter Link para Convite de Equipe",
"sidebar_right_menu.teamSettings": "Configurações da Equipe",
- "signup_team.choose": "Escolha uma Equipe",
"signup_team.createTeam": "Ou Criar uma Equipe",
"signup_team.disabled": "A criação de equipe foi desativada. Por favor, entre em contato com um administrador para o acesso.",
"signup_team.noTeams": "Não existe equipes incluidas no Diretório de Equipe e a criação de equipes foi desativada.",
@@ -1181,61 +1176,6 @@
"team_settings_modal.generalTab": "Geral",
"team_settings_modal.importTab": "Importar",
"team_settings_modal.title": "Configurações da Equipe",
- "team_signup_display_name.back": "Voltar para o passo anterior",
- "team_signup_display_name.charLength": "O nome deve ser de 4 ou mais caracteres até um máximo de 15",
- "team_signup_display_name.nameHelp": "Nome da sua equipe em qualquer idioma. Seu nome de equipe é mostrado em menus e títulos.",
- "team_signup_display_name.next": "Próximo",
- "team_signup_display_name.required": "Este campo é obrigatório",
- "team_signup_display_name.teamName": "Nome Da Equipe",
- "team_signup_email.address": "Endereço de E-mail",
- "team_signup_email.different": "Por favor, use um e-mail diferente do que o usado na inscrição",
- "team_signup_email.validEmail": "Por favor entre um endereço de e-mail válido",
- "team_signup_password.agreement": "Ao prosseguir para criar sua conta e usar {siteName}, você concorda com nosso <a href='/static/help/terms.html'>Termo de Serviço</a> e <a href='/static/help/privacy.html'>Politica de Privacidade</a>. Se você não concorda, você não pode usar {siteName}.",
- "team_signup_password.back": "Voltar para o passo anterior",
- "team_signup_password.choosePwd": "Escolha sua senha",
- "team_signup_password.creating": "Criando um equipe...",
- "team_signup_password.email": "E-mail",
- "team_signup_password.finish": "Terminar",
- "team_signup_password.hint": "Senhas precisam conter {min} a {max} caracteres. Sua senha será segura se conter uma mistura de símbolos, números, e caracteres maiúsculos e minúsculos.",
- "team_signup_password.passwordError": "Por favor, insira pelo menos {chars} caracteres",
- "team_signup_password.selectPassword": "Selecione uma senha que você irá usar no login com seu endereço de email:",
- "team_signup_password.yourPassword": "Sua senha",
- "team_signup_send_invites.addInvitation": "Adicionar Convite",
- "team_signup_send_invites.back": "Voltar para o passo anterior",
- "team_signup_send_invites.disabled": "Email está desativado para a sua equipe, e emails e não podem ser enviados. Contate o seu administrador do sistema para ativar e-mail e convites por e-mail.",
- "team_signup_send_invites.forNow": "agora.",
- "team_signup_send_invites.next": "Próximo",
- "team_signup_send_invites.prefer": "se você preferir, você pode convidar membros da equipe depois<br /> e ",
- "team_signup_send_invites.skip": "pular este passo ",
- "team_signup_send_invites.title": "Convidar Membros da Equipe",
- "team_signup_url.back": "Voltar para o passo anterior",
- "team_signup_url.charLength": "O nome deve ser de 4 ou mais caracteres até um máximo de 15",
- "team_signup_url.hint": "<li>Curto e memorizável é o melhor</li><li>Use letras minúsculas, números e traços</li><li>Deve começar com uma letra e não pode terminar em um traço</li>",
- "team_signup_url.next": "Próximo",
- "team_signup_url.regex": "Utilize apenas letras minúsculas, números e traços. Deve começar com uma letra e não pode terminar em um traço.",
- "team_signup_url.required": "Este campo é obrigatório",
- "team_signup_url.taken": "URL é usada ou contém uma palavra reservada",
- "team_signup_url.teamUrl": "Equipe URL",
- "team_signup_url.unavailable": "Está URL está indisponível. Por favor tente outra.",
- "team_signup_url.webAddress": "Escolha o endereço web para sua nova equipe:",
- "team_signup_username.back": "Voltar para o passo anterior",
- "team_signup_username.chooseUsername": "Escolha o seu nome de usuário",
- "team_signup_username.hint": "O nome de usuário precisa começar com uma letra, e conter entre {min} e {max} caracteres minúsculos contendo números, letras, e os símbolos '.', '-' e '_'",
- "team_signup_username.invalid": "O nome de usuário precisa começar com uma letra, e conter entre {min} e {max} caracteres no total, podendo ser números, letras minúsculas, ou qualquer dos símbolos '.', '-' ou '_'",
- "team_signup_username.memorable": "Escolha um nome de usuário memorizável que torna fácil para sua equipe de trabalho identificá-lo:",
- "team_signup_username.next": "Próximo",
- "team_signup_username.reserved": "Este nome de usuário é reservado, por favor, escolha uma nova.",
- "team_signup_username.username": "Seu usuário",
- "team_signup_welcome.address": "Endereço de E-mail",
- "team_signup_welcome.admin": "Sua conta irá administrar o novo site da equipe. <br />Você pode adicionar outros administradores depois.",
- "team_signup_welcome.confirm": "Por favor confirme seu endereço de e-mail:",
- "team_signup_welcome.different": "Utilize um e-mail diferente",
- "team_signup_welcome.instead": "Use este ao invez",
- "team_signup_welcome.lets": "Vamos configurar sua nova equipe",
- "team_signup_welcome.storageError": "Este serviço requer um armazenamento local para ser ativado. Por favor, habilite ou saia da navegação privada.",
- "team_signup_welcome.validEmailError": "Por favor entre um endereço de e-mail válido",
- "team_signup_welcome.welcome": "Bem-vindo:",
- "team_signup_welcome.yes": "Sim, este endereço de email está correto",
"textbox.bold": "**negrito**",
"textbox.edit": "Editar mensagem",
"textbox.help": "Ajuda",
diff --git a/webapp/package.json b/webapp/package.json
index 2e9efadb3..3ab971c09 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -28,7 +28,8 @@
"react-router": "2.0.1",
"react-textarea-autosize": "3.3.0",
"twemoji": "1.4.1",
- "velocity-animate": "1.2.3"
+ "velocity-animate": "1.2.3",
+ "superagent": "1.8.3"
},
"devDependencies": {
"babel-eslint": "5.0.0",
@@ -53,12 +54,22 @@
"sass-loader": "3.2.0",
"style-loader": "0.13.0",
"url-loader": "0.5.7",
- "webpack": "2.1.0-beta.5"
+ "webpack": "2.1.0-beta.5",
+
+ "mocha": "*",
+ "mocha-webpack": "*",
+ "webpack-node-externals": "*",
+ "mocha-jsdom": "*",
+ "jsdom": "*",
+ "jsdom-global": "*",
+ "react-addons-test-utils": "*",
+ "jquery-deferred": "*"
},
"scripts": {
"check": "eslint --ext \".jsx\" --ignore-pattern node_modules --quiet .",
"build": "webpack",
"run": "webpack --progress --watch",
- "run-fullmap": "webpack --progress --watch"
+ "run-fullmap": "webpack --progress --watch",
+ "test": "mocha-webpack --webpack-config webpack.config-test.js \"**/*.test.jsx\""
}
}
diff --git a/webapp/root.html b/webapp/root.html
index 1612bdce4..cc2b7cd61 100644
--- a/webapp/root.html
+++ b/webapp/root.html
@@ -46,7 +46,18 @@
</script>
</head>
<body>
- <div id='root'/>
+ <div id='root'>
+ <div
+ class='loading-screen'
+ style='relative'
+ >
+ <div class='loading__content'>
+ <div class='round round-1'></div>
+ <div class='round round-2'></div>
+ <div class='round round-3'></div>
+ </div>
+ </div>
+ </div>
<script>
window.setup_root();
</script>
diff --git a/webapp/root.jsx b/webapp/root.jsx
index 9268643f3..b0625438f 100644
--- a/webapp/root.jsx
+++ b/webapp/root.jsx
@@ -10,10 +10,10 @@ import 'sass/styles.scss';
import React from 'react';
import ReactDOM from 'react-dom';
-import {Router, Route, IndexRoute, IndexRedirect, Redirect, browserHistory} from 'react-router';
+import {Router, Route, IndexRoute, Redirect, browserHistory} from 'react-router';
import Root from 'components/root.jsx';
import LoggedIn from 'components/logged_in.jsx';
-import NotLoggedIn from 'components/not_logged_in.jsx';
+import HeaderFooterTemplate from 'components/header_footer_template.jsx';
import NeedsTeam from 'components/needs_team.jsx';
import PasswordResetSendLink from 'components/password_reset_send_link.jsx';
import PasswordResetForm from 'components/password_reset_form.jsx';
@@ -21,16 +21,15 @@ import ChannelView from 'components/channel_view.jsx';
import PermalinkView from 'components/permalink_view.jsx';
import Sidebar from 'components/sidebar.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
-import PreferenceStore from 'stores/preference_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import ErrorStore from 'stores/error_store.jsx';
-import BrowserStore from 'stores/browser_store.jsx';
-import SignupTeam from 'components/signup_team.jsx';
-import * as Client from 'utils/client.jsx';
-import * as Websockets from 'action_creators/websocket_actions.jsx';
+import TeamStore from 'stores/team_store.jsx';
import * as Utils from 'utils/utils.jsx';
+
+import Client from 'utils/web_client.jsx';
+
+import * as Websockets from 'action_creators/websocket_actions.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
-import SignupTeamConfirm from 'components/signup_team_confirm.jsx';
import SignupUserComplete from 'components/signup_user_complete.jsx';
import ShouldVerifyEmail from 'components/should_verify_email.jsx';
import DoVerifyEmail from 'components/do_verify_email.jsx';
@@ -47,14 +46,9 @@ import AddOutgoingWebhook from 'components/backstage/add_outgoing_webhook.jsx';
import AddCommand from 'components/backstage/add_command.jsx';
import ErrorPage from 'components/error_page.jsx';
-import SignupTeamComplete from 'components/signup_team_complete/components/signup_team_complete.jsx';
-import WelcomePage from 'components/signup_team_complete/components/team_signup_welcome_page.jsx';
-import TeamDisplayNamePage from 'components/signup_team_complete/components/team_signup_display_name_page.jsx';
-import TeamURLPage from 'components/signup_team_complete/components/team_signup_url_page.jsx';
-import SendInivtesPage from 'components/signup_team_complete/components/team_signup_send_invites_page.jsx';
-import UsernamePage from 'components/signup_team_complete/components/team_signup_username_page.jsx';
-import PasswordPage from 'components/signup_team_complete/components/team_signup_password_page.jsx';
-import FinishedPage from 'components/signup_team_complete/components/team_signup_finished.jsx';
+import AppDispatcher from './dispatcher/app_dispatcher.jsx';
+import Constants from './utils/constants.jsx';
+const ActionTypes = Constants.ActionTypes;
import Claim from 'components/claim/claim.jsx';
import EmailToOAuth from 'components/claim/components/email_to_oauth.jsx';
@@ -63,6 +57,10 @@ import LDAPToEmail from 'components/claim/components/ldap_to_email.jsx';
import EmailToLDAP from 'components/claim/components/email_to_ldap.jsx';
import Login from 'components/login/login.jsx';
+import SelectTeam from 'components/select_team/select_team.jsx';
+import CreateTeam from 'components/create_team/create_team.jsx';
+import CreateTeamDisplayName from 'components/create_team/components/display_name.jsx';
+import CreateTeamTeamUrl from 'components/create_team/components/team_url.jsx';
import * as I18n from 'i18n/i18n.jsx';
@@ -76,53 +74,33 @@ const notFoundParams = {
// This is for anything that needs to be done for ALL react components.
// This runs before we start to render anything.
function preRenderSetup(callwhendone) {
- const d1 = Client.getClientConfig(
- (data, textStatus, xhr) => {
- if (!data) {
- return;
- }
-
- global.window.mm_config = data;
-
- var serverVersion = xhr.getResponseHeader('X-Version-ID');
-
- if (serverVersion !== BrowserStore.getLastServerVersion()) {
- if (!BrowserStore.getLastServerVersion() || BrowserStore.getLastServerVersion() === '') {
- BrowserStore.setLastServerVersion(serverVersion);
- } else {
- BrowserStore.setLastServerVersion(serverVersion);
- window.location.reload(true);
- console.log('Detected version update refreshing the page'); //eslint-disable-line no-console
- }
- }
- },
- (err) => {
- AsyncClient.dispatchError(err, 'getClientConfig');
+ window.onerror = (msg, url, line, column, stack) => {
+ var l = {};
+ l.level = 'ERROR';
+ l.message = 'msg: ' + msg + ' row: ' + line + ' col: ' + column + ' stack: ' + stack + ' url: ' + url;
+
+ $.ajax({
+ url: '/api/v3/admin/log_client',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'POST',
+ data: JSON.stringify(l)
+ });
+
+ if (window.mm_config && window.mm_config.EnableDeveloper === 'true') {
+ window.ErrorStore.storeLastError({message: 'DEVELOPER MODE: A javascript error has occured. Please use the javascript console to capture and report the error (row: ' + line + ' col: ' + column + ').'});
+ window.ErrorStore.emitChange();
}
- );
+ };
- const d2 = Client.getClientLicenceConfig(
- (data) => {
- if (!data) {
- return;
- }
+ var d1 = $.Deferred(); //eslint-disable-line new-cap
- global.window.mm_license = data;
- },
- (err) => {
- AsyncClient.dispatchError(err, 'getClientLicenceConfig');
+ GlobalActions.emitInitialLoad(
+ () => {
+ d1.resolve();
}
);
- // Set these here so they don't fail in client.jsx track
- global.window.analytics = [];
- global.window.analytics.page = () => {
- // Do Nothing
- };
- global.window.analytics.track = () => {
- // Do Nothing
- };
-
// Make sure the websockets close
$(window).on('beforeunload',
() => {
@@ -132,7 +110,9 @@ function preRenderSetup(callwhendone) {
function afterIntl() {
I18n.doAddLocaleData();
- $.when(d1, d2).done(callwhendone);
+ $.when(d1).done(() => {
+ callwhendone();
+ });
}
if (global.Intl) {
@@ -143,18 +123,59 @@ function preRenderSetup(callwhendone) {
}
function preLoggedIn(nextState, replace, callback) {
- const d1 = Client.getAllPreferences(
+ ErrorStore.clearLastError();
+ callback();
+}
+
+function preNeedsTeam(nextState, replace, callback) {
+ // First check to make sure you're in the current team
+ // for the current url.
+ var teamName = Utils.getTeamNameFromUrl();
+ var team = TeamStore.getByName(teamName);
+
+ if (!team) {
+ browserHistory.push('/error');
+ return;
+ }
+
+ GlobalActions.emitCloseRightHandSide();
+
+ TeamStore.saveMyTeam(team);
+ TeamStore.emitChange();
+
+ var d1 = $.Deferred(); //eslint-disable-line new-cap
+ var d2 = $.Deferred(); //eslint-disable-line new-cap
+
+ Client.getChannels(
(data) => {
- PreferenceStore.setPreferencesFromServer(data);
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_CHANNELS,
+ channels: data.channels,
+ members: data.members
+ });
+
+ d1.resolve();
},
(err) => {
- AsyncClient.dispatchError(err, 'getAllPreferences');
+ AsyncClient.dispatchError(err, 'getChannels');
+ d1.resolve();
}
);
- const d2 = AsyncClient.getChannels();
+ Client.getProfiles(
+ (data) => {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_PROFILES,
+ profiles: data
+ });
- ErrorStore.clearLastError();
+ d2.resolve();
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'getProfiles');
+ d2.resolve();
+ }
+ );
$.when(d1, d2).done(() => {
callback();
@@ -163,21 +184,20 @@ function preLoggedIn(nextState, replace, callback) {
function onPermalinkEnter(nextState) {
const postId = nextState.params.postid;
-
GlobalActions.emitPostFocusEvent(postId);
}
-function onChannelEnter(nextState) {
- doChannelChange(nextState);
+function onChannelEnter(nextState, replace) {
+ doChannelChange(nextState, replace);
}
-function onChannelChange(prevState, nextState) {
+function onChannelChange(prevState, nextState, replace) {
if (prevState.params.channel !== nextState.params.channel) {
- doChannelChange(nextState);
+ doChannelChange(nextState, replace);
}
}
-function doChannelChange(state) {
+function doChannelChange(state, replace) {
let channel;
if (state.location.query.fakechannel) {
channel = JSON.parse(state.location.query.fakechannel);
@@ -187,28 +207,13 @@ function doChannelChange(state) {
channel = ChannelStore.getMoreByName(state.params.channel);
}
if (!channel) {
- console.error('Unable to get channel to change to.'); //eslint-disable-line no-console
+ replace('/');
+ return;
}
}
GlobalActions.emitChannelClickEvent(channel);
}
-function onLoggedOut(nextState) {
- const teamName = nextState.params.team;
- Client.logout(
- () => {
- browserHistory.push('/' + teamName + '/login');
- BrowserStore.signalLogout();
- BrowserStore.clear();
- ErrorStore.clearLastError();
- PreferenceStore.clear();
- },
- () => {
- browserHistory.push('/' + teamName + '/login');
- }
- );
-}
-
function renderRootComponent() {
ReactDOM.render((
<Router
@@ -222,141 +227,38 @@ function renderRootComponent() {
path='error'
component={ErrorPage}
/>
- <Route
- component={LoggedIn}
- onEnter={preLoggedIn}
- >
+ <Route component={HeaderFooterTemplate}>
<Route
- path=':team/channels/:channel'
- onEnter={onChannelEnter}
- onChange={onChannelChange}
- components={{
- sidebar: Sidebar,
- center: ChannelView
- }}
+ path='login'
+ component={Login}
/>
<Route
- path=':team/pl/:postid'
- onEnter={onPermalinkEnter}
- components={{
- sidebar: Sidebar,
- center: PermalinkView
- }}
- />
- <Route
- path=':team/tutorial'
- components={{
- sidebar: Sidebar,
- center: TutorialView
- }}
- />
- <Route
- path=':team/logout'
- onEnter={onLoggedOut}
- />
- <Route path='settings/integrations'>
- <IndexRoute
- components={{
- navbar: BackstageNavbar,
- sidebar: BackstageSidebar,
- center: Integrations
- }}
- />
- <Route path='incoming_webhooks'>
- <IndexRoute
- components={{
- navbar: BackstageNavbar,
- sidebar: BackstageSidebar,
- center: InstalledIncomingWebhooks
- }}
- />
- <Route
- path='add'
- components={{
- navbar: BackstageNavbar,
- sidebar: BackstageSidebar,
- center: AddIncomingWebhook
- }}
- />
- </Route>
- <Route path='outgoing_webhooks'>
- <IndexRoute
- components={{
- navbar: BackstageNavbar,
- sidebar: BackstageSidebar,
- center: InstalledOutgoingWebhooks
- }}
- />
- <Route
- path='add'
- components={{
- navbar: BackstageNavbar,
- sidebar: BackstageSidebar,
- center: AddOutgoingWebhook
- }}
- />
- </Route>
- <Route path='commands'>
- <IndexRoute
- components={{
- navbar: BackstageNavbar,
- sidebar: BackstageSidebar,
- center: InstalledCommands
- }}
- />
- <Route
- path='add'
- components={{
- navbar: BackstageNavbar,
- sidebar: BackstageSidebar,
- center: AddCommand
- }}
- />
- </Route>
- <Redirect
- from='*'
- to='/error'
- query={notFoundParams}
- />
- </Route>
- <Route
- path='admin_console'
- component={AdminConsole}
+ path='reset_password'
+ component={PasswordResetSendLink}
/>
- </Route>
- <Route component={NotLoggedIn}>
<Route
- path='signup_team'
- component={SignupTeam}
+ path='reset_password_complete'
+ component={PasswordResetForm}
/>
<Route
- path='signup_team_complete'
- component={SignupTeamComplete}
+ path='claim'
+ component={Claim}
>
- <IndexRoute component={FinishedPage}/>
- <Route
- path='welcome'
- component={WelcomePage}
- />
- <Route
- path='team_display_name'
- component={TeamDisplayNamePage}
- />
<Route
- path='team_url'
- component={TeamURLPage}
+ path='oauth_to_email'
+ component={OAuthToEmail}
/>
<Route
- path='send_invites'
- component={SendInivtesPage}
+ path='email_to_oauth'
+ component={EmailToOAuth}
/>
<Route
- path='username'
- component={UsernamePage}
+ path='email_to_ldap'
+ component={EmailToLDAP}
/>
<Route
- path='password'
- component={PasswordPage}
+ path='ldap_to_email'
+ component={LDAPToEmail}
/>
</Route>
<Route
@@ -364,10 +266,6 @@ function renderRootComponent() {
component={SignupUserComplete}
/>
<Route
- path='signup_team_confirm'
- component={SignupTeamConfirm}
- />
- <Route
path='should_verify_email'
component={ShouldVerifyEmail}
/>
@@ -375,51 +273,136 @@ function renderRootComponent() {
path='do_verify_email'
component={DoVerifyEmail}
/>
+ </Route>
+ <Route
+ component={LoggedIn}
+ onEnter={preLoggedIn}
+ >
+ <Route component={HeaderFooterTemplate}>
+ <Route
+ path='select_team'
+ component={SelectTeam}
+ />
+ <Route
+ path='create_team'
+ component={CreateTeam}
+ >
+ <IndexRoute component={CreateTeamDisplayName}/>
+ <Route
+ path='display_name'
+ component={CreateTeamDisplayName}
+ />
+ <Route
+ path='team_url'
+ component={CreateTeamTeamUrl}
+ />
+ </Route>
+ </Route>
+ <Route
+ path='admin_console'
+ component={AdminConsole}
+ />
<Route
path=':team'
component={NeedsTeam}
+ onEnter={preNeedsTeam}
>
- <IndexRedirect to='login'/>
<Route
- path='login'
- component={Login}
+ path='channels/:channel'
+ onEnter={onChannelEnter}
+ onChange={onChannelChange}
+ components={{
+ sidebar: Sidebar,
+ center: ChannelView
+ }}
/>
<Route
- path='reset_password'
- component={PasswordResetSendLink}
+ path='pl/:postid'
+ onEnter={onPermalinkEnter}
+ components={{
+ sidebar: Sidebar,
+ center: PermalinkView
+ }}
/>
<Route
- path='reset_password_complete'
- component={PasswordResetForm}
+ path='tutorial'
+ components={{
+ sidebar: Sidebar,
+ center: TutorialView
+ }}
/>
- <Route
- path='claim'
- component={Claim}
- >
- <Route
- path='oauth_to_email'
- component={OAuthToEmail}
- />
- <Route
- path='email_to_oauth'
- component={EmailToOAuth}
- />
- <Route
- path='email_to_ldap'
- component={EmailToLDAP}
+ <Route path='settings/integrations'>
+ <IndexRoute
+ components={{
+ navbar: BackstageNavbar,
+ sidebar: BackstageSidebar,
+ center: Integrations
+ }}
/>
- <Route
- path='ldap_to_email'
- component={LDAPToEmail}
+ <Route path='incoming_webhooks'>
+ <IndexRoute
+ components={{
+ navbar: BackstageNavbar,
+ sidebar: BackstageSidebar,
+ center: InstalledIncomingWebhooks
+ }}
+ />
+ <Route
+ path='add'
+ components={{
+ navbar: BackstageNavbar,
+ sidebar: BackstageSidebar,
+ center: AddIncomingWebhook
+ }}
+ />
+ </Route>
+ <Route path='outgoing_webhooks'>
+ <IndexRoute
+ components={{
+ navbar: BackstageNavbar,
+ sidebar: BackstageSidebar,
+ center: InstalledOutgoingWebhooks
+ }}
+ />
+ <Route
+ path='add'
+ components={{
+ navbar: BackstageNavbar,
+ sidebar: BackstageSidebar,
+ center: AddOutgoingWebhook
+ }}
+ />
+ </Route>
+ <Route path='commands'>
+ <IndexRoute
+ components={{
+ navbar: BackstageNavbar,
+ sidebar: BackstageSidebar,
+ center: InstalledCommands
+ }}
+ />
+ <Route
+ path='add'
+ components={{
+ navbar: BackstageNavbar,
+ sidebar: BackstageSidebar,
+ center: AddCommand
+ }}
+ />
+ </Route>
+ <Redirect
+ from='*'
+ to='/error'
+ query={notFoundParams}
/>
</Route>
- <Redirect
- from='*'
- to='/error'
- query={notFoundParams}
- />
</Route>
</Route>
+ <Redirect
+ from='*'
+ to='/error'
+ query={notFoundParams}
+ />
</Route>
</Router>
),
diff --git a/webapp/sass/routes/_backstage.scss b/webapp/sass/routes/_backstage.scss
index 3257f6582..ebfe97ee4 100644
--- a/webapp/sass/routes/_backstage.scss
+++ b/webapp/sass/routes/_backstage.scss
@@ -216,6 +216,7 @@ body {
.item-details__description,
.item-details__token,
.item-details__trigger-words,
+ .item-details__url,
.item-details__creation {
display: inline-block;
margin-top: 10px;
diff --git a/webapp/sass/routes/_signup.scss b/webapp/sass/routes/_signup.scss
index 77ccdf4ed..08bd0d12d 100644
--- a/webapp/sass/routes/_signup.scss
+++ b/webapp/sass/routes/_signup.scss
@@ -419,6 +419,17 @@
}
}
+ .signup-team-dir-err {
+ background: #fafafa;
+ border-top: 1px solid #d5d5d5;
+ color: inherit;
+ padding: 5px 15px;
+
+ &:first-child {
+ border: none;
+ }
+ }
+
.signup-team-dir__name {
float: left;
overflow: hidden;
diff --git a/webapp/stores/browser_store.jsx b/webapp/stores/browser_store.jsx
index d605aac80..2dae78f46 100644
--- a/webapp/stores/browser_store.jsx
+++ b/webapp/stores/browser_store.jsx
@@ -35,18 +35,6 @@ class BrowserStoreClass {
this.isSignallingLogin = this.isSignallingLogin.bind(this);
}
- checkVersion() {
- var currentVersion = this.getGlobalItem('storage_version');
- if (currentVersion !== global.window.mm_config.Version) {
- this.clearAll();
- try {
- this.setGlobalItem('storage_version', global.window.mm_config.Version);
- } catch (e) {
- // Do nothing
- }
- }
- }
-
setItem(name, value) {
this.setGlobalItem(getPrefix() + name, value);
}
diff --git a/webapp/stores/error_store.jsx b/webapp/stores/error_store.jsx
index 715029185..3e043dd78 100644
--- a/webapp/stores/error_store.jsx
+++ b/webapp/stores/error_store.jsx
@@ -20,6 +20,12 @@ class ErrorStoreClass extends EventEmitter {
this.removeChangeListener = this.removeChangeListener.bind(this);
this.getLastError = this.getLastError.bind(this);
this.storeLastError = this.storeLastError.bind(this);
+ this.getIgnoreEmailPreview = this.getIgnoreEmailPreview.bind(this);
+ this.ignore_email_preview = false;
+ }
+
+ getIgnoreEmailPreview() {
+ return this.ignore_email_preview;
}
emitChange() {
@@ -57,6 +63,11 @@ class ErrorStoreClass extends EventEmitter {
}
clearLastError() {
+ var lastError = this.getLastError();
+ if (lastError && lastError.email_preview) {
+ this.ignore_email_preview = true;
+ }
+
BrowserStore.removeGlobalItem('last_error');
BrowserStore.removeGlobalItem('last_error_conn');
this.emitChange();
diff --git a/webapp/stores/preference_store.jsx b/webapp/stores/preference_store.jsx
index fcfd1c426..1a461f39f 100644
--- a/webapp/stores/preference_store.jsx
+++ b/webapp/stores/preference_store.jsx
@@ -112,4 +112,4 @@ class PreferenceStoreClass extends EventEmitter {
const PreferenceStore = new PreferenceStoreClass();
export default PreferenceStore;
-window.PreferenceStore = PreferenceStore;
+global.window.PreferenceStore = PreferenceStore;
diff --git a/webapp/stores/team_store.jsx b/webapp/stores/team_store.jsx
index e1fc9167d..356df7b07 100644
--- a/webapp/stores/team_store.jsx
+++ b/webapp/stores/team_store.jsx
@@ -33,7 +33,14 @@ class TeamStoreClass extends EventEmitter {
this.getCurrentInviteLink = this.getCurrentInviteLink.bind(this);
this.saveTeam = this.saveTeam.bind(this);
+ this.clear();
+ }
+
+ clear() {
this.teams = {};
+ this.team_members = [];
+ this.members_for_team = [];
+ this.teamListings = {};
this.currentTeamId = '';
}
@@ -119,6 +126,34 @@ class TeamStoreClass extends EventEmitter {
this.saveTeam(team);
this.currentTeamId = team.id;
}
+
+ saveTeamMembers(members) {
+ this.team_members = members;
+ }
+
+ appendTeamMember(member) {
+ this.team_members.push(member);
+ }
+
+ getTeamMembers() {
+ return this.team_members;
+ }
+
+ saveMembersForTeam(members) {
+ this.members_for_team = members;
+ }
+
+ getMembersForTeam() {
+ return this.members_for_team;
+ }
+
+ saveTeamListings(teams) {
+ this.teamListings = teams;
+ }
+
+ getTeamListings() {
+ return this.teamListings;
+ }
}
var TeamStore = new TeamStoreClass();
@@ -135,6 +170,18 @@ TeamStore.dispatchToken = AppDispatcher.register((payload) => {
TeamStore.saveTeams(action.teams);
TeamStore.emitChange();
break;
+ case ActionTypes.RECEIVED_TEAM_MEMBERS:
+ TeamStore.saveTeamMembers(action.team_members);
+ TeamStore.emitChange();
+ break;
+ case ActionTypes.RECEIVED_ALL_TEAM_LISTINGS:
+ TeamStore.saveTeamListings(action.teams);
+ TeamStore.emitChange();
+ break;
+ case ActionTypes.RECEIVED_MEMBERS_FOR_TEAM:
+ TeamStore.saveMembersForTeam(action.team_members);
+ TeamStore.emitChange();
+ break;
default:
}
});
diff --git a/webapp/stores/user_store.jsx b/webapp/stores/user_store.jsx
index 4213e6e8c..2c6bfade3 100644
--- a/webapp/stores/user_store.jsx
+++ b/webapp/stores/user_store.jsx
@@ -16,11 +16,17 @@ const CHANGE_EVENT_STATUSES = 'change_statuses';
class UserStoreClass extends EventEmitter {
constructor() {
super();
+ this.clear();
+ }
+
+ clear() {
this.profiles = {};
+ this.direct_profiles = {};
this.statuses = {};
this.sessions = {};
this.audits = {};
this.currentUserId = '';
+ this.noAccounts = false;
}
emitChange(userId) {
@@ -116,7 +122,12 @@ class UserStoreClass extends EventEmitter {
return this.getCurrentUser();
}
- return this.getProfiles()[userId];
+ const user = this.getProfiles()[userId];
+ if (user) {
+ return user;
+ }
+
+ return this.getDirectProfiles()[userId];
}
getProfileByUsername(username) {
@@ -137,6 +148,14 @@ class UserStoreClass extends EventEmitter {
return profileUsernameMap;
}
+ getDirectProfiles() {
+ return this.direct_profiles;
+ }
+
+ saveDirectProfiles(profiles) {
+ this.direct_profiles = profiles;
+ }
+
getProfiles() {
return this.profiles;
}
@@ -259,6 +278,14 @@ class UserStoreClass extends EventEmitter {
getStatus(id) {
return this.getStatuses()[id];
}
+
+ getNoAccounts() {
+ return this.noAccounts;
+ }
+
+ setNoAccounts(noAccounts) {
+ this.noAccounts = noAccounts;
+ }
}
var UserStore = new UserStoreClass();
@@ -272,6 +299,10 @@ UserStore.dispatchToken = AppDispatcher.register((payload) => {
UserStore.saveProfiles(action.profiles);
UserStore.emitChange();
break;
+ case ActionTypes.RECEIVED_DIRECT_PROFILES:
+ UserStore.saveDirectProfiles(action.profiles);
+ UserStore.emitChange();
+ break;
case ActionTypes.RECEIVED_ME:
UserStore.setCurrentUser(action.me);
UserStore.emitChange(action.me.id);
diff --git a/webapp/tests/client_channel.test.jsx b/webapp/tests/client_channel.test.jsx
new file mode 100644
index 000000000..b8374123c
--- /dev/null
+++ b/webapp/tests/client_channel.test.jsx
@@ -0,0 +1,334 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+/* eslint-disable no-console */
+/* eslint-disable global-require */
+/* eslint-disable func-names */
+/* eslint-disable prefer-arrow-callback */
+/* eslint-disable no-magic-numbers */
+/* eslint-disable no-unreachable */
+
+import assert from 'assert';
+import TestHelper from './test_helper.jsx';
+
+describe('Client.Channels', function() {
+ this.timeout(100000);
+
+ it('createChannel', function(done) {
+ TestHelper.initBasic(() => {
+ var channel = TestHelper.fakeChannel();
+ channel.team_id = TestHelper.basicTeam().id;
+ TestHelper.basicClient().createChannel(
+ channel,
+ function(data) {
+ assert.equal(data.id.length > 0, true);
+ assert.equal(data.name, channel.name);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('createDirectChannel', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().createUser(
+ TestHelper.fakeUser(),
+ function(user2) {
+ TestHelper.basicClient().addUserToTeam(
+ user2.id,
+ function() {
+ TestHelper.basicClient().createDirectChannel(
+ user2.id,
+ function(data) {
+ assert.equal(data.id.length > 0, true);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('updateChannel', function(done) {
+ TestHelper.initBasic(() => {
+ var channel = TestHelper.basicChannel();
+ channel.display_name = 'changed';
+ TestHelper.basicClient().updateChannel(
+ channel,
+ function(data) {
+ assert.equal(data.id.length > 0, true);
+ assert.equal(data.display_name, 'changed');
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('updateChannelHeader', function(done) {
+ TestHelper.initBasic(() => {
+ var channel = TestHelper.basicChannel();
+ channel.display_name = 'changed';
+ TestHelper.basicClient().updateChannelHeader(
+ channel.id,
+ 'new header',
+ function(data) {
+ assert.equal(data.id.length > 0, true);
+ assert.equal(data.header, 'new header');
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('updateChannelPurpose', function(done) {
+ TestHelper.initBasic(() => {
+ var channel = TestHelper.basicChannel();
+ channel.display_name = 'changed';
+ TestHelper.basicClient().updateChannelPurpose(
+ channel.id,
+ 'new purpose',
+ function(data) {
+ assert.equal(data.id.length > 0, true);
+ assert.equal(data.purpose, 'new purpose');
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('updateChannelNotifyProps', function(done) {
+ TestHelper.initBasic(() => {
+ var props = {};
+ props.channel_id = TestHelper.basicChannel().id;
+ props.user_id = TestHelper.basicUser().id;
+ props.desktop = 'all';
+ TestHelper.basicClient().updateChannelNotifyProps(
+ props,
+ function(data) {
+ assert.equal(data.desktop, 'all');
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('leaveChannel', function(done) {
+ TestHelper.initBasic(() => {
+ var channel = TestHelper.basicChannel();
+ TestHelper.basicClient().leaveChannel(
+ channel.id,
+ function(data) {
+ assert.equal(data.id, channel.id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('joinChannel', function(done) {
+ TestHelper.initBasic(() => {
+ var channel = TestHelper.basicChannel();
+ TestHelper.basicClient().leaveChannel(
+ channel.id,
+ function() {
+ TestHelper.basicClient().joinChannel(
+ channel.id,
+ function(data) {
+ assert.equal(data.id, channel.id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('deleteChannel', function(done) {
+ TestHelper.initBasic(() => {
+ var channel = TestHelper.basicChannel();
+ TestHelper.basicClient().deleteChannel(
+ channel.id,
+ function(data) {
+ assert.equal(data.id, channel.id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('updateLastViewedAt', function(done) {
+ TestHelper.initBasic(() => {
+ var channel = TestHelper.basicChannel();
+ TestHelper.basicClient().updateLastViewedAt(
+ channel.id,
+ function(data) {
+ assert.equal(data.id, channel.id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getChannels', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getChannels(
+ function(data) {
+ assert.equal(data.channels.length, 3);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getChannel', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getChannel(
+ TestHelper.basicChannel().id,
+ function(data) {
+ assert.equal(TestHelper.basicChannel().id, data.channel.id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getMoreChannels', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getMoreChannels(
+ function(data) {
+ assert.equal(data.channels.length, 0);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getChannelCounts', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getChannelCounts(
+ function(data) {
+ assert.equal(data.counts[TestHelper.basicChannel().id], 1);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getChannelExtraInfo', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getChannelExtraInfo(
+ TestHelper.basicChannel().id,
+ 5,
+ function(data) {
+ assert.equal(data.member_count, 1);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('addChannelMember', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().createUser(
+ TestHelper.fakeUser(),
+ function(user2) {
+ TestHelper.basicClient().addUserToTeam(
+ user2.id,
+ function() {
+ TestHelper.basicClient().addChannelMember(
+ TestHelper.basicChannel().id,
+ user2.id,
+ function(data) {
+ assert.equal(data.channel_id.length > 0, true);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('removeChannelMember', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().removeChannelMember(
+ TestHelper.basicChannel().id,
+ TestHelper.basicUser().id,
+ function(data) {
+ assert.equal(data.channel_id.length > 0, true);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+});
+
diff --git a/webapp/tests/client_command.test.jsx b/webapp/tests/client_command.test.jsx
new file mode 100644
index 000000000..f7f0d2b25
--- /dev/null
+++ b/webapp/tests/client_command.test.jsx
@@ -0,0 +1,123 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+/* eslint-disable no-console */
+/* eslint-disable global-require */
+/* eslint-disable func-names */
+/* eslint-disable prefer-arrow-callback */
+/* eslint-disable no-magic-numbers */
+/* eslint-disable no-unreachable */
+
+import assert from 'assert';
+import TestHelper from './test_helper.jsx';
+
+describe('Client.Commands', function() {
+ this.timeout(100000);
+
+ it('listCommands', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().listCommands(
+ function(data) {
+ assert.equal(data.length > 0, true);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('listTeamCommands', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().listTeamCommands(
+ function() {
+ done(new Error('cmds not enabled'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.command.disabled.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('executeCommand', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().executeCommand(
+ TestHelper.basicChannel().id,
+ '/shrug',
+ null,
+ function(data) {
+ assert.equal(data.response_type, 'in_channel');
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('addCommand', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+
+ var cmd = {};
+ cmd.url = 'http://www.gonowhere.com';
+ cmd.trigger = '/hello';
+ cmd.method = 'P';
+ cmd.username = '';
+ cmd.icon_url = '';
+ cmd.auto_complete = false;
+ cmd.auto_complete_desc = '';
+ cmd.auto_complete_hint = '';
+ cmd.display_name = 'Unit Test';
+
+ TestHelper.basicClient().addCommand(
+ cmd,
+ function() {
+ done(new Error('cmds not enabled'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.command.disabled.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('deleteCommand', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().deleteCommand(
+ TestHelper.generateId(),
+ function() {
+ done(new Error('cmds not enabled'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.command.disabled.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('regenCommandToken', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().regenCommandToken(
+ TestHelper.generateId(),
+ function() {
+ done(new Error('cmds not enabled'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.command.disabled.app_error');
+ done();
+ }
+ );
+ });
+ });
+});
+
diff --git a/webapp/tests/client_general.test.jsx b/webapp/tests/client_general.test.jsx
new file mode 100644
index 000000000..870c11257
--- /dev/null
+++ b/webapp/tests/client_general.test.jsx
@@ -0,0 +1,333 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+/* eslint-disable no-console */
+/* eslint-disable global-require */
+/* eslint-disable func-names */
+/* eslint-disable prefer-arrow-callback */
+/* eslint-disable no-magic-numbers */
+/* eslint-disable no-unreachable */
+
+var assert = require('assert');
+import TestHelper from './test_helper.jsx';
+
+describe('Client.General', function() {
+ this.timeout(10000);
+
+ it('Admin.getClientConfig', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getClientConfig(
+ function(data) {
+ assert.equal(data.SiteName, 'Mattermost');
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('Admin.getComplianceReports', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().getComplianceReports(
+ function() {
+ done(new Error('should need system admin permissions'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.context.system_permissions.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('Admin.saveComplianceReports', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+
+ var job = {};
+ job.desc = 'desc';
+ job.emails = '';
+ job.keywords = 'test';
+ job.start_at = new Date();
+ job.end_at = new Date();
+
+ TestHelper.basicClient().saveComplianceReports(
+ job,
+ function() {
+ done(new Error('should need system admin permissions'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.context.system_permissions.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('Admin.getLogs', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().getLogs(
+ function() {
+ done(new Error('should need system admin permissions'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.context.system_permissions.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('Admin.getServerAudits', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().getServerAudits(
+ function() {
+ done(new Error('should need system admin permissions'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.context.system_permissions.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('Admin.getConfig', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().getConfig(
+ function() {
+ done(new Error('should need system admin permissions'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.context.system_permissions.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('Admin.getAnalytics', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().getAnalytics(
+ 'standard',
+ null,
+ function() {
+ done(new Error('should need system admin permissions'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.context.system_permissions.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('Admin.getTeamAnalytics', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().getTeamAnalytics(
+ TestHelper.basicTeam().id,
+ 'standard',
+ function() {
+ done(new Error('should need system admin permissions'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.context.system_permissions.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('Admin.saveConfig', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ var config = {};
+ config.site_name = 'test';
+
+ TestHelper.basicClient().saveConfig(
+ config,
+ function() {
+ done(new Error('should need system admin permissions'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.context.system_permissions.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('Admin.testEmail', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ var config = {};
+ config.site_name = 'test';
+
+ TestHelper.basicClient().testEmail(
+ config,
+ function() {
+ done(new Error('should need system admin permissions'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.context.system_permissions.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('Admin.logClientError', function(done) {
+ TestHelper.initBasic(() => {
+ var config = {};
+ config.site_name = 'test';
+ TestHelper.basicClient().logClientError('this is a test');
+ done();
+ });
+ });
+
+ it('Admin.adminResetMfa', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+
+ TestHelper.basicClient().adminResetMfa(
+ TestHelper.basicUser().id,
+ function() {
+ done(new Error('should need a license'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.context.permissions.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('Admin.adminResetPassword', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ var user = TestHelper.basicUser();
+
+ TestHelper.basicClient().resetPassword(
+ user.id,
+ 'new_password',
+ function() {
+ throw Error('shouldnt work');
+ },
+ function(err) {
+ // this should fail since you're not a system admin
+ assert.equal(err.id, 'api.context.invalid_param.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('License.getClientLicenceConfig', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getClientLicenceConfig(
+ function(data) {
+ assert.equal(data.IsLicensed, 'false');
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('License.removeLicenseFile', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().removeLicenseFile(
+ function() {
+ done(new Error('not enabled'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.context.permissions.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ /*it('License.uploadLicenseFile', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().uploadLicenseFile(
+ 'form data',
+ function() {
+ done(new Error('not enabled'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.context.permissions.app_error');
+ done();
+ }
+ );
+ });
+ });*/
+
+ // TODO XXX FIX ME - this test depends on make dist
+
+ // it('General.getTranslations', function(done) {
+ // TestHelper.initBasic(() => {
+ // TestHelper.basicClient().getTranslations(
+ // 'http://localhost:8065/static/i18n/es.json',
+ // function(data) {
+ // assert.equal(data['login.or'], 'o');
+ // done();
+ // },
+ // function(err) {
+ // done(new Error(err.message));
+ // }
+ // );
+ // });
+ // });
+
+ it('File.getFileInfo', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+
+ TestHelper.basicClient().getFileInfo(
+ `/${TestHelper.basicChannel().id}/${TestHelper.basicUser().id}/filename.txt`,
+ function(data) {
+ assert.equal(data.filename, 'filename.txt');
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('File.getPublicLink', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ var data = {};
+ data.channel_id = TestHelper.basicChannel().id;
+ data.user_id = TestHelper.basicUser().id;
+ data.filename = `/${TestHelper.basicChannel().id}/${TestHelper.basicUser().id}/filename.txt`;
+
+ TestHelper.basicClient().getPublicLink(
+ data,
+ function() {
+ done(new Error('not enabled'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.file.get_public_link.disabled.app_error');
+ done();
+ }
+ );
+ });
+ });
+});
+
diff --git a/webapp/tests/client_hooks.test.jsx b/webapp/tests/client_hooks.test.jsx
new file mode 100644
index 000000000..0cad22153
--- /dev/null
+++ b/webapp/tests/client_hooks.test.jsx
@@ -0,0 +1,139 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+/* eslint-disable no-console */
+/* eslint-disable global-require */
+/* eslint-disable func-names */
+/* eslint-disable prefer-arrow-callback */
+/* eslint-disable no-magic-numbers */
+/* eslint-disable no-unreachable */
+
+import assert from 'assert';
+import TestHelper from './test_helper.jsx';
+
+describe('Client.Hooks', function() {
+ this.timeout(100000);
+
+ it('addIncomingHook', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+
+ var hook = {};
+ hook.channel_id = TestHelper.basicChannel().id;
+ hook.description = 'desc';
+ hook.display_name = 'Unit Test';
+
+ TestHelper.basicClient().addIncomingHook(
+ hook,
+ function() {
+ done(new Error('hooks not enabled'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.webhook.create_incoming.disabled.app_errror');
+ done();
+ }
+ );
+ });
+ });
+
+ it('deleteIncomingHook', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().deleteIncomingHook(
+ TestHelper.generateId(),
+ function() {
+ done(new Error('hooks not enabled'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.webhook.delete_incoming.disabled.app_errror');
+ done();
+ }
+ );
+ });
+ });
+
+ it('listIncomingHooks', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().listIncomingHooks(
+ function() {
+ done(new Error('hooks not enabled'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.webhook.get_incoming.disabled.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('addOutgoingHook', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+
+ var hook = {};
+ hook.channel_id = TestHelper.basicChannel().id;
+ hook.description = 'desc';
+ hook.display_name = 'Unit Test';
+
+ TestHelper.basicClient().addOutgoingHook(
+ hook,
+ function() {
+ done(new Error('hooks not enabled'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.webhook.create_outgoing.disabled.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('deleteOutgoingHook', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().deleteOutgoingHook(
+ TestHelper.generateId(),
+ function() {
+ done(new Error('hooks not enabled'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.webhook.delete_outgoing.disabled.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('listOutgoingHooks', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().listOutgoingHooks(
+ function() {
+ done(new Error('hooks not enabled'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.webhook.get_outgoing.disabled.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('regenOutgoingHookToken', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().regenOutgoingHookToken(
+ TestHelper.generateId(),
+ function() {
+ done(new Error('hooks not enabled'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.webhook.regen_outgoing_token.disabled.app_error');
+ done();
+ }
+ );
+ });
+ });
+});
+
diff --git a/webapp/tests/client_oauth.test.jsx b/webapp/tests/client_oauth.test.jsx
new file mode 100644
index 000000000..df2fc665b
--- /dev/null
+++ b/webapp/tests/client_oauth.test.jsx
@@ -0,0 +1,60 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+/* eslint-disable no-console */
+/* eslint-disable global-require */
+/* eslint-disable func-names */
+/* eslint-disable prefer-arrow-callback */
+/* eslint-disable no-magic-numbers */
+/* eslint-disable no-unreachable */
+
+import assert from 'assert';
+import TestHelper from './test_helper.jsx';
+
+describe('Client.OAuth', function() {
+ this.timeout(100000);
+
+ it('registerOAuthApp', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+
+ var app = {};
+ app.name = 'test';
+ app.homepage = 'homepage';
+ app.description = 'desc';
+ app.callback_urls = '';
+
+ TestHelper.basicClient().registerOAuthApp(
+ app,
+ function() {
+ done(new Error('not enabled'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.oauth.register_oauth_app.turn_off.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('allowOAuth2', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+
+ TestHelper.basicClient().allowOAuth2(
+ 'GET',
+ '123456',
+ 'http://nowhere.com',
+ 'state',
+ 'scope',
+ function() {
+ done(new Error('not enabled'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.oauth.allow_oauth.turn_off.app_error');
+ done();
+ }
+ );
+ });
+ });
+});
diff --git a/webapp/tests/client_post.test.jsx b/webapp/tests/client_post.test.jsx
new file mode 100644
index 000000000..db48e4000
--- /dev/null
+++ b/webapp/tests/client_post.test.jsx
@@ -0,0 +1,207 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+/* eslint-disable no-console */
+/* eslint-disable global-require */
+/* eslint-disable func-names */
+/* eslint-disable prefer-arrow-callback */
+/* eslint-disable no-magic-numbers */
+/* eslint-disable no-unreachable */
+
+import assert from 'assert';
+import TestHelper from './test_helper.jsx';
+
+describe('Client.Posts', function() {
+ this.timeout(100000);
+
+ it('createPost', function(done) {
+ TestHelper.initBasic(() => {
+ var post = TestHelper.fakePost();
+ post.channel_id = TestHelper.basicChannel().id;
+
+ TestHelper.basicClient().createPost(
+ post,
+ function(data) {
+ assert.equal(data.id.length > 0, true);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getPostById', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getPostById(
+ TestHelper.basicPost().id,
+ function(data) {
+ assert.equal(data.order[0], TestHelper.basicPost().id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getPost', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getPost(
+ TestHelper.basicChannel().id,
+ TestHelper.basicPost().id,
+ function(data) {
+ assert.equal(data.order[0], TestHelper.basicPost().id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('updatePost', function(done) {
+ TestHelper.initBasic(() => {
+ var post = TestHelper.basicPost();
+ post.message = 'new message';
+ post.channel_id = TestHelper.basicChannel().id;
+
+ TestHelper.basicClient().updatePost(
+ post,
+ function(data) {
+ assert.equal(data.id.length > 0, true);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('deletePost', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().deletePost(
+ TestHelper.basicChannel().id,
+ TestHelper.basicPost().id,
+ function(data) {
+ assert.equal(data.id, TestHelper.basicPost().id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('searchPost', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().search(
+ 'unit test',
+ function(data) {
+ assert.equal(data.order[0], TestHelper.basicPost().id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getPostsPage', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getPostsPage(
+ TestHelper.basicChannel().id,
+ 0,
+ 10,
+ function(data) {
+ assert.equal(data.order[0], TestHelper.basicPost().id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getPosts', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getPosts(
+ TestHelper.basicChannel().id,
+ 0,
+ function(data) {
+ assert.equal(data.order[0], TestHelper.basicPost().id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getPostsBefore', function(done) {
+ TestHelper.initBasic(() => {
+ var post = TestHelper.fakePost();
+ post.channel_id = TestHelper.basicChannel().id;
+
+ TestHelper.basicClient().createPost(
+ post,
+ function(rpost) {
+ TestHelper.basicClient().getPostsBefore(
+ TestHelper.basicChannel().id,
+ rpost.id,
+ 0,
+ 10,
+ function(data) {
+ assert.equal(data.order[0], TestHelper.basicPost().id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getPostsAfter', function(done) {
+ TestHelper.initBasic(() => {
+ var post = TestHelper.fakePost();
+ post.channel_id = TestHelper.basicChannel().id;
+
+ TestHelper.basicClient().createPost(
+ post,
+ function(rpost) {
+ TestHelper.basicClient().getPostsAfter(
+ TestHelper.basicChannel().id,
+ TestHelper.basicPost().id,
+ 0,
+ 10,
+ function(data) {
+ assert.equal(data.order[0], rpost.id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+});
+
diff --git a/webapp/tests/client_preferences.test.jsx b/webapp/tests/client_preferences.test.jsx
new file mode 100644
index 000000000..987728704
--- /dev/null
+++ b/webapp/tests/client_preferences.test.jsx
@@ -0,0 +1,72 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+/* eslint-disable no-console */
+/* eslint-disable global-require */
+/* eslint-disable func-names */
+/* eslint-disable prefer-arrow-callback */
+/* eslint-disable no-magic-numbers */
+/* eslint-disable no-unreachable */
+
+import assert from 'assert';
+import TestHelper from './test_helper.jsx';
+
+describe('Client.Preferences', function() {
+ this.timeout(100000);
+
+ it('getAllPreferences', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getAllPreferences(
+ function(data) {
+ assert.equal(data[0].category, 'tutorial_step');
+ assert.equal(data[0].user_id, TestHelper.basicUser().id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('savePreferences', function(done) {
+ TestHelper.initBasic(() => {
+ var perf = {};
+ perf.user_id = TestHelper.basicUser().id;
+ perf.category = 'test';
+ perf.name = 'name';
+ perf.value = 'value';
+
+ var perfs = [];
+ perfs.push(perf);
+
+ TestHelper.basicClient().savePreferences(
+ perfs,
+ function(data) {
+ assert.equal(data, true);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getPreferenceCategory', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getPreferenceCategory(
+ 'tutorial_step',
+ function(data) {
+ assert.equal(data[0].category, 'tutorial_step');
+ assert.equal(data[0].user_id, TestHelper.basicUser().id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+});
+
diff --git a/webapp/tests/client_team.test.jsx b/webapp/tests/client_team.test.jsx
new file mode 100644
index 000000000..e8b71d2f8
--- /dev/null
+++ b/webapp/tests/client_team.test.jsx
@@ -0,0 +1,235 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+/* eslint-disable no-console */
+/* eslint-disable global-require */
+/* eslint-disable func-names */
+/* eslint-disable prefer-arrow-callback */
+/* eslint-disable no-magic-numbers */
+/* eslint-disable no-unreachable */
+
+import assert from 'assert';
+import TestHelper from './test_helper.jsx';
+
+describe('Client.Team', function() {
+ this.timeout(100000);
+
+ it('findTeamByName', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().findTeamByName(
+ TestHelper.basicTeam().name,
+ function(data) {
+ assert.equal(data, true);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('signupTeam', function(done) {
+ var client = TestHelper.createClient();
+ var email = TestHelper.fakeEmail();
+
+ client.signupTeam(
+ email,
+ function(data) {
+ assert.equal(data.email, email);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+
+ it('createTeamFromSignup', function(done) {
+ var client = TestHelper.createClient();
+ var email = TestHelper.fakeEmail();
+
+ client.signupTeam(
+ email,
+ function(data) {
+ var teamSignup = {};
+ teamSignup.invites = [];
+ teamSignup.data = decodeURIComponent(data.follow_link.split('&h=')[0].replace('/signup_team_complete/?d=', ''));
+ teamSignup.hash = decodeURIComponent(data.follow_link.split('&h=')[1]);
+
+ teamSignup.user = TestHelper.fakeUser();
+ teamSignup.team = TestHelper.fakeTeam();
+ teamSignup.team.email = teamSignup.user.email;
+
+ client.createTeamFromSignup(
+ teamSignup,
+ function(data2) {
+ assert.equal(data2.team.id.length > 0, true);
+ assert.equal(data2.user.id.length > 0, true);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+
+ it('createTeam', function(done) {
+ var client = TestHelper.createClient();
+ var team = TestHelper.fakeTeam();
+ client.createTeam(
+ team,
+ function(data) {
+ assert.equal(data.id.length > 0, true);
+ assert.equal(data.name, team.name);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+
+ it('getAllTeams', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getAllTeams(
+ function(data) {
+ assert.equal(data[TestHelper.basicTeam().id].name, TestHelper.basicTeam().name);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getAllTeamListings', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getAllTeamListings(
+ function(data) {
+ console.log(data);
+ assert.equal(data != null, true);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getMyTeam', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getMyTeam(
+ function(data) {
+ assert.equal(data.name, TestHelper.basicTeam().name);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('GetTeamMembers', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getTeamMembers(
+ TestHelper.basicTeam().id,
+ function(data) {
+ assert.equal(data.length > 0, true);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('inviteMembers', function(done) {
+ TestHelper.initBasic(() => {
+ var data = {};
+ data.invites = [];
+ var invite = {};
+ invite.email = TestHelper.fakeEmail();
+ invite.firstName = 'first';
+ invite.lastName = 'last';
+ data.invites.push(invite);
+
+ TestHelper.basicClient().inviteMembers(
+ data,
+ function(dataBack) {
+ assert.equal(dataBack.invites.length, 1);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('updateTeam', function(done) {
+ TestHelper.initBasic(() => {
+ var team = TestHelper.basicTeam();
+ team.display_name = 'test_updated';
+
+ TestHelper.basicClient().updateTeam(
+ team,
+ function(data) {
+ assert.equal(data.display_name, 'test_updated');
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('addUserToTeam', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().createUser(
+ TestHelper.fakeUser(),
+ function(user2) {
+ TestHelper.basicClient().addUserToTeam(
+ user2.id,
+ function(data) {
+ assert.equal(data.user_id, user2.id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getInviteInfo', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getInviteInfo(
+ TestHelper.basicTeam().invite_id,
+ function(data) {
+ assert.equal(data.display_name.length > 0, true);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+});
+
diff --git a/webapp/tests/client_user.test.jsx b/webapp/tests/client_user.test.jsx
new file mode 100644
index 000000000..e0ead2de9
--- /dev/null
+++ b/webapp/tests/client_user.test.jsx
@@ -0,0 +1,550 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+/* eslint-disable no-console */
+/* eslint-disable global-require */
+/* eslint-disable func-names */
+/* eslint-disable prefer-arrow-callback */
+/* eslint-disable no-magic-numbers */
+/* eslint-disable no-unreachable */
+
+import assert from 'assert';
+import TestHelper from './test_helper.jsx';
+
+describe('Client.User', function() {
+ this.timeout(100000);
+
+ it('getMe', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getMe(
+ function(data) {
+ assert.equal(data.id, TestHelper.basicUser().id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getInitialLoad', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getInitialLoad(
+ function(data) {
+ assert.equal(data.user.id.length > 0, true);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('createUser', function(done) {
+ var client = TestHelper.createClient();
+ var user = TestHelper.fakeUser();
+ client.createUser(
+ user,
+ function(data) {
+ assert.equal(data.id.length > 0, true);
+ assert.equal(data.email, user.email);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+
+ it('loginByEmail', function(done) {
+ var client = TestHelper.createClient();
+ var user = TestHelper.fakeUser();
+ client.createUser(
+ user,
+ function() {
+ client.login(
+ user.email,
+ null,
+ user.password,
+ null,
+ function(data) {
+ assert.equal(data.id.length > 0, true);
+ assert.equal(data.email, user.email);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+
+ it('loginByUsername', function(done) {
+ var client = TestHelper.createClient();
+ var user = TestHelper.fakeUser();
+ client.createUser(
+ user,
+ function() {
+ client.login(
+ null,
+ user.username,
+ user.password,
+ null,
+ function(data) {
+ assert.equal(data.id.length > 0, true);
+ assert.equal(data.email, user.email);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+
+ it('loginByLdap', function(done) {
+ var client = TestHelper.createClient();
+ client.enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ var user = TestHelper.fakeUser();
+ client.createUser(
+ user,
+ function() {
+ client.loginByLdap(
+ user.username,
+ user.password,
+ null,
+ function() {
+ done(new Error());
+ },
+ function(err) {
+ assert.equal(err.id, 'api.user.login_ldap.disabled.app_error');
+ done();
+ }
+ );
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+
+ it('updateUser', function(done) {
+ TestHelper.initBasic(() => {
+ var user = TestHelper.basicUser();
+ user.nickname = 'updated';
+
+ TestHelper.basicClient().updateUser(
+ user,
+ function(data) {
+ assert.equal(data.nickname, 'updated');
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('updatePassword', function(done) {
+ TestHelper.initBasic(() => {
+ var user = TestHelper.basicUser();
+
+ TestHelper.basicClient().updatePassword(
+ user.id,
+ user.password,
+ 'update_password',
+ function(data) {
+ assert.equal(data.user_id, user.id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('updateUserNotifyProps', function(done) {
+ TestHelper.initBasic(() => {
+ var user = TestHelper.basicUser();
+
+ var notifyProps = {
+ all: 'true',
+ channel: 'true',
+ desktop: 'all',
+ desktop_sound: 'true',
+ email: 'false',
+ first_name: 'false',
+ mention_keys: '',
+ user_id: user.id
+ };
+
+ TestHelper.basicClient().updateUserNotifyProps(
+ notifyProps,
+ function(data) {
+ assert.equal(data.notify_props.email, 'false');
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('updateRoles', function(done) {
+ TestHelper.initBasic(() => {
+ var user = TestHelper.basicUser();
+
+ TestHelper.basicClient().updateRoles(
+ user.id,
+ '',
+ function(data) {
+ assert.equal(data.roles, '');
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('updateActive', function(done) {
+ TestHelper.initBasic(() => {
+ var user = TestHelper.basicUser();
+
+ TestHelper.basicClient().updateActive(
+ user.id,
+ false,
+ function(data) {
+ assert.equal(data.last_activity_at > 0, true);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('sendPasswordReset', function(done) {
+ TestHelper.initBasic(() => {
+ var user = TestHelper.basicUser();
+
+ TestHelper.basicClient().sendPasswordReset(
+ user.email,
+ function(data) {
+ assert.equal(data.email, user.email);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('resetPassword', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+
+ TestHelper.basicClient().resetPassword(
+ '',
+ 'new_password',
+ function() {
+ throw Error('shouldnt work');
+ },
+ function(err) {
+ // this should fail since you're not a system admin
+ assert.equal(err.id, 'api.context.invalid_param.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('emailToOAuth', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ var user = TestHelper.basicUser();
+
+ TestHelper.basicClient().emailToOAuth(
+ user.email,
+ 'new_password',
+ 'gitlab',
+ function() {
+ throw Error('shouldnt work');
+ },
+ function(err) {
+ // this should fail since you're not a system admin
+ assert.equal(err.id, 'api.user.check_user_password.invalid.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('oauthToEmail', function(done) {
+ TestHelper.initBasic(() => {
+ var user = TestHelper.basicUser();
+
+ TestHelper.basicClient().oauthToEmail(
+ user.email,
+ 'new_password',
+ function(data) {
+ assert.equal(data.follow_link.length > 0, true);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('emailToLdap', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ var user = TestHelper.basicUser();
+
+ TestHelper.basicClient().emailToLdap(
+ user.email,
+ user.password,
+ 'unknown_id',
+ 'unknown_pwd',
+ function() {
+ throw Error('shouldnt work');
+ },
+ function(err) {
+ assert.equal(err.id, 'ent.ldap.do_login.licence_disable.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('ldapToEmail', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ var user = TestHelper.basicUser();
+
+ TestHelper.basicClient().ldapToEmail(
+ user.email,
+ 'new_password',
+ 'new_password',
+ function() {
+ throw Error('shouldnt work');
+ },
+ function(err) {
+ assert.equal(err.id, 'api.user.ldap_to_email.not_ldap_account.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('logout', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().logout(
+ function(data) {
+ assert.equal(data.user_id, TestHelper.basicUser().id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('checkMfa', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().checkMfa(
+ 'email',
+ TestHelper.generateId(),
+ function(data) {
+ assert.equal(data.mfa_required, 'false');
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getSessions', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getSessions(
+ TestHelper.basicUser().id,
+ function(data) {
+ assert.equal(data[0].user_id, TestHelper.basicUser().id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('revokeSession', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getSessions(
+ TestHelper.basicUser().id,
+ function(sessions) {
+ TestHelper.basicClient().revokeSession(
+ sessions[0].id,
+ function(data) {
+ assert.equal(data.id, sessions[0].id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getAudits', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getAudits(
+ TestHelper.basicUser().id,
+ function(data) {
+ assert.equal(data[0].user_id, TestHelper.basicUser().id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getDirectProfiles', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getDirectProfiles(
+ function(data) {
+ assert.equal(Object.keys(data).length === 0, true);
+ done();
+ },
+ function(err) {
+ done(new Error(err.getDirectProfiles));
+ }
+ );
+ });
+ });
+
+ it('getProfiles', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getProfiles(
+ function(data) {
+ assert.equal(data[TestHelper.basicUser().id].id, TestHelper.basicUser().id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getProfilesForTeam', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getProfilesForTeam(
+ TestHelper.basicTeam().id,
+ function(data) {
+ assert.equal(data[TestHelper.basicUser().id].id, TestHelper.basicUser().id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('getStatuses', function(done) {
+ TestHelper.initBasic(() => {
+ var ids = [];
+ ids.push(TestHelper.basicUser().id);
+
+ TestHelper.basicClient().getStatuses(
+ ids,
+ function(data) {
+ assert.equal(data[TestHelper.basicUser().id], 'online');
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('verifyEmail', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().verifyEmail(
+ 'junk',
+ 'junk',
+ function() {
+ done(new Error('should be invalid'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.context.invalid_param.app_error');
+ done();
+ }
+ );
+ });
+ });
+
+ it('resendVerification', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().resendVerification(
+ TestHelper.basicUser().email,
+ function() {
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
+ it('updateMfa', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+ TestHelper.basicClient().updateMfa(
+ 'junk',
+ true,
+ function() {
+ done(new Error('not enabled'));
+ },
+ function(err) {
+ assert.equal(err.id, 'ent.mfa.license_disable.app_error');
+ done();
+ }
+ );
+ });
+ });
+});
diff --git a/webapp/tests/spinner_button.test.jsx b/webapp/tests/spinner_button.test.jsx
new file mode 100644
index 000000000..0e282e0ee
--- /dev/null
+++ b/webapp/tests/spinner_button.test.jsx
@@ -0,0 +1,24 @@
+/* eslint-disable no-console */
+/* eslint-disable global-require */
+/* eslint-disable func-names */
+/* eslint-disable prefer-arrow-callback */
+/* eslint-disable no-magic-numbers */
+/* eslint-disable no-unreachable */
+
+var jsdom = require('mocha-jsdom');
+var assert = require('assert');
+import TestUtils from 'react-addons-test-utils';
+import SpinnerButton from '../components/spinner_button.jsx';
+import React from 'react';
+
+describe('SpinnerButton', function() {
+ jsdom();
+
+ it('check props', function() {
+ const spinner = TestUtils.renderIntoDocument(
+ <SpinnerButton spinning={false}/>
+ );
+
+ assert.equal(spinner.props.spinning, false, 'should start in the default false state');
+ });
+}); \ No newline at end of file
diff --git a/webapp/tests/test_helper.jsx b/webapp/tests/test_helper.jsx
new file mode 100644
index 000000000..385279360
--- /dev/null
+++ b/webapp/tests/test_helper.jsx
@@ -0,0 +1,183 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+/* eslint-disable no-console */
+/* eslint-disable global-require */
+/* eslint-disable func-names */
+/* eslint-disable prefer-arrow-callback */
+/* eslint-disable no-magic-numbers */
+/* eslint-disable no-unreachable */
+/* eslint-disable new-cap */
+
+import Client from '../client/client.jsx';
+import jqd from 'jquery-deferred';
+
+class TestHelperClass {
+ basicClient = () => {
+ return this.basicc;
+ }
+
+ basicTeam = () => {
+ return this.basict;
+ }
+
+ basicUser = () => {
+ return this.basicu;
+ }
+
+ basicChannel = () => {
+ return this.basicch;
+ }
+
+ basicPost = () => {
+ return this.basicp;
+ }
+
+ generateId = () => {
+ // implementation taken from http://stackoverflow.com/a/2117523
+ var id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
+
+ id = id.replace(/[xy]/g, function replaceRandom(c) {
+ var r = Math.floor(Math.random() * 16);
+
+ var v;
+ if (c === 'x') {
+ v = r;
+ } else {
+ v = r & 0x3 | 0x8;
+ }
+
+ return v.toString(16);
+ });
+
+ return 'uid' + id;
+ }
+
+ createClient() {
+ var c = new Client();
+ c.setUrl('http://localhost:8065');
+ c.useHeaderToken();
+ c.enableLogErrorsToConsole(true);
+ return c;
+ }
+
+ fakeEmail = () => {
+ return 'success' + this.generateId() + '@simulator.amazonses.com';
+ }
+
+ fakeUser = () => {
+ var user = {};
+ user.email = this.fakeEmail();
+ user.allow_marketing = true;
+ user.password = 'password1';
+ user.username = this.generateId();
+ return user;
+ }
+
+ fakeTeam = () => {
+ var team = {};
+ team.name = this.generateId();
+ team.display_name = `Unit Test ${team.name}`;
+ team.type = 'O';
+ team.email = this.fakeEmail();
+ team.allowed_domains = '';
+ return team;
+ }
+
+ fakeChannel = () => {
+ var channel = {};
+ channel.name = this.generateId();
+ channel.display_name = `Unit Test ${channel.name}`;
+ channel.type = 'O'; // open channel
+ return channel;
+ }
+
+ fakePost = () => {
+ var post = {};
+ post.message = `Unit Test ${this.generateId()}`;
+ return post;
+ }
+
+ initBasic = (callback) => {
+ this.basicc = this.createClient();
+
+ var d1 = jqd.Deferred();
+ var email = this.fakeEmail();
+ var outer = this; // eslint-disable-line consistent-this
+
+ this.basicClient().signupTeam(
+ email,
+ function(rsignUp) {
+ var teamSignup = {};
+ teamSignup.invites = [];
+ teamSignup.data = decodeURIComponent(rsignUp.follow_link.split('&h=')[0].replace('/signup_team_complete/?d=', ''));
+ teamSignup.hash = decodeURIComponent(rsignUp.follow_link.split('&h=')[1]);
+
+ teamSignup.user = outer.fakeUser();
+ teamSignup.team = outer.fakeTeam();
+ teamSignup.team.email = email;
+ teamSignup.user.email = email;
+ var password = teamSignup.user.password;
+
+ outer.basicClient().createTeamFromSignup(
+ teamSignup,
+ function(rteamSignup) {
+ outer.basict = rteamSignup.team;
+ outer.basicu = rteamSignup.user;
+ outer.basicu.password = password;
+ outer.basicClient().setTeamId(outer.basict.id);
+ outer.basicClient().login(
+ rteamSignup.user.email,
+ null,
+ password,
+ null,
+ function() {
+ outer.basicClient().useHeaderToken();
+ var channel = outer.fakeChannel();
+ channel.team_id = outer.basicTeam().id;
+ outer.basicClient().createChannel(
+ channel,
+ function(rchannel) {
+ outer.basicch = rchannel;
+ var post = outer.fakePost();
+ post.channel_id = rchannel.id;
+
+ outer.basicClient().createPost(
+ post,
+ function(rpost) {
+ outer.basicp = rpost;
+ d1.resolve();
+ },
+ function(err) {
+ throw err;
+ }
+ );
+ },
+ function(err) {
+ throw err;
+ }
+ );
+ },
+ function(err) {
+ throw err;
+ }
+ );
+ },
+ function(err) {
+ throw err;
+ }
+ );
+ },
+ function(err) {
+ throw err;
+ }
+ );
+
+ jqd.when(d1).done(() => {
+ callback();
+ });
+ }
+}
+
+var TestHelper = new TestHelperClass();
+export default TestHelper;
diff --git a/webapp/utils/async_client.jsx b/webapp/utils/async_client.jsx
index 80a08dc21..189b159e8 100644
--- a/webapp/utils/async_client.jsx
+++ b/webapp/utils/async_client.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import $ from 'jquery';
-import * as client from './client.jsx';
+import Client from './web_client.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import BrowserStore from 'stores/browser_store.jsx';
@@ -11,6 +11,7 @@ import PreferenceStore from 'stores/preference_store.jsx';
import PostStore from 'stores/post_store.jsx';
import UserStore from 'stores/user_store.jsx';
import * as utils from './utils.jsx';
+import ErrorStore from 'stores/error_store.jsx';
import Constants from './constants.jsx';
const ActionTypes = Constants.ActionTypes;
@@ -19,6 +20,8 @@ const StatTypes = Constants.StatTypes;
// Used to track in progress async calls
const callTracker = {};
+const ASYNC_CLIENT_TIMEOUT = 5000;
+
export function dispatchError(err, method) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_ERROR,
@@ -36,7 +39,7 @@ function isCallInProgress(callName) {
return false;
}
- if (utils.getTimestamp() - callTracker[callName] > 5000) {
+ if (utils.getTimestamp() - callTracker[callName] > ASYNC_CLIENT_TIMEOUT) {
//console.log('AsyncClient call ' + callName + ' expired after more than 5 seconds');
return false;
}
@@ -51,16 +54,12 @@ export function getChannels(checkVersion) {
callTracker.getChannels = utils.getTimestamp();
- return client.getChannels(
- (data, textStatus, xhr) => {
+ return Client.getChannels(
+ (data) => {
callTracker.getChannels = 0;
- if (xhr.status === 304 || !data) {
- return;
- }
-
if (checkVersion) {
- var serverVersion = xhr.getResponseHeader('X-Version-ID');
+ var serverVersion = Client.getServerVersion();
if (serverVersion !== BrowserStore.getLastServerVersion()) {
if (!BrowserStore.getLastServerVersion() || BrowserStore.getLastServerVersion() === '') {
@@ -93,14 +92,10 @@ export function getChannel(id) {
callTracker['getChannel' + id] = utils.getTimestamp();
- client.getChannel(id,
- (data, textStatus, xhr) => {
+ Client.getChannel(id,
+ (data) => {
callTracker['getChannel' + id] = 0;
- if (xhr.status === 304 || !data) {
- return;
- }
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_CHANNEL,
channel: data.channel,
@@ -131,13 +126,17 @@ export function updateLastViewedAt(id) {
}
callTracker[`updateLastViewed${channelId}`] = utils.getTimestamp();
- client.updateLastViewedAt(
+ Client.updateLastViewedAt(
channelId,
() => {
callTracker.updateLastViewed = 0;
+ ErrorStore.clearLastError();
+ ErrorStore.emitChange();
},
(err) => {
callTracker.updateLastViewed = 0;
+ var count = ErrorStore.getConnectionErrorCount();
+ ErrorStore.setConnectionErrorCount(count + 1);
dispatchError(err, 'updateLastViewedAt');
}
);
@@ -150,21 +149,17 @@ export function getMoreChannels(force) {
if (ChannelStore.getMoreAll().loading || force) {
callTracker.getMoreChannels = utils.getTimestamp();
- client.getMoreChannels(
- function getMoreChannelsSuccess(data, textStatus, xhr) {
+ Client.getMoreChannels(
+ (data) => {
callTracker.getMoreChannels = 0;
- if (xhr.status === 304 || !data) {
- return;
- }
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_MORE_CHANNELS,
channels: data.channels,
members: data.members
});
},
- function getMoreChannelsFailure(err) {
+ (err) => {
callTracker.getMoreChannels = 0;
dispatchError(err, 'getMoreChannels');
}
@@ -187,16 +182,12 @@ export function getChannelExtraInfo(id, memberLimit) {
callTracker['getChannelExtraInfo_' + channelId] = utils.getTimestamp();
- client.getChannelExtraInfo(
+ Client.getChannelExtraInfo(
channelId,
memberLimit,
- (data, textStatus, xhr) => {
+ (data) => {
callTracker['getChannelExtraInfo_' + channelId] = 0;
- if (xhr.status === 304 || !data) {
- return;
- }
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_CHANNEL_EXTRA_INFO,
extra_info: data
@@ -210,53 +201,90 @@ export function getChannelExtraInfo(id, memberLimit) {
}
}
+export function getTeamMembers(teamId) {
+ if (isCallInProgress('getTeamMembers')) {
+ return;
+ }
+
+ callTracker.getTeamMembers = utils.getTimestamp();
+ Client.getTeamMembers(
+ teamId,
+ (data) => {
+ callTracker.getTeamMembers = 0;
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_MEMBERS_FOR_TEAM,
+ team_members: data
+ });
+ },
+ (err) => {
+ callTracker.getTeamMembers = 0;
+ dispatchError(err, 'getTeamMembers');
+ }
+ );
+}
+
export function getProfiles() {
if (isCallInProgress('getProfiles')) {
return;
}
callTracker.getProfiles = utils.getTimestamp();
- client.getProfiles(
- function getProfilesSuccess(data, textStatus, xhr) {
+ Client.getProfiles(
+ (data) => {
callTracker.getProfiles = 0;
- if (xhr.status === 304 || !data) {
- return;
- }
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES,
profiles: data
});
},
- function getProfilesFailure(err) {
+ (err) => {
callTracker.getProfiles = 0;
dispatchError(err, 'getProfiles');
}
);
}
+export function getDirectProfiles() {
+ if (isCallInProgress('getDirectProfiles')) {
+ return;
+ }
+
+ callTracker.getDirectProfiles = utils.getTimestamp();
+ Client.getDirectProfiles(
+ (data) => {
+ callTracker.getDirectProfiles = 0;
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_DIRECT_PROFILES,
+ profiles: data
+ });
+ },
+ (err) => {
+ callTracker.getDirectProfiles = 0;
+ dispatchError(err, 'getDirectProfiles');
+ }
+ );
+}
+
export function getSessions() {
if (isCallInProgress('getSessions')) {
return;
}
callTracker.getSessions = utils.getTimestamp();
- client.getSessions(
+ Client.getSessions(
UserStore.getCurrentId(),
- function getSessionsSuccess(data, textStatus, xhr) {
+ (data) => {
callTracker.getSessions = 0;
- if (xhr.status === 304 || !data) {
- return;
- }
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SESSIONS,
sessions: data
});
},
- function getSessionsFailure(err) {
+ (err) => {
callTracker.getSessions = 0;
dispatchError(err, 'getSessions');
}
@@ -269,21 +297,17 @@ export function getAudits() {
}
callTracker.getAudits = utils.getTimestamp();
- client.getAudits(
+ Client.getAudits(
UserStore.getCurrentId(),
- function getAuditsSuccess(data, textStatus, xhr) {
+ (data) => {
callTracker.getAudits = 0;
- if (xhr.status === 304 || !data) {
- return;
- }
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_AUDITS,
audits: data
});
},
- function getAuditsFailure(err) {
+ (err) => {
callTracker.getAudits = 0;
dispatchError(err, 'getAudits');
}
@@ -296,14 +320,10 @@ export function getLogs() {
}
callTracker.getLogs = utils.getTimestamp();
- client.getLogs(
- (data, textStatus, xhr) => {
+ Client.getLogs(
+ (data) => {
callTracker.getLogs = 0;
- if (xhr.status === 304 || !data) {
- return;
- }
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_LOGS,
logs: data
@@ -322,14 +342,10 @@ export function getServerAudits() {
}
callTracker.getServerAudits = utils.getTimestamp();
- client.getServerAudits(
- (data, textStatus, xhr) => {
+ Client.getServerAudits(
+ (data) => {
callTracker.getServerAudits = 0;
- if (xhr.status === 304 || !data) {
- return;
- }
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SERVER_AUDITS,
audits: data
@@ -348,14 +364,10 @@ export function getComplianceReports() {
}
callTracker.getComplianceReports = utils.getTimestamp();
- client.getComplianceReports(
- (data, textStatus, xhr) => {
+ Client.getComplianceReports(
+ (data) => {
callTracker.getComplianceReports = 0;
- if (xhr.status === 304 || !data) {
- return;
- }
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SERVER_COMPLIANCE_REPORTS,
complianceReports: data
@@ -374,14 +386,10 @@ export function getConfig() {
}
callTracker.getConfig = utils.getTimestamp();
- client.getConfig(
- (data, textStatus, xhr) => {
+ Client.getConfig(
+ (data) => {
callTracker.getConfig = 0;
- if (xhr.status === 304 || !data) {
- return;
- }
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_CONFIG,
config: data
@@ -400,14 +408,10 @@ export function getAllTeams() {
}
callTracker.getAllTeams = utils.getTimestamp();
- client.getAllTeams(
- (data, textStatus, xhr) => {
+ Client.getAllTeams(
+ (data) => {
callTracker.getAllTeams = 0;
- if (xhr.status === 304 || !data) {
- return;
- }
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_ALL_TEAMS,
teams: data
@@ -420,27 +424,45 @@ export function getAllTeams() {
);
}
+export function getAllTeamListings() {
+ if (isCallInProgress('getAllTeamListings')) {
+ return;
+ }
+
+ callTracker.getAllTeamListings = utils.getTimestamp();
+ Client.getAllTeamListings(
+ (data) => {
+ callTracker.getAllTeamListings = 0;
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_ALL_TEAM_LISTINGS,
+ teams: data
+ });
+ },
+ (err) => {
+ callTracker.getAllTeams = 0;
+ dispatchError(err, 'getAllTeamListings');
+ }
+ );
+}
+
export function search(terms) {
if (isCallInProgress('search_' + String(terms))) {
return;
}
callTracker['search_' + String(terms)] = utils.getTimestamp();
- client.search(
+ Client.search(
terms,
- function searchSuccess(data, textStatus, xhr) {
+ (data) => {
callTracker['search_' + String(terms)] = 0;
- if (xhr.status === 304 || !data) {
- return;
- }
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SEARCH,
results: data
});
},
- function searchFailure(err) {
+ (err) => {
callTracker['search_' + String(terms)] = 0;
dispatchError(err, 'search');
}
@@ -478,15 +500,11 @@ export function getPostsPage(id, maxPosts) {
if (channelId != null) {
callTracker['getPostsPage_' + channelId] = utils.getTimestamp();
- client.getPostsPage(
+ Client.getPostsPage(
channelId,
0,
numPosts,
- (data, textStatus, xhr) => {
- if (xhr.status === 304 || !data) {
- return;
- }
-
+ (data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POSTS,
id: channelId,
@@ -536,14 +554,10 @@ export function getPosts(id) {
callTracker['getPosts_' + channelId] = utils.getTimestamp();
- client.getPosts(
+ Client.getPosts(
channelId,
latestPostTime,
- (data, textStatus, xhr) => {
- if (xhr.status === 304 || !data) {
- return;
- }
-
+ (data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POSTS,
id: channelId,
@@ -573,16 +587,12 @@ export function getPostsBefore(postId, offset, numPost) {
return;
}
- client.getPostsBefore(
+ Client.getPostsBefore(
channelId,
postId,
offset,
numPost,
- (data, textStatus, xhr) => {
- if (xhr.status === 304 || !data) {
- return;
- }
-
+ (data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POSTS,
id: channelId,
@@ -612,16 +622,12 @@ export function getPostsAfter(postId, offset, numPost) {
return;
}
- client.getPostsAfter(
+ Client.getPostsAfter(
channelId,
postId,
offset,
numPost,
- (data, textStatus, xhr) => {
- if (xhr.status === 304 || !data) {
- return;
- }
-
+ (data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POSTS,
id: channelId,
@@ -647,14 +653,10 @@ export function getMe() {
}
callTracker.getMe = utils.getTimestamp();
- return client.getMe(
- (data, textStatus, xhr) => {
+ return Client.getMe(
+ (data) => {
callTracker.getMe = 0;
- if (xhr.status === 304 || !data) {
- return;
- }
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_ME,
me: data
@@ -684,14 +686,10 @@ export function getStatuses() {
}
callTracker.getStatuses = utils.getTimestamp();
- client.getStatuses(teammateIds,
- (data, textStatus, xhr) => {
+ Client.getStatuses(teammateIds,
+ (data) => {
callTracker.getStatuses = 0;
- if (xhr.status === 304 || !data) {
- return;
- }
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_STATUSES,
statuses: data
@@ -710,20 +708,16 @@ export function getMyTeam() {
}
callTracker.getMyTeam = utils.getTimestamp();
- return client.getMyTeam(
- function getMyTeamSuccess(data, textStatus, xhr) {
+ return Client.getMyTeam(
+ (data) => {
callTracker.getMyTeam = 0;
- if (xhr.status === 304 || !data) {
- return;
- }
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_MY_TEAM,
team: data
});
},
- function getMyTeamFailure(err) {
+ (err) => {
callTracker.getMyTeam = 0;
dispatchError(err, 'getMyTeam');
}
@@ -736,14 +730,10 @@ export function getAllPreferences() {
}
callTracker.getAllPreferences = utils.getTimestamp();
- client.getAllPreferences(
- (data, textStatus, xhr) => {
+ Client.getAllPreferences(
+ (data) => {
callTracker.getAllPreferences = 0;
- if (xhr.status === 304 || !data) {
- return;
- }
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PREFERENCES,
preferences: data
@@ -768,15 +758,13 @@ export function savePreference(category, name, value, success, error) {
}
export function savePreferences(preferences, success, error) {
- client.savePreferences(
+ Client.savePreferences(
preferences,
- (data, textStatus, xhr) => {
- if (xhr.status !== 304) {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_PREFERENCES,
- preferences
- });
- }
+ (data) => {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_PREFERENCES,
+ preferences
+ });
if (success) {
success(data);
@@ -793,12 +781,12 @@ export function savePreferences(preferences, success, error) {
}
export function getSuggestedCommands(command, suggestionId, component) {
- client.listCommands(
+ Client.listCommands(
(data) => {
var matches = [];
data.forEach((cmd) => {
if (('/' + cmd.trigger).indexOf(command) === 0) {
- let s = '/' + cmd.trigger;
+ const s = '/' + cmd.trigger;
let hint = '';
if (cmd.auto_complete_hint && cmd.auto_complete_hint.length !== 0) {
hint = cmd.auto_complete_hint;
@@ -842,7 +830,7 @@ export function getFileInfo(filename) {
callTracker[callName] = utils.getTimestamp();
- client.getFileInfo(
+ Client.getFileInfo(
filename,
(data) => {
callTracker[callName] = 0;
@@ -870,7 +858,7 @@ export function getStandardAnalytics(teamId) {
callTracker[callName] = utils.getTimestamp();
- client.getAnalytics(
+ Client.getAnalytics(
'standard',
teamId,
(data) => {
@@ -923,7 +911,7 @@ export function getAdvancedAnalytics(teamId) {
callTracker[callName] = utils.getTimestamp();
- client.getAnalytics(
+ Client.getAnalytics(
'extra_counts',
teamId,
(data) => {
@@ -980,7 +968,7 @@ export function getPostsPerDayAnalytics(teamId) {
callTracker[callName] = utils.getTimestamp();
- client.getAnalytics(
+ Client.getAnalytics(
'post_counts_day',
teamId,
(data) => {
@@ -1014,7 +1002,7 @@ export function getUsersPerDayAnalytics(teamId) {
callTracker[callName] = utils.getTimestamp();
- client.getAnalytics(
+ Client.getAnalytics(
'user_counts_with_posts_day',
teamId,
(data) => {
@@ -1048,7 +1036,7 @@ export function getRecentAndNewUsersAnalytics(teamId) {
callTracker[callName] = utils.getTimestamp();
- client.getProfilesForTeam(
+ Client.getProfilesForTeam(
teamId,
(users) => {
const stats = {};
@@ -1129,7 +1117,7 @@ export function listIncomingHooks() {
callTracker.listIncomingHooks = utils.getTimestamp();
- client.listIncomingHooks(
+ Client.listIncomingHooks(
(data) => {
callTracker.listIncomingHooks = 0;
@@ -1152,7 +1140,7 @@ export function listOutgoingHooks() {
callTracker.listOutgoingHooks = utils.getTimestamp();
- client.listOutgoingHooks(
+ Client.listOutgoingHooks(
(data) => {
callTracker.listOutgoingHooks = 0;
@@ -1169,7 +1157,7 @@ export function listOutgoingHooks() {
}
export function addIncomingHook(hook, success, error) {
- client.addIncomingHook(
+ Client.addIncomingHook(
hook,
(data) => {
AppDispatcher.handleServerAction({
@@ -1192,7 +1180,7 @@ export function addIncomingHook(hook, success, error) {
}
export function addOutgoingHook(hook, success, error) {
- client.addOutgoingHook(
+ Client.addOutgoingHook(
hook,
(data) => {
AppDispatcher.handleServerAction({
@@ -1215,8 +1203,8 @@ export function addOutgoingHook(hook, success, error) {
}
export function deleteIncomingHook(id) {
- client.deleteIncomingHook(
- {id},
+ Client.deleteIncomingHook(
+ id,
() => {
AppDispatcher.handleServerAction({
type: ActionTypes.REMOVED_INCOMING_WEBHOOK,
@@ -1230,8 +1218,8 @@ export function deleteIncomingHook(id) {
}
export function deleteOutgoingHook(id) {
- client.deleteOutgoingHook(
- {id},
+ Client.deleteOutgoingHook(
+ id,
() => {
AppDispatcher.handleServerAction({
type: ActionTypes.REMOVED_OUTGOING_WEBHOOK,
@@ -1245,8 +1233,8 @@ export function deleteOutgoingHook(id) {
}
export function regenOutgoingHookToken(id) {
- client.regenOutgoingHookToken(
- {id},
+ Client.regenOutgoingHookToken(
+ id,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.UPDATED_OUTGOING_WEBHOOK,
@@ -1266,7 +1254,7 @@ export function listTeamCommands() {
callTracker.listTeamCommands = utils.getTimestamp();
- client.listTeamCommands(
+ Client.listTeamCommands(
(data) => {
callTracker.listTeamCommands = 0;
@@ -1283,7 +1271,7 @@ export function listTeamCommands() {
}
export function addCommand(command, success, error) {
- client.addCommand(
+ Client.addCommand(
command,
(data) => {
AppDispatcher.handleServerAction({
@@ -1306,8 +1294,8 @@ export function addCommand(command, success, error) {
}
export function deleteCommand(id) {
- client.deleteCommand(
- {id},
+ Client.deleteCommand(
+ id,
() => {
AppDispatcher.handleServerAction({
type: ActionTypes.REMOVED_COMMAND,
@@ -1321,8 +1309,8 @@ export function deleteCommand(id) {
}
export function regenCommandToken(id) {
- client.regenCommandToken(
- {id},
+ Client.regenCommandToken(
+ id,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.UPDATED_COMMAND,
diff --git a/webapp/utils/channel_intro_messages.jsx b/webapp/utils/channel_intro_messages.jsx
index ddd615581..1d18e26ba 100644
--- a/webapp/utils/channel_intro_messages.jsx
+++ b/webapp/utils/channel_intro_messages.jsx
@@ -9,6 +9,7 @@ import UserProfile from 'components/user_profile.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import Constants from 'utils/constants.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
+import Client from 'utils/web_client.jsx';
import React from 'react';
import {FormattedMessage, FormattedHTMLMessage, FormattedDate} from 'react-intl';
@@ -40,7 +41,7 @@ export function createDMIntroMessage(channel) {
<div className='post-profile-img__container channel-intro-img'>
<img
className='post-profile-img'
- src={'/api/v1/users/' + teammate.id + '/image?time=' + teammate.update_at}
+ src={Client.getUsersRoute() + '/' + teammate.id + '/image?time=' + teammate.update_at}
height='50'
width='50'
/>
diff --git a/webapp/utils/client.jsx b/webapp/utils/client.jsx
deleted file mode 100644
index 687d47da4..000000000
--- a/webapp/utils/client.jsx
+++ /dev/null
@@ -1,1759 +0,0 @@
-// See License.txt for license information.
-
-import BrowserStore from 'stores/browser_store.jsx';
-import $ from 'jquery';
-
-import {browserHistory} from 'react-router';
-
-let translations = {
- connectionError: 'There appears to be a problem with your internet connection.',
- unknownError: 'We received an unexpected status code from the server.'
-};
-
-export function setTranslations(messages) {
- translations = messages;
-}
-
-export function track(category, action, label, property, value) {
- global.window.analytics.track(action, {category, label, property, value});
-}
-
-export function trackPage() {
- global.window.analytics.page();
-}
-
-function handleError(methodName, xhr, status, err) {
- var e = null;
- try {
- e = JSON.parse(xhr.responseText);
- } catch (parseError) {
- e = null;
- }
-
- var msg = '';
-
- if (e) {
- msg = 'method=' + methodName + ' msg=' + e.message + ' detail=' + e.detailed_error + ' rid=' + e.request_id;
- } else {
- msg = 'method=' + methodName + ' status=' + status + ' statusCode=' + xhr.status + ' err=' + err;
-
- if (xhr.status === 0) {
- e = {message: translations.connectionError};
- } else {
- e = {message: translations.unknownError + ' (' + xhr.status + ')'};
- }
- }
-
- console.error(msg); //eslint-disable-line no-console
- console.error(e); //eslint-disable-line no-console
-
- track('api', 'api_weberror', methodName, 'message', msg);
-
- if (xhr.status === 401) {
- const team = window.location.pathname.split('/')[1];
- browserHistory.push('/' + team + '/login?extra=expired&redirect=' + encodeURIComponent(window.location.pathname + window.location.search));
- }
-
- return e;
-}
-
-export function getTranslations(url, success, error) {
- $.ajax({
- url: url,
- dataType: 'json',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getTranslations', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function createTeamFromSignup(teamSignup, success, error) {
- $.ajax({
- url: '/api/v1/teams/create_from_signup',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(teamSignup),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('createTeamFromSignup', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function createTeamWithLdap(teamSignup, success, error) {
- $.ajax({
- url: '/api/v1/teams/create_with_ldap',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(teamSignup),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('createTeamFromSignup', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function createTeamWithSSO(team, service, success, error) {
- $.ajax({
- url: '/api/v1/teams/create_with_sso/' + service,
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(team),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('createTeamWithSSO', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function createUser(user, data, emailHash, success, error) {
- $.ajax({
- url: '/api/v1/users/create?d=' + encodeURIComponent(data) + '&h=' + encodeURIComponent(emailHash),
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(user),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('createUser', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_users_create', user.team_id, 'email', user.email);
-}
-
-export function updateUser(user, success, error) {
- $.ajax({
- url: '/api/v1/users/update',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(user),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('updateUser', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_users_update');
-}
-
-export function updatePassword(data, success, error) {
- $.ajax({
- url: '/api/v1/users/newpassword',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('newPassword', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_users_newpassword');
-}
-
-export function updateUserNotifyProps(data, success, error) {
- $.ajax({
- url: '/api/v1/users/update_notify',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('updateUserNotifyProps', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function updateRoles(data, success, error) {
- $.ajax({
- url: '/api/v1/users/update_roles',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('updateRoles', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_users_update_roles');
-}
-
-export function updateActive(userId, active, success, error) {
- var data = {};
- data.user_id = userId;
- data.active = '' + active;
-
- $.ajax({
- url: '/api/v1/users/update_active',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('updateActive', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_users_update_roles');
-}
-
-export function sendPasswordReset(data, success, error) {
- $.ajax({
- url: '/api/v1/users/send_password_reset',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('sendPasswordReset', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_users_send_password_reset');
-}
-
-export function resetPassword(data, success, error) {
- $.ajax({
- url: '/api/v1/users/reset_password',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('resetPassword', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_users_reset_password');
-}
-
-export function emailToOAuth(data, success, error) {
- $.ajax({
- url: '/api/v1/users/claim/email_to_oauth',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('emailToOAuth', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_users_email_to_oauth');
-}
-
-export function oauthToEmail(data, success, error) {
- $.ajax({
- url: '/api/v1/users/claim/oauth_to_email',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('oauthToEmail', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_users_oauth_to_email');
-}
-
-export function emailToLDAP(data, success, error) {
- $.ajax({
- url: '/api/v1/users/claim/email_to_ldap',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('emailToLDAP', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_users_email_to_ldap');
-}
-
-export function ldapToEmail(data, success, error) {
- $.ajax({
- url: '/api/v1/users/claim/ldap_to_email',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('ldapToEmail', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_users_ldap_to_email');
-}
-
-export function logout(success, error) {
- track('api', 'api_users_logout');
- $.ajax({
- url: '/api/v1/users/logout',
- type: 'POST',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('logout', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function checkMfa(method, team, loginId, success, error) {
- $.ajax({
- url: '/api/v1/users/mfa',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify({method, team_name: team, login_id: loginId}),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('checkMfa', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function loginByEmail(name, email, password, token, success, error) {
- $.ajax({
- url: '/api/v1/users/login',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify({name, email, password, token}),
- success: function onSuccess(data, textStatus, xhr) {
- track('api', 'api_users_login_success', data.team_id, 'email', data.email);
- sessionStorage.removeItem(data.id + '_last_error');
- BrowserStore.signalLogin();
- success(data, textStatus, xhr);
- },
- error: function onError(xhr, status, err) {
- track('api', 'api_users_login_fail', name, 'email', email);
-
- var e = handleError('loginByEmail', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function loginByUsername(name, username, password, success, error) {
- $.ajax({
- url: '/api/v1/users/login',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify({name, username, password}),
- success: function onSuccess(data, textStatus, xhr) {
- track('api', 'api_users_login_success', data.team_id, 'username', data.username);
- sessionStorage.removeItem(data.id + '_last_error');
- BrowserStore.signalLogin();
- success(data, textStatus, xhr);
- },
- error: function onError(xhr, status, err) {
- track('api', 'api_users_login_fail', name, 'username', username);
-
- var e = handleError('loginByUsername', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function loginByLdap(teamName, id, password, token, success, error) {
- $.ajax({
- url: '/api/v1/users/login_ldap',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify({teamName, id, password, token}),
- success: function onSuccess(data, textStatus, xhr) {
- track('api', 'api_users_loginLdap_success', data.team_id, 'id', id);
- sessionStorage.removeItem(data.id + '_last_error');
- BrowserStore.signalLogin();
- success(data, textStatus, xhr);
- },
- error: function onError(xhr, status, err) {
- track('api', 'api_users_loginLdap_fail', teamName, 'id', id);
-
- var e = handleError('loginByLdap', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function revokeSession(altId, success, error) {
- $.ajax({
- url: '/api/v1/users/revoke_session',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify({id: altId}),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('revokeSession', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getSessions(userId, success, error) {
- $.ajax({
- cache: false,
- url: '/api/v1/users/' + userId + '/sessions',
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getSessions', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getAudits(userId, success, error) {
- $.ajax({
- url: '/api/v1/users/' + userId + '/audits',
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getAudits', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getComplianceReports(success, error) {
- $.ajax({
- url: '/api/v1/admin/compliance_reports',
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getComplianceReports', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function saveComplianceReports(job, success, error) {
- $.ajax({
- url: '/api/v1/admin/save_compliance_report',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(job),
- success,
- error: (xhr, status, err) => {
- var e = handleError('saveComplianceReports', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getLogs(success, error) {
- $.ajax({
- url: '/api/v1/admin/logs',
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getLogs', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getServerAudits(success, error) {
- $.ajax({
- url: '/api/v1/admin/audits',
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getServerAudits', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getConfig(success, error) {
- return $.ajax({
- url: '/api/v1/admin/config',
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getConfig', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getAnalytics(name, teamId, success, error) {
- let url = '/api/v1/admin/analytics/';
- if (teamId == null) {
- url += name;
- } else {
- url += teamId + '/' + name;
- }
- $.ajax({
- url,
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success,
- error: (xhr, status, err) => {
- var e = handleError('getSystemAnalytics', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getClientConfig(success, error) {
- return $.ajax({
- url: '/api/v1/admin/client_props',
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getClientConfig', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getTeamAnalytics(teamId, name, success, error) {
- $.ajax({
- url: '/api/v1/admin/analytics/' + teamId + '/' + name,
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success,
- error: (xhr, status, err) => {
- var e = handleError('getTeamAnalytics', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function saveConfig(config, success, error) {
- $.ajax({
- url: '/api/v1/admin/save_config',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(config),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('saveConfig', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function logClientError(msg) {
- var l = {};
- l.level = 'ERROR';
- l.message = msg;
-
- $.ajax({
- url: '/api/v1/admin/log_client',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(l)
- });
-}
-
-export function testEmail(config, success, error) {
- $.ajax({
- url: '/api/v1/admin/test_email',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(config),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('testEmail', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getAllTeams(success, error) {
- $.ajax({
- url: '/api/v1/teams/all',
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getAllTeams', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getMeLoggedIn(success, error) {
- return $.ajax({
- cache: false,
- url: '/api/v1/users/me_logged_in',
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getMeLoggedIn', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getMe(success, error) {
- var currentUser = null;
- $.ajax({
- cache: false,
- url: '/api/v1/users/me',
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success: function gotUser(data, textStatus, xhr) {
- currentUser = data;
- if (success) {
- success(data, textStatus, xhr);
- }
- },
- error: function onError(xhr, status, err) {
- if (error) {
- var e = handleError('getMe', xhr, status, err);
- error(e);
- }
- }
- });
-
- return currentUser;
-}
-
-export function inviteMembers(data, success, error) {
- $.ajax({
- url: '/api/v1/teams/invite_members',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('inviteMembers', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_teams_invite_members');
-}
-
-export function updateTeam(team, success, error) {
- $.ajax({
- url: '/api/v1/teams/update',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(team),
- success,
- error: (xhr, status, err) => {
- var e = handleError('updateTeam', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_teams_update_name');
-}
-
-export function signupTeam(email, success, error) {
- $.ajax({
- url: '/api/v1/teams/signup',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify({email: email}),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('singupTeam', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_teams_signup');
-}
-
-export function createTeam(team, success, error) {
- $.ajax({
- url: '/api/v1/teams/create',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(team),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('createTeam', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function findTeamByName(teamName, success, error) {
- $.ajax({
- url: '/api/v1/teams/find_team_by_name',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify({name: teamName}),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('findTeamByName', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function createChannel(channel, success, error) {
- $.ajax({
- url: '/api/v1/channels/create',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(channel),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('createChannel', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_channels_create');
-}
-
-export function createDirectChannel(channel, userId, success, error) {
- $.ajax({
- url: '/api/v1/channels/create_direct',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify({user_id: userId}),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('createDirectChannel', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_channels_create_direct');
-}
-
-export function updateChannel(channel, success, error) {
- $.ajax({
- url: '/api/v1/channels/update',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(channel),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('updateChannel', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_channels_update');
-}
-
-export function updateChannelHeader(channelId, header, success, error) {
- const data = {
- channel_id: channelId,
- channel_header: header
- };
-
- $.ajax({
- url: '/api/v1/channels/update_header',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('updateChannelHeader', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_channels_header');
-}
-
-export function updateChannelPurpose(data, success, error) {
- $.ajax({
- url: '/api/v1/channels/update_purpose',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('updateChannelPurpose', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_channels_purpose');
-}
-
-export function updateNotifyProps(data, success, error) {
- $.ajax({
- url: '/api/v1/channels/update_notify_props',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('updateNotifyProps', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function joinChannel(id, success, error) {
- $.ajax({
- url: '/api/v1/channels/' + id + '/join',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('joinChannel', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_channels_join');
-}
-
-export function leaveChannel(id, success, error) {
- $.ajax({
- url: '/api/v1/channels/' + id + '/leave',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('leaveChannel', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_channels_leave');
-}
-
-export function deleteChannel(id, success, error) {
- $.ajax({
- url: '/api/v1/channels/' + id + '/delete',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('deleteChannel', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_channels_delete');
-}
-
-export function updateLastViewedAt(channelId, success, error) {
- $.ajax({
- url: '/api/v1/channels/' + channelId + '/update_last_viewed_at',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('updateLastViewedAt', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getChannels(success, error) {
- return $.ajax({
- cache: false,
- url: '/api/v1/channels/',
- dataType: 'json',
- type: 'GET',
- success,
- ifModified: true,
- error: function onError(xhr, status, err) {
- var e = handleError('getChannels', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getChannel(id, success, error) {
- $.ajax({
- cache: false,
- url: '/api/v1/channels/' + id + '/',
- dataType: 'json',
- type: 'GET',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getChannel', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_channel_get');
-}
-
-export function getMoreChannels(success, error) {
- $.ajax({
- url: '/api/v1/channels/more',
- dataType: 'json',
- type: 'GET',
- success,
- ifModified: true,
- error: function onError(xhr, status, err) {
- var e = handleError('getMoreChannels', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getChannelCounts(success, error) {
- $.ajax({
- cache: false,
- url: '/api/v1/channels/counts',
- dataType: 'json',
- type: 'GET',
- success,
- ifModified: true,
- error: function onError(xhr, status, err) {
- var e = handleError('getChannelCounts', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getChannelExtraInfo(id, memberLimit, success, error) {
- let url = '/api/v1/channels/' + id + '/extra_info';
-
- if (memberLimit) {
- url += '/' + memberLimit;
- }
-
- return $.ajax({
- url,
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getChannelExtraInfo', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function executeCommand(channelId, command, suggest, success, error) {
- $.ajax({
- url: '/api/v1/commands/execute',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify({channelId, command, suggest: '' + suggest}),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('executeCommand', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function addCommand(cmd, success, error) {
- $.ajax({
- url: '/api/v1/commands/create',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(cmd),
- success,
- error: (xhr, status, err) => {
- var e = handleError('addCommand', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function deleteCommand(data, success, error) {
- $.ajax({
- url: '/api/v1/commands/delete',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: (xhr, status, err) => {
- var e = handleError('deleteCommand', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function listTeamCommands(success, error) {
- $.ajax({
- url: '/api/v1/commands/list_team_commands',
- dataType: 'json',
- type: 'GET',
- success,
- error: (xhr, status, err) => {
- var e = handleError('listTeamCommands', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function regenCommandToken(data, success, error) {
- $.ajax({
- url: '/api/v1/commands/regen_token',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: (xhr, status, err) => {
- var e = handleError('regenCommandToken', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function listCommands(success, error) {
- $.ajax({
- url: '/api/v1/commands/list',
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('listCommands', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getPostsPage(channelId, offset, limit, success, error, complete) {
- $.ajax({
- cache: false,
- url: '/api/v1/channels/' + channelId + '/posts/' + offset + '/' + limit,
- dataType: 'json',
- type: 'GET',
- ifModified: true,
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getPosts', xhr, status, err);
- error(e);
- },
- complete: complete
- });
-}
-
-export function getPosts(channelId, since, success, error, complete) {
- return $.ajax({
- url: '/api/v1/channels/' + channelId + '/posts/' + since,
- dataType: 'json',
- type: 'GET',
- ifModified: true,
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getPosts', xhr, status, err);
- error(e);
- },
- complete: complete
- });
-}
-
-export function getPostsBefore(channelId, post, offset, numPost, success, error, complete) {
- $.ajax({
- url: '/api/v1/channels/' + channelId + '/post/' + post + '/before/' + offset + '/' + numPost,
- dataType: 'json',
- type: 'GET',
- ifModified: false,
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getPostsBefore', xhr, status, err);
- error(e);
- },
- complete: complete
- });
-}
-
-export function getPostsAfter(channelId, post, offset, numPost, success, error, complete) {
- $.ajax({
- url: '/api/v1/channels/' + channelId + '/post/' + post + '/after/' + offset + '/' + numPost,
- dataType: 'json',
- type: 'GET',
- ifModified: false,
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getPostsAfter', xhr, status, err);
- error(e);
- },
- complete: complete
- });
-}
-
-export function getPost(channelId, postId, success, error, complete) {
- $.ajax({
- cache: false,
- url: '/api/v1/channels/' + channelId + '/post/' + postId,
- dataType: 'json',
- type: 'GET',
- ifModified: false,
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getPost', xhr, status, err);
- error(e);
- },
- complete
- });
-}
-
-export function getPostById(postId, success, error, complete) {
- $.ajax({
- cache: false,
- url: '/api/v1/posts/' + postId,
- dataType: 'json',
- type: 'GET',
- ifModified: false,
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getPostById', xhr, status, err);
- error(e);
- },
- complete
- });
-}
-
-export function search(terms, success, error) {
- $.ajax({
- url: '/api/v1/posts/search',
- dataType: 'json',
- type: 'GET',
- data: {terms: terms},
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('search', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_posts_search');
-}
-
-export function deletePost(channelId, id, success, error) {
- $.ajax({
- url: '/api/v1/channels/' + channelId + '/post/' + id + '/delete',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('deletePost', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_posts_delete');
-}
-
-export function createPost(post, channel, success, error) {
- $.ajax({
- url: '/api/v1/channels/' + post.channel_id + '/create',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(post),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('createPost', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_posts_create', channel.name, 'length', post.message.length);
-
- // global.window.analytics.track('api_posts_create', {
- // category: 'api',
- // channel_name: channel.name,
- // channel_type: channel.type,
- // length: post.message.length,
- // files: (post.filenames || []).length,
- // mentions: (post.message.match('/<mention>/g') || []).length
- // });
-}
-
-export function updatePost(post, success, error) {
- $.ajax({
- url: '/api/v1/channels/' + post.channel_id + '/update',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(post),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('updatePost', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_posts_update');
-}
-
-export function addChannelMember(id, data, success, error) {
- $.ajax({
- url: '/api/v1/channels/' + id + '/add',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('addChannelMember', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_channels_add_member');
-}
-
-export function removeChannelMember(id, data, success, error) {
- $.ajax({
- url: '/api/v1/channels/' + id + '/remove',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('removeChannelMember', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_channels_remove_member');
-}
-
-export function getProfiles(success, error) {
- $.ajax({
- cache: false,
- url: '/api/v1/users/profiles',
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success,
- ifModified: true,
- error: function onError(xhr, status, err) {
- var e = handleError('getProfiles', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getProfilesForTeam(teamId, success, error) {
- $.ajax({
- cache: false,
- url: '/api/v1/users/profiles/' + teamId,
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getProfilesForTeam', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function uploadFile(formData, success, error) {
- var request = $.ajax({
- url: '/api/v1/files/upload',
- type: 'POST',
- data: formData,
- cache: false,
- contentType: false,
- processData: false,
- success,
- error: function onError(xhr, status, err) {
- if (err !== 'abort') {
- var e = handleError('uploadFile', xhr, status, err);
- error(e);
- }
- }
- });
-
- track('api', 'api_files_upload');
-
- return request;
-}
-
-export function getFileInfo(filename, success, error) {
- $.ajax({
- url: '/api/v1/files/get_info' + filename,
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success: (data) => {
- success(data);
- },
- error: function onError(xhr, status, err) {
- var e = handleError('getFileInfo', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getPublicLink(data, success, error) {
- $.ajax({
- url: '/api/v1/files/get_public_link',
- dataType: 'json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getPublicLink', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function uploadProfileImage(imageData, success, error) {
- $.ajax({
- url: '/api/v1/users/newimage',
- type: 'POST',
- data: imageData,
- cache: false,
- contentType: false,
- processData: false,
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('uploadProfileImage', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function importSlack(fileData, success, error) {
- $.ajax({
- url: '/api/v1/teams/import_team',
- type: 'POST',
- data: fileData,
- cache: false,
- contentType: false,
- processData: false,
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('importTeam', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function exportTeam(success, error) {
- $.ajax({
- url: '/api/v1/teams/export_team',
- type: 'GET',
- dataType: 'json',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('exportTeam', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getStatuses(ids, success, error) {
- $.ajax({
- url: '/api/v1/users/status',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(ids),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getStatuses', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getMyTeam(success, error) {
- return $.ajax({
- url: '/api/v1/teams/me',
- dataType: 'json',
- type: 'GET',
- success,
- ifModified: true,
- error: function onError(xhr, status, err) {
- var e = handleError('getMyTeam', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function registerOAuthApp(app, success, error) {
- $.ajax({
- url: '/api/v1/oauth/register',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(app),
- success: success,
- error: (xhr, status, err) => {
- const e = handleError('registerApp', xhr, status, err);
- error(e);
- }
- });
-
- module.exports.track('api', 'api_apps_register');
-}
-
-export function allowOAuth2(responseType, clientId, redirectUri, state, scope, success, error) {
- $.ajax({
- url: '/api/v1/oauth/allow?response_type=' + responseType + '&client_id=' + clientId + '&redirect_uri=' + redirectUri + '&scope=' + scope + '&state=' + state,
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success,
- error: (xhr, status, err) => {
- const e = handleError('allowOAuth2', xhr, status, err);
- error(e);
- }
- });
-
- module.exports.track('api', 'api_users_allow_oauth2');
-}
-
-export function addIncomingHook(hook, success, error) {
- $.ajax({
- url: '/api/v1/hooks/incoming/create',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(hook),
- success,
- error: (xhr, status, err) => {
- var e = handleError('addIncomingHook', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function deleteIncomingHook(data, success, error) {
- $.ajax({
- url: '/api/v1/hooks/incoming/delete',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: (xhr, status, err) => {
- var e = handleError('deleteIncomingHook', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function listIncomingHooks(success, error) {
- $.ajax({
- url: '/api/v1/hooks/incoming/list',
- dataType: 'json',
- type: 'GET',
- success,
- error: (xhr, status, err) => {
- var e = handleError('listIncomingHooks', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getAllPreferences(success, error) {
- return $.ajax({
- url: '/api/v1/preferences/',
- dataType: 'json',
- type: 'GET',
- success,
- error: (xhr, status, err) => {
- var e = handleError('getAllPreferences', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getPreferenceCategory(category, success, error) {
- $.ajax({
- url: `/api/v1/preferences/${category}`,
- dataType: 'json',
- type: 'GET',
- success,
- error: (xhr, status, err) => {
- var e = handleError('getPreferenceCategory', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function savePreferences(preferences, success, error) {
- $.ajax({
- url: '/api/v1/preferences/save',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(preferences),
- success,
- error: (xhr, status, err) => {
- var e = handleError('savePreferences', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function addOutgoingHook(hook, success, error) {
- $.ajax({
- url: '/api/v1/hooks/outgoing/create',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(hook),
- success,
- error: (xhr, status, err) => {
- var e = handleError('addOutgoingHook', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function deleteOutgoingHook(data, success, error) {
- $.ajax({
- url: '/api/v1/hooks/outgoing/delete',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: (xhr, status, err) => {
- var e = handleError('deleteOutgoingHook', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function listOutgoingHooks(success, error) {
- $.ajax({
- url: '/api/v1/hooks/outgoing/list',
- dataType: 'json',
- type: 'GET',
- success,
- error: (xhr, status, err) => {
- var e = handleError('listOutgoingHooks', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function regenOutgoingHookToken(data, success, error) {
- $.ajax({
- url: '/api/v1/hooks/outgoing/regen_token',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: (xhr, status, err) => {
- var e = handleError('regenOutgoingHookToken', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function uploadLicenseFile(formData, success, error) {
- $.ajax({
- url: '/api/v1/license/add',
- type: 'POST',
- data: formData,
- cache: false,
- contentType: false,
- processData: false,
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('uploadLicenseFile', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_license_upload');
-}
-
-export function removeLicenseFile(success, error) {
- $.ajax({
- url: '/api/v1/license/remove',
- type: 'POST',
- cache: false,
- contentType: false,
- processData: false,
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('removeLicenseFile', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_license_upload');
-}
-
-export function getClientLicenceConfig(success, error) {
- return $.ajax({
- url: '/api/v1/license/client_config',
- dataType: 'json',
- contentType: 'application/json',
- type: 'GET',
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getClientLicenceConfig', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function getInviteInfo(success, error, id) {
- $.ajax({
- url: '/api/v1/teams/get_invite_info',
- type: 'POST',
- dataType: 'json',
- contentType: 'application/json',
- data: JSON.stringify({invite_id: id}),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('getInviteInfo', xhr, status, err);
- if (error) {
- error(e);
- }
- }
- });
-}
-
-export function verifyEmail(success, error, uid, hid) {
- $.ajax({
- url: '/api/v1/users/verify_email',
- type: 'POST',
- contentType: 'application/json',
- dataType: 'text',
- data: JSON.stringify({uid, hid}),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('verifyEmail', xhr, status, err);
- if (error) {
- error(e);
- }
- }
- });
-}
-
-export function resendVerification(success, error, teamName, email) {
- $.ajax({
- url: '/api/v1/users/resend_verification',
- type: 'POST',
- contentType: 'application/json',
- dataType: 'text',
- data: JSON.stringify({team_name: teamName, email}),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('resendVerification', xhr, status, err);
- if (error) {
- error(e);
- }
- }
- });
-}
-
-export function updateMfa(data, success, error) {
- $.ajax({
- url: '/api/v1/users/update_mfa',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: (xhr, status, err) => {
- var e = handleError('updateMfa', xhr, status, err);
- error(e);
- }
- });
-}
-
-export function uploadBrandImage(image, success, error) {
- const formData = new FormData();
- formData.append('image', image, image.name);
-
- $.ajax({
- url: '/api/v1/admin/upload_brand_image',
- type: 'POST',
- data: formData,
- cache: false,
- contentType: false,
- processData: false,
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('uploadBrandImage', xhr, status, err);
- error(e);
- }
- });
-}
diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx
index 87f4153fb..9bdf348cd 100644
--- a/webapp/utils/constants.jsx
+++ b/webapp/utils/constants.jsx
@@ -61,6 +61,7 @@ export default {
RECEIVED_ADD_MENTION: null,
RECEIVED_PROFILES: null,
+ RECEIVED_DIRECT_PROFILES: null,
RECEIVED_ME: null,
RECEIVED_SESSIONS: null,
RECEIVED_AUDITS: null,
@@ -92,6 +93,9 @@ export default {
RECEIVED_SERVER_AUDITS: null,
RECEIVED_SERVER_COMPLIANCE_REPORTS: null,
RECEIVED_ALL_TEAMS: null,
+ RECEIVED_ALL_TEAM_LISTINGS: null,
+ RECEIVED_TEAM_MEMBERS: null,
+ RECEIVED_MEMBERS_FOR_TEAM: null,
RECEIVED_LOCALE: null,
@@ -205,6 +209,7 @@ export default {
LDAP_SERVICE: 'ldap',
USERNAME_SERVICE: 'username',
SIGNIN_CHANGE: 'signin_change',
+ PASSWORD_CHANGE: 'password_change',
SIGNIN_VERIFIED: 'verified',
SESSION_EXPIRED: 'expired',
POST_CHUNK_SIZE: 60,
diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx
index c4f4a025e..a1e16928b 100644
--- a/webapp/utils/utils.jsx
+++ b/webapp/utils/utils.jsx
@@ -10,9 +10,8 @@ import PreferenceStore from 'stores/preference_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import Constants from 'utils/constants.jsx';
var ActionTypes = Constants.ActionTypes;
-import * as Client from './client.jsx';
import * as AsyncClient from './async_client.jsx';
-import * as client from './client.jsx';
+import Client from './web_client.jsx';
import React from 'react';
import {browserHistory} from 'react-router';
@@ -1114,7 +1113,7 @@ export function fileSizeToString(bytes) {
// Converts a filename (like those attached to Post objects) to a url that can be used to retrieve attachments from the server.
export function getFileUrl(filename) {
- return getWindowLocationOrigin() + '/api/v1/files/get' + filename;
+ return getWindowLocationOrigin() + Client.getFilesRoute() + '/get' + filename;
}
// Gets the name of a file (including extension) from a given url or file path.
@@ -1210,13 +1209,17 @@ export function importSlack(file, success, error) {
formData.append('filesize', file.size);
formData.append('importFrom', 'slack');
- client.importSlack(formData, success, error);
+ Client.importSlack(formData, success, error);
}
export function getTeamURLFromAddressBar() {
return window.location.origin + '/' + window.location.pathname.split('/')[1];
}
+export function getTeamNameFromUrl() {
+ return window.location.pathname.split('/')[1];
+}
+
export function getTeamURLNoOriginFromAddressBar() {
return '/' + window.location.pathname.split('/')[1];
}
@@ -1263,16 +1266,11 @@ export function openDirectChannelToUser(user, successCb, errorCb) {
};
Client.createDirectChannel(
- channel,
user.id,
(data) => {
Client.getChannel(
data.id,
- (data2, textStatus, xhr) => {
- if (xhr.status === 304 || !data2) {
- return;
- }
-
+ (data2) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_CHANNEL,
channel: data2.channel,
@@ -1400,7 +1398,7 @@ export function localizeMessage(id, defaultMessage) {
}
export function getProfilePicSrcForPost(post, timestamp) {
- let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp;
+ let src = Client.getUsersRoute() + '/' + post.user_id + '/image?time=' + timestamp;
if (post.props && post.props.from_webhook && global.window.mm_config.EnablePostIconOverride === 'true') {
if (post.props.override_icon_url) {
src = post.props.override_icon_url;
diff --git a/webapp/utils/web_client.jsx b/webapp/utils/web_client.jsx
new file mode 100644
index 000000000..6071b4bb4
--- /dev/null
+++ b/webapp/utils/web_client.jsx
@@ -0,0 +1,67 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import Client from '../client/client.jsx';
+import TeamStore from '../stores/team_store.jsx';
+import BrowserStore from '../stores/browser_store.jsx';
+import * as GlobalActions from 'action_creators/global_actions.jsx';
+
+const HTTP_UNAUTHORIZED = 401;
+
+class WebClientClass extends Client {
+ constructor() {
+ super();
+ this.enableLogErrorsToConsole(true);
+ TeamStore.addChangeListener(this.onTeamStoreChanged);
+ }
+
+ onTeamStoreChanged = () => {
+ this.setTeamId(TeamStore.getCurrentId());
+ }
+
+ track = (category, action, label, property, value) => {
+ if (global.window && global.window.analytics) {
+ global.window.analytics.track(action, {category, label, property, value});
+ }
+ }
+
+ trackPage = () => {
+ if (global.window && global.window.analytics) {
+ global.window.analytics.page();
+ }
+ }
+
+ handleError = (err, res) => { // eslint-disable-line no-unused-vars
+ if (err.status === HTTP_UNAUTHORIZED) {
+ GlobalActions.emitUserLoggedOutEvent('/login');
+ }
+ }
+
+ // not sure why but super.login doesn't work if using an () => arrow functions.
+ // I think this might be a webpack issue.
+ webLogin(email, username, password, token, success, error) {
+ this.login(
+ email,
+ username,
+ password,
+ token,
+ (data) => {
+ this.track('api', 'api_users_login_success', '', 'email', data.email);
+ BrowserStore.signalLogin();
+
+ if (success) {
+ success(data);
+ }
+ },
+ (err) => {
+ this.track('api', 'api_users_login_fail', name, 'email', email);
+ if (error) {
+ error(err);
+ }
+ }
+ );
+ }
+}
+
+var WebClient = new WebClientClass();
+export default WebClient;
diff --git a/webapp/webpack.config-test.js b/webapp/webpack.config-test.js
new file mode 100644
index 000000000..aaeefeb8c
--- /dev/null
+++ b/webapp/webpack.config-test.js
@@ -0,0 +1,131 @@
+const webpack = require('webpack');
+const path = require('path');
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
+const CopyWebpackPlugin = require('copy-webpack-plugin');
+const nodeExternals = require('webpack-node-externals');
+
+const htmlExtract = new ExtractTextPlugin('html', 'root.html');
+
+const NPM_TARGET = process.env.npm_lifecycle_event; //eslint-disable-line no-process-env
+
+var DEV = true;
+var FULLMAP = false;
+if (NPM_TARGET === 'run' || NPM_TARGET === 'run-fullmap') {
+ DEV = true;
+ if (NPM_TARGET === 'run-fullmap') {
+ FULLMAP = true;
+ }
+}
+
+var config = {
+ target: 'node',
+ externals: [nodeExternals()],
+ module: {
+ loaders: [
+ {
+ test: /\.jsx?$/,
+ loader: 'babel',
+ exclude: /(node_modules|non_npm_dependencies)/,
+ query: {
+ presets: ['react', 'es2015-webpack', 'stage-0'],
+ plugins: ['transform-runtime'],
+ cacheDirectory: DEV
+ }
+ },
+ {
+ test: /\.json$/,
+ loader: 'json'
+ },
+ {
+ test: /(node_modules|non_npm_dependencies)\/.+\.(js|jsx)$/,
+ loader: 'imports',
+ query: {
+ $: 'jquery',
+ jQuery: 'jquery'
+ }
+ },
+ {
+ test: /\.scss$/,
+ loaders: ['style', 'css', 'sass']
+ },
+ {
+ test: /\.css$/,
+ loaders: ['style', 'css']
+ },
+ {
+ test: /\.(png|eot|tiff|svg|woff2|woff|ttf|gif|mp3|jpg)$/,
+ loader: 'file',
+ query: {
+ name: 'files/[hash].[ext]'
+ }
+ },
+ {
+ test: /\.html$/,
+ loader: htmlExtract.extract('html?attrs=link:href')
+ }
+ ]
+ },
+ sassLoader: {
+ includePaths: ['node_modules/compass-mixins/lib']
+ },
+ plugins: [
+ new webpack.ProvidePlugin({
+ 'window.jQuery': 'jquery'
+ }),
+ htmlExtract,
+ new CopyWebpackPlugin([
+ {from: 'images/emoji', to: 'emoji'}
+ ]),
+ new webpack.LoaderOptionsPlugin({
+ minimize: !DEV,
+ debug: false
+ })
+ ],
+ resolve: {
+ alias: {
+ jquery: 'jquery/dist/jquery'
+ },
+ modules: [
+ 'node_modules',
+ 'non_npm_dependencies',
+ path.resolve(__dirname)
+ ]
+ }
+};
+
+// Development mode configuration
+if (DEV) {
+ if (FULLMAP) {
+ config.devtool = 'source-map';
+ } else {
+ config.devtool = 'eval-cheap-module-source-map';
+ }
+}
+
+// Production mode configuration
+if (!DEV) {
+ config.devtool = 'source-map';
+ config.plugins.push(
+ new webpack.optimize.UglifyJsPlugin({
+ 'screw-ie8': true,
+ mangle: {
+ toplevel: false
+ },
+ compress: {
+ warnings: false
+ },
+ comments: false
+ })
+ );
+ config.plugins.push(
+ new webpack.optimize.AggressiveMergingPlugin()
+ );
+ config.plugins.push(
+ new webpack.optimize.OccurrenceOrderPlugin(true)
+ );
+ config.plugins.push(
+ new webpack.optimize.DedupePlugin()
+ );
+}
+
+module.exports = config;
diff --git a/webapp/webpack.config.js b/webapp/webpack.config.js
index 4e2d6b70d..dac074c0b 100644
--- a/webapp/webpack.config.js
+++ b/webapp/webpack.config.js
@@ -88,7 +88,8 @@ var config = {
],
resolve: {
alias: {
- jquery: 'jquery/dist/jquery'
+ jquery: 'jquery/dist/jquery',
+ superagent: 'node_modules/superagent/lib/client'
},
modules: [
'node_modules',