summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md128
-rw-r--r--Makefile10
-rw-r--r--api/context.go9
-rw-r--r--api/user.go8
-rw-r--r--config/config.json6
-rw-r--r--doc/developer/tests/test-search.md43
-rw-r--r--doc/install/Configuration-Settings.md93
-rw-r--r--doc/install/Upgrade-Guide.md2
-rw-r--r--docker/dev/config_docker.json6
-rw-r--r--docker/local/config_docker.json6
-rw-r--r--manualtesting/manual_testing.go2
-rw-r--r--model/config.go24
-rw-r--r--model/session.go21
-rw-r--r--web/react/components/admin_console/service_settings.jsx133
-rw-r--r--web/react/components/create_comment.jsx7
-rw-r--r--web/react/components/create_post.jsx7
-rw-r--r--web/react/components/file_upload.jsx46
-rw-r--r--web/react/utils/utils.jsx6
-rw-r--r--web/sass-files/sass/partials/_post.scss21
-rw-r--r--web/web.go2
20 files changed, 488 insertions, 92 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d5094f06e..42b485742 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,133 @@
# Mattermost Changelog
+## Release v1.4.0
+
+Expected Release date: 2016-01-16
+
+### Release Highlights
+
+#### Data Center Support
+
+- Deployment guides on Red Hat Enterprise Linux 6 and 7 now available
+- Legal disclosure and support links (terms of service, privacy policy, help, about, and support email) now configurable
+- Over a dozen new configuration options in System Console
+
+#### Mobile Experience
+
+- iOS reference app [now available from iTunes](https://itunes.apple.com/us/app/mattermost/id984966508?ls=1&mt=8), compiled from [open source repo](https://github.com/mattermost/ios)
+- Date headers now show when scrolling on mobile, so you can quickly see when messages were sent
+- Added "rapid scroll" support for jumping quickily to bottom of channels on mobile
+
+### New Features
+
+Mobile Experience
+- Date headers now show when scrolling on mobile, so you can quickly see when messages were sent
+- Added "rapid scroll" support for jumping quickily to bottom of channels on mobile
+
+Authentication
+
+- Accounts can now switch between email and GitLab SSO sign-in options
+- New ability to customize session token length
+
+System Console
+
+- Added **Legal and Support Settings** so System Administrators can change the default Terms of Service, Privacy Policy, and Help links
+- Under **Service Settings** added options to customize expiry of web, mobile and SSO session tokens, expiry of caches in memory, and an EnableDeveloper option to turn on Developer Mode which alerts users to any console errors that occur
+
+### Improvements
+
+Performance and Testing
+
+- Added logging for email and push notifications events in DEBUG mode
+
+Integrations
+
+- Added support to allow optional parameters in the `Content-Type` of incoming webhook requests
+
+Files and Images
+
+- Animated GIFs autoplay in the image previewer
+
+Notifications and Email
+
+- Changed email notifications to display the server's local timezone instead of UTC
+
+User Interface
+
+- Updated the "About Mattermost" dialog formatting
+- Going to domain/teamname now goes to the last channel of your previous session, instead of Town Square
+- Various improvements to mobile UI, including a floating date indicator and the ability to quickly scroll to the bottom of the channel
+
+#### Bug Fixes
+
+- Fixed issue where usernames containing a "." did not get mention notifications
+- Fixed issue where System Console did not save the "Send push notifications" setting
+- Fixed issue with Font Display cancel button not working in Account Settings menu
+- Fixed incorrect default for "Team Name Display" settings
+- Fixed issue where various media files appeared broken in the media player on some browsers
+- Fixed cross-contamination issue when multiple accounts log into the same team on the same browser
+- Fixed issue where color pickers did not update when a theme was pasted in
+- Increased the maximum number of channels
+
+### Compatibility
+
+#### Config.json Changes from v1.3 to v1.4
+
+Multiple settings were added to `config.json`. Below is a list of the changes and their new default values in a fresh install.
+
+The following options can be modified in the System Console:
+
+- Under `ServiceSettings` in `config.json`:
+ - Added: `"EnableDeveloper": false` to set whether developer mode is enabled, which alerts users to any console errors that occur
+ - Added: `"SessionLengthWebInDays" : 30` to set the number of days before web sessions expire and users will need to log in again
+ - Added: `"SessionLengthMobileInDays" : 30` to set the number of days before native mobile sessions expire
+ - Added: `"SessionLengthSSOInDays" : 30` to set the number of days before SSO sessions expire
+ - Added: `"SessionCacheInMinutes" : 10` to set the number of minutes to cache a session in memory
+- Added `SupportSettings` section to `config.json`:
+ - Added: `"TermsOfServiceLink": "/static/help/terms.html"` to allow System Administrators to set the terms of service link
+ - Added: `"PrivacyPolicyLink": "/static/help/privacy.html"` to allow System Administrators to set the privacy policy link
+ - Added: `"AboutLink": "/static/help/about.html"` to allow System Administrators to set the about page link
+ - Added: `"HelpLink": "/static/help/help.html"` to allow System Administrators to set the help page link
+ - Added: `"ReportAProblemLink": "/static/help/report_problem.html"` to allow System Administrators to set the home page for the support website
+ - Added: `"SupportEmail":"feedback@mattermost.com"` to allow System Administrators to set an email address for feedback and support requests
+
+The following options are not present in the System Console, and can be modified manually in the `config.json` file:
+
+- Under `FileSettings` in `config.json`:
+ - Added: `"AmazonS3Endpoint": ""` to set an endpoint URL for an Amazon S3 instance
+ - Added: `"AmazonS3BucketEndpoint": ""` to set an endpoint URL for Amazon S3 buckets
+ - Added: `"AmazonS3LocationConstraint": false` to set whether the S3 region is location constrained
+ - Added: `"AmazonS3LowercaseBucket": false` to set whether bucket names are fully lowercase or not
+
+#### Known Issues
+
+- When navigating to a page with new messages as well as message containing inline images added via markdown, the channel may move up and down while loading the inline images
+- Microsoft Edge does not yet support drag and drop
+- No scroll bar in center channel
+- Pasting images into text box fails to upload on Firefox, Safari, and IE11
+- Public links for attachments attempt to download the file on IE, Edge, and Safari
+- Importing from Slack breaks @mentions and fails to load in certain cases with comments on files
+- System Console > TEAMS > Statistics > Newly Created Users shows all of the users are created "just now"
+- Favicon does not always become red when @mentions and direct messages are received on an inactive browser tab
+- Searching for a phrase in quotations returns more than just the phrase on Mattermost installations with a Postgres database
+- Deleted/Archived channels are not removed from the "More" menu of the person that deleted/archived the channel until after refresh
+- Search results don't highlight searches for @username, non-latin characters, or terms inside Markdown code blocks
+- Hashtags less than three characters long are not searchable
+- After deactivating a team member, the person remains in the channel counter
+- Certain symbols (<,>,-,+,=,%,^,#,*,|) directly before or after a hashtag cause the message to not show up in a hashtag search
+
+#### Contributors
+
+Many thanks to our external contributors. In no particular order:
+
+- [npcode](https://github.com/npcode)
+- [hjf288](https://github.com/hjf288)
+- [apskim](https://github.com/apskim)
+- [ejm2172](https://github.com/ejm2172)
+- [hvnsweeting](https://github.com/hvnsweeting)
+- [benburkert](https://github.com/benburkert)
+- [erikthered](https://github.com/erikthered)
+
## Release v1.3.0
Release date: 2015-12-16
diff --git a/Makefile b/Makefile
index 9fd74b959..1d7fbea5a 100644
--- a/Makefile
+++ b/Makefile
@@ -126,6 +126,10 @@ package:
cp -RL web/static/js/jquery-dragster $(DIST_PATH)/web/static/js/
cp -RL web/templates $(DIST_PATH)/web
+ cp -L web/static/js/react-0.14.3.js $(DIST_PATH)/web/static/js/
+ cp -L web/static/js/react-dom-0.14.3.js $(DIST_PATH)/web/static/js/
+ cp -L web/static/js/react-bootstrap-0.28.1.js $(DIST_PATH)/web/static/js/
+
mkdir -p $(DIST_PATH)/api
cp -RL api/templates $(DIST_PATH)/api
@@ -136,11 +140,11 @@ package:
mv $(DIST_PATH)/web/static/js/bundle.min.js $(DIST_PATH)/web/static/js/bundle-$(BUILD_NUMBER).min.js
mv $(DIST_PATH)/web/static/js/libs.min.js $(DIST_PATH)/web/static/js/libs-$(BUILD_NUMBER).min.js
- sed -i'.bak' 's|react-0.14.3.js|react-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html
- sed -i'.bak' 's|react-dom-0.14.3.js|react-dom-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html
+ #sed -i'.bak' 's|react-0.14.3.js|react-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html
+ #sed -i'.bak' 's|react-dom-0.14.3.js|react-dom-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html
sed -i'.bak' 's|jquery-2.1.4.js|jquery-2.1.4.min.js|g' $(DIST_PATH)/web/templates/head.html
sed -i'.bak' 's|bootstrap-3.3.5.js|bootstrap-3.3.5.min.js|g' $(DIST_PATH)/web/templates/head.html
- sed -i'.bak' 's|react-bootstrap-0.28.1.js|react-bootstrap-0.28.1.min.js|g' $(DIST_PATH)/web/templates/head.html
+ #sed -i'.bak' 's|react-bootstrap-0.28.1.js|react-bootstrap-0.28.1.min.js|g' $(DIST_PATH)/web/templates/head.html
sed -i'.bak' 's|perfect-scrollbar-0.6.7.jquery.js|perfect-scrollbar-0.6.7.jquery.min.js|g' $(DIST_PATH)/web/templates/head.html
sed -i'.bak' 's|bundle.js|bundle-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html
sed -i'.bak' 's|libs.min.js|libs-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html
diff --git a/api/context.go b/api/context.go
index a6f9bc1e1..b39f03a7d 100644
--- a/api/context.go
+++ b/api/context.go
@@ -523,6 +523,13 @@ func GetSession(token string) *model.Session {
l4g.Error("Invalid session token=" + token + ", err=" + sessionResult.Err.DetailedError)
} else {
session = sessionResult.Data.(*model.Session)
+
+ if session.IsExpired() {
+ return nil
+ } else {
+ AddSessionToCache(session)
+ return session
+ }
}
}
@@ -553,5 +560,5 @@ func FindMultiSessionForTeamId(r *http.Request, teamId string) (int64, *model.Se
}
func AddSessionToCache(session *model.Session) {
- sessionCache.Add(session.Token, session)
+ sessionCache.AddWithExpiresInSecs(session.Token, session, int64(*utils.Cfg.ServiceSettings.SessionCacheInMinutes*60))
}
diff --git a/api/user.go b/api/user.go
index 42a65c934..d4c7fcaf5 100644
--- a/api/user.go
+++ b/api/user.go
@@ -492,11 +492,11 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User,
session := &model.Session{UserId: user.Id, TeamId: user.TeamId, Roles: user.Roles, DeviceId: deviceId, IsOAuth: false}
- maxAge := model.SESSION_TIME_WEB_IN_SECS
+ maxAge := *utils.Cfg.ServiceSettings.SessionLengthWebInDays * 60 * 60 * 24
if len(deviceId) > 0 {
- session.SetExpireInDays(model.SESSION_TIME_MOBILE_IN_DAYS)
- maxAge = model.SESSION_TIME_MOBILE_IN_SECS
+ session.SetExpireInDays(*utils.Cfg.ServiceSettings.SessionLengthMobileInDays)
+ maxAge = *utils.Cfg.ServiceSettings.SessionLengthMobileInDays * 60 * 60 * 24
// 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 {
@@ -518,7 +518,7 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User,
}
} else {
- session.SetExpireInDays(model.SESSION_TIME_WEB_IN_DAYS)
+ session.SetExpireInDays(*utils.Cfg.ServiceSettings.SessionLengthWebInDays)
}
ua := user_agent.New(r.UserAgent())
diff --git a/config/config.json b/config/config.json
index 7c2ec9426..c43db1e50 100644
--- a/config/config.json
+++ b/config/config.json
@@ -11,7 +11,11 @@
"EnablePostIconOverride": false,
"EnableTesting": false,
"EnableDeveloper": false,
- "EnableSecurityFixAlert": true
+ "EnableSecurityFixAlert": true,
+ "SessionLengthWebInDays" : 30,
+ "SessionLengthMobileInDays" : 30,
+ "SessionLengthSSOInDays" : 30,
+ "SessionCacheInMinutes" : 10
},
"TeamSettings": {
"SiteName": "Mattermost",
diff --git a/doc/developer/tests/test-search.md b/doc/developer/tests/test-search.md
new file mode 100644
index 000000000..0f0ba1153
--- /dev/null
+++ b/doc/developer/tests/test-search.md
@@ -0,0 +1,43 @@
+# Search Testing
+
+### Basic Search Testing
+
+This post is used by the core team to test search. It should be returned for the test cases for search, with proper highlighting in the search results.
+
+**Basic word search:** Hello world!
+**Emoji search:** :strawberry:
+**Accent search:** Crème friache
+**Non-latin search:**
+您好吗
+您好
+**Email search:** person@domain.org
+**Link search:** www.dropbox.com
+**Markdown search:**
+##### Hello
+```
+Hello
+```
+`Hello`
+
+
+### Hashtags:
+
+Click on the linked hashtags below, and confirm that the search results match the linked hashtags. Confirm that the highlighting in the search results is correct.
+
+#### Basic Hashtags
+
+#hello #world
+
+#### Hashtags containing punctuation:
+
+*Note: Make a separate post containing only the word “hashtag”, and confirm the hashtags below do not return the separate post.*
+
+#hashtag #hashtag-dash #hashtag_underscore #hashtag.dot
+
+#### Punctuation following a hashtag:
+
+#colon: #slash/ #backslash\ #percent% #dollar$ #semicolon; #ampersand& #bracket( #bracket) #lessthan< #greaterthan> #dash- #plus+ #equals= #caret^ #hashtag# #asterisk* #verticalbar| #invertedquestion¿ #atsign@ #quote” #apostrophe' #curlybracket{ #curlybracket} #squarebracket[ #squarebracket]
+
+#### Markdown surrounding a hashtag:
+
+*#markdown-hashtag*
diff --git a/doc/install/Configuration-Settings.md b/doc/install/Configuration-Settings.md
index 74a7c777c..962be0eb7 100644
--- a/doc/install/Configuration-Settings.md
+++ b/doc/install/Configuration-Settings.md
@@ -23,8 +23,23 @@ Set this key to enable embedding of YouTube video previews based on hyperlinks a
```"EnableTesting": false```
"true": `/loadtest` slash command is enabled to load test accounts and test data.
+```"EnableDeveloper": false```
+"true": Users are alerted to any console errors that occur.
+
```"EnableSecurityFixAlert": true```
-”true”: System Administrators are notified by email if a relevant security fix alert has been announced in the last 12 hours. Requires email to be enabled.
+"true": System Administrators are notified by email if a relevant security fix alert has been announced in the last 12 hours. Requires email to be enabled.
+
+```"SessionLengthWebInDays" : 30```
+Set the number of days before web sessions expire and users will need to log in again.
+
+```"SessionLengthMobileInDays" : 30```
+Set the number of days before native mobile sessions expire.
+
+```"SessionLengthSSOInDays" : 30```
+Set the number of days before SSO sessions expire.
+
+```"SessionCacheInMinutes" : 10```
+Set the number of minutes to cache a session in memory.
#### Webhooks
@@ -198,7 +213,7 @@ The width to which profile pictures are resized after being uploaded via Account
The height to which profile pictures are resized after being uploaded via Account Settings.
```"EnablePublicLink": true```
-”true”: Allow users to share public links to files and images when previewing; “false”: The Get Public Link option is hidden from the image preview user interface.
+"true": Allow users to share public links to files and images when previewing; “false”: The Get Public Link option is hidden from the image preview user interface.
```"PublicLinkSalt": "A705AklYF8MFDOfcwh3I488G8vtLlVip"```
32-character (to be randomly generated via Admin Console) salt added to signing of public image links.
@@ -211,7 +226,7 @@ Settings to configure the console and log file output, detail level, format and
#### Console Settings
```"EnableConsole": true```
-“true”: Output log messages to the console based on **ConsoleLevel** option. The server writes messages to the standard output stream (stdout).
+"true": Output log messages to the console based on **ConsoleLevel** option. The server writes messages to the standard output stream (stdout).
```"ConsoleLevel": "DEBUG"```
Level of detail at which log events are written to the console when **EnableConsole**=true. ”ERROR”: Outputs only error messages; “INFO”: Outputs error messages and information around startup and initialization; “DEBUG”: Prints high detail for developers debugging issues.
@@ -219,7 +234,7 @@ Level of detail at which log events are written to the console when **EnableCons
#### Log File Settings
```"EnableFile": true```
-”true”: Log files are written to files specified in **FileLocation**.
+"true": Log files are written to files specified in **FileLocation**.
```"FileLevel": "INFO"```
Level of detail at which log events are written to log files when **EnableFile**=true. “ERROR”: Outputs only error messages; “INFO”: Outputs error messages and information around startup and initialization; “DEBUG”: Prints high detail for developers debugging issues.
@@ -243,7 +258,7 @@ Directory to which log files are written. If blank, log files write to ./logs/ma
Settings to enable API rate limiting and configure requests per second, user sessions and variables for rate limiting. Changing properties in this section will require a server restart before taking effect.
```"EnableRateLimiter": true```
-”true”: APIs are throttled at the rate specified by **PerSec**.
+"true": APIs are throttled at the rate specified by **PerSec**.
```"PerSec": 10```
Throttle API at this number of requests per second if **EnableRateLimiter**=true.
@@ -262,17 +277,17 @@ Vary rate limiting by HTTP header field specified (e.g. when configuring Ngnix s
Settings to configure the name and email privacy of users on your system.
```"ShowEmailAddress": true```
-“true”: Show email address of all users; "false": Hide email address of users from other users in the user interface, including team owners and team administrators. This is designed for managing teams where users choose to keep their contact information private.
+"true": Show email address of all users; "false": Hide email address of users from other users in the user interface, including team owners and team administrators. This is designed for managing teams where users choose to keep their contact information private.
```"ShowFullName": true```
-”true”: Show full name of all users; “false”: hide full name of users from other users including team owner and team administrators.
+"true": Show full name of all users; “false”: hide full name of users from other users including team owner and team administrators.
### GitLab Settings
Settings to configure account and team creation using GitLab OAuth.
```"Enable": false```
-“true”: Allow team creation and account signup using GitLab OAuth. To configure, input the **Secret** and **Id** credentials.
+"true": Allow team creation and account signup using GitLab OAuth. To configure, input the **Secret** and **Id** credentials.
```"Secret": ""```
Obtain this value by logging into your GitLab account. Go to Profile Settings -> Applications -> New Application, enter a Name, then enter Redirect URLs `https://<your-mattermost-url>/login/gitlab/complete` (example: `https://example.com:8065/login/gitlab/complete`) and `https://<your-mattermost-url>/signup/gitlab/complete`.
@@ -289,50 +304,68 @@ Enter `https://<your-gitlab-url>/oauth/authorize` (example: `https://example.com
```"UserApiEndpoint": ""```
Enter `https://<your-gitlab-url>/oauth/authorize` (example: `https://example.com:3000/api/v3/user`). Use HTTP or HTTPS depending on how your server is configured.
+### Support Settings
+
+```"TermsOfServiceLink": "/static/help/terms.html"```
+Set the link for the terms of service.
+
+```"PrivacyPolicyLink": "/static/help/privacy.html"```
+Set the link for the privacy policy.
+
+```"AboutLink": "/static/help/about.html"```
+Set the link for the about page.
+
+```"HelpLink": "/static/help/help.html"```
+Set the link for the help page.
+
+```"ReportAProblemLink": "/static/help/report_problem.html"```
+Set the link for the support website.
+
+`"SupportEmail":"feedback@mattermost.com"`
+Set an email for feedback or support requests.
+
### LDAP Settings (Enterprise)
Settings used to enable and configure LDAP authentication with Mattermost. Available in the Enterprise version of Mattermost.
-```"Enable Login With LDAP": "false"```
-When true, Mattermost allows login using LDAP.
+```"Enable Login With LDAP": "false"```
+"true": Mattermost allows login using LDAP.
-```“LDAP Server”: “”```
+```"LDAP Server": ""```
The domain or IP address of the LDAP server.
-```“LDAP Port”: “389”```
+```"LDAP Port": "389"```
The port Mattermost will use to connect to the LDAP server. Default is 389.
-```”BaseDN”: ””```
+```"BaseDN": ""```
The Base DN is the Distinguished Name of the location where Mattermost should start its search for users in the LDAP tree.
-```”Bind Username”: ””```
+```"Bind Username": ""```
The username used to perform the LDAP search. This should typically be an account created specifically for use with Mattermost. It should be a read only account with access limited to the portion of the LDAP tree specified in the BaseDN field.
-```”Bind Password”: ””```
+```"Bind Password": ""```
Password of the user given in “Bind Username”.
-```”First Name Attribute”: ””```
+```"First Name Attribute": ""```
The attribute in the LDAP server that will be used to populate the first name of users in Mattermost.
-```”Last Name Attribute”: ””```
+```"Last Name Attribute": ""```
The attribute in the LDAP server that will be used to populate the last name of users in Mattermost.
-```”Email Attribute”: ””```
+```"Email Attribute": ""```
The attribute in the LDAP server that will be used to populate the email addresses of users in Mattermost.
-```”Username Attribute”: ””```
+```"Username Attribute": ""```
The attribute in the LDAP server that will be used to populate the username field in Mattermost. This may be the same as the ID Attribute.
-```”ID Attribute”: ””```
-
+```"ID Attribute": ""```
The attribute in the LDAP server that will be used as a unique identifier in Mattermost.
This is the attribute that will be used to create Mattermost accounts. It should be an LDAP attribute with a value that does not change, such as username or uid. If a user’s Id Attribute changes, it will create a new Mattermost account unassociated with their old one.
This is also the value used to log in to Mattermost in the “LDAP Username” field on the sign in page. Normally this attribute is the same as the “Username Attribute” field above. If your team typically uses domain\username to sign in to other services with LDAP, you may choose to put domain\username in this field to maintain consistency between sites.
-```”Query Timeout (seconds)”: ”60”```
-
+```"Query Timeout (seconds)": "60"```
The timeout value for queries to the LDAP server. Increase this value if you are getting timeout errors caused by a slow LDAP server.
## Config.json Settings Not in System Console
@@ -342,13 +375,25 @@ System Console allows an IT Admin to update settings defined in `config.json`. H
### Service Settings
```"EnableOAuthServiceProvider": false```
-”true”: Allow Mattermost to function as an OAuth provider, allowing 3rd party apps access to your user store for authentication.
+"true": Allow Mattermost to function as an OAuth provider, allowing 3rd party apps access to your user store for authentication.
### File Settings
```"InitialFont": "luximbi.ttf"```
Font used in auto-generated profile pics with colored backgrounds.
+```"AmazonS3Endpoint": ""```
+Set an endpoint URL for an Amazon S3 instance.
+
+```"AmazonS3BucketEndpoint": ""```
+Set an endpoint URL for Amazon S3 buckets.
+
+```"AmazonS3LocationConstraint": false```
+Set whether the S3 region is location constrained.
+
+```Added: "AmazonS3LowercaseBucket": false```
+Set whether bucket names are fully lowercase or not.
+
### GitLab Settings
```"Scope": ""```
diff --git a/doc/install/Upgrade-Guide.md b/doc/install/Upgrade-Guide.md
index 4480dedd2..1f3ff9510 100644
--- a/doc/install/Upgrade-Guide.md
+++ b/doc/install/Upgrade-Guide.md
@@ -43,7 +43,7 @@ The following instructions apply to updating installations of Mattermost v0.7-Be
Mattermost is designed to be upgraded sequentially through major version releases. If you skip versions when upgrading GitLab, you may find a `panic: The database schema version of 1.1.0 cannot be upgraded. You must not skip a version` error in your `/var/log/gitlab/mattermost/current` directory. If so:
1. Run `platform -version` to check your version of Mattermost
-2. If your version of the Mattermost binary doesn't match the version listed in the database error message, downgrade the version of the Mattermost binary you are using by [following the manual upgrade steps for Mattermost](/var/log/gitlab/mattermost/current) and using the database schema version listed in the error messages for the version you select in Step 1) iv).
+2. If your version of the Mattermost binary doesn't match the version listed in the database error message, downgrade the version of the Mattermost binary you are using by [following the manual upgrade steps for Mattermost](https://github.com/mattermost/platform/blob/master/doc/install/Upgrade-Guide.md#upgrading-mattermost-to-next-major-release) and using the database schema version listed in the error messages for the version you select in Step 1) iv).
3. Once Mattermost is working again, you can use the same upgrade procedure to upgrade Mattermost version by version to your current GitLab version. After this is done, GitLab automation should continue to work for future upgrades, so long as you don't skip versions.
| GitLab Version | Mattermost Version |
diff --git a/docker/dev/config_docker.json b/docker/dev/config_docker.json
index 3a5195de3..1aa2ee843 100644
--- a/docker/dev/config_docker.json
+++ b/docker/dev/config_docker.json
@@ -11,7 +11,11 @@
"EnablePostIconOverride": false,
"EnableTesting": false,
"EnableDeveloper": false,
- "EnableSecurityFixAlert": true
+ "EnableSecurityFixAlert": true,
+ "SessionLengthWebInDays" : 30,
+ "SessionLengthMobileInDays" : 30,
+ "SessionLengthSSOInDays" : 30,
+ "SessionCacheInMinutes" : 10
},
"TeamSettings": {
"SiteName": "Mattermost",
diff --git a/docker/local/config_docker.json b/docker/local/config_docker.json
index 3a5195de3..1aa2ee843 100644
--- a/docker/local/config_docker.json
+++ b/docker/local/config_docker.json
@@ -11,7 +11,11 @@
"EnablePostIconOverride": false,
"EnableTesting": false,
"EnableDeveloper": false,
- "EnableSecurityFixAlert": true
+ "EnableSecurityFixAlert": true,
+ "SessionLengthWebInDays" : 30,
+ "SessionLengthMobileInDays" : 30,
+ "SessionLengthSSOInDays" : 30,
+ "SessionCacheInMinutes" : 10
},
"TeamSettings": {
"SiteName": "Mattermost",
diff --git a/manualtesting/manual_testing.go b/manualtesting/manual_testing.go
index ffdb578a4..b3d01a5a6 100644
--- a/manualtesting/manual_testing.go
+++ b/manualtesting/manual_testing.go
@@ -114,7 +114,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) {
Name: model.SESSION_COOKIE_TOKEN,
Value: client.AuthToken,
Path: "/",
- MaxAge: model.SESSION_TIME_WEB_IN_SECS,
+ MaxAge: *utils.Cfg.ServiceSettings.SessionLengthWebInDays * 60 * 60 * 24,
HttpOnly: true,
}
http.SetCookie(w, sessionCookie)
diff --git a/model/config.go b/model/config.go
index a4792ff9e..ed56ed0c7 100644
--- a/model/config.go
+++ b/model/config.go
@@ -36,6 +36,10 @@ type ServiceSettings struct {
EnableTesting bool
EnableDeveloper *bool
EnableSecurityFixAlert *bool
+ SessionLengthWebInDays *int
+ SessionLengthMobileInDays *int
+ SessionLengthSSOInDays *int
+ SessionCacheInMinutes *int
}
type SSOSettings struct {
@@ -306,6 +310,26 @@ func (o *Config) SetDefaults() {
o.LdapSettings.Enable = new(bool)
*o.LdapSettings.Enable = false
}
+
+ if o.ServiceSettings.SessionLengthWebInDays == nil {
+ o.ServiceSettings.SessionLengthWebInDays = new(int)
+ *o.ServiceSettings.SessionLengthWebInDays = 30
+ }
+
+ if o.ServiceSettings.SessionLengthMobileInDays == nil {
+ o.ServiceSettings.SessionLengthMobileInDays = new(int)
+ *o.ServiceSettings.SessionLengthMobileInDays = 30
+ }
+
+ if o.ServiceSettings.SessionLengthSSOInDays == nil {
+ o.ServiceSettings.SessionLengthSSOInDays = new(int)
+ *o.ServiceSettings.SessionLengthSSOInDays = 30
+ }
+
+ if o.ServiceSettings.SessionCacheInMinutes == nil {
+ o.ServiceSettings.SessionCacheInMinutes = new(int)
+ *o.ServiceSettings.SessionCacheInMinutes = 10
+ }
}
func (o *Config) IsValid() *AppError {
diff --git a/model/session.go b/model/session.go
index 5fe74a161..5d9424d64 100644
--- a/model/session.go
+++ b/model/session.go
@@ -9,18 +9,11 @@ import (
)
const (
- SESSION_COOKIE_TOKEN = "MMTOKEN"
- SESSION_TIME_WEB_IN_DAYS = 30
- SESSION_TIME_WEB_IN_SECS = 60 * 60 * 24 * SESSION_TIME_WEB_IN_DAYS
- SESSION_TIME_MOBILE_IN_DAYS = 30
- SESSION_TIME_MOBILE_IN_SECS = 60 * 60 * 24 * SESSION_TIME_MOBILE_IN_DAYS
- SESSION_TIME_OAUTH_IN_DAYS = 365
- SESSION_TIME_OAUTH_IN_SECS = 60 * 60 * 24 * SESSION_TIME_OAUTH_IN_DAYS
- SESSION_CACHE_IN_SECS = 60 * 10
- SESSION_CACHE_SIZE = 10000
- SESSION_PROP_PLATFORM = "platform"
- SESSION_PROP_OS = "os"
- SESSION_PROP_BROWSER = "browser"
+ SESSION_COOKIE_TOKEN = "MMTOKEN"
+ SESSION_CACHE_SIZE = 10000
+ SESSION_PROP_PLATFORM = "platform"
+ SESSION_PROP_OS = "os"
+ SESSION_PROP_BROWSER = "browser"
)
type Session struct {
@@ -89,8 +82,8 @@ func (me *Session) IsExpired() bool {
return false
}
-func (me *Session) SetExpireInDays(days int64) {
- me.ExpiresAt = GetMillis() + (1000 * 60 * 60 * 24 * days)
+func (me *Session) SetExpireInDays(days int) {
+ me.ExpiresAt = GetMillis() + (1000 * 60 * 60 * 24 * int64(days))
}
func (me *Session) AddProp(key string, value string) {
diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx
index e235819fe..f10721ffa 100644
--- a/web/react/components/admin_console/service_settings.jsx
+++ b/web/react/components/admin_console/service_settings.jsx
@@ -4,6 +4,10 @@
import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
+const DefaultSessionLength = 30;
+const DefaultMaximumLoginAttempts = 10;
+const DefaultSessionCacheInMinutes = 10;
+
export default class ServiceSettings extends React.Component {
constructor(props) {
super(props);
@@ -45,13 +49,56 @@ export default class ServiceSettings extends React.Component {
//config.ServiceSettings.EnableOAuthServiceProvider = ReactDOM.findDOMNode(this.refs.EnableOAuthServiceProvider).checked;
- var MaximumLoginAttempts = 10;
+ var MaximumLoginAttempts = DefaultMaximumLoginAttempts;
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MaximumLoginAttempts).value, 10))) {
MaximumLoginAttempts = parseInt(ReactDOM.findDOMNode(this.refs.MaximumLoginAttempts).value, 10);
}
+ if (MaximumLoginAttempts < 1) {
+ MaximumLoginAttempts = 1;
+ }
config.ServiceSettings.MaximumLoginAttempts = MaximumLoginAttempts;
ReactDOM.findDOMNode(this.refs.MaximumLoginAttempts).value = MaximumLoginAttempts;
+ var SessionLengthWebInDays = DefaultSessionLength;
+ if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.SessionLengthWebInDays).value, 10))) {
+ SessionLengthWebInDays = parseInt(ReactDOM.findDOMNode(this.refs.SessionLengthWebInDays).value, 10);
+ }
+ if (SessionLengthWebInDays < 1) {
+ SessionLengthWebInDays = 1;
+ }
+ config.ServiceSettings.SessionLengthWebInDays = SessionLengthWebInDays;
+ ReactDOM.findDOMNode(this.refs.SessionLengthWebInDays).value = SessionLengthWebInDays;
+
+ var SessionLengthMobileInDays = DefaultSessionLength;
+ if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.SessionLengthMobileInDays).value, 10))) {
+ SessionLengthMobileInDays = parseInt(ReactDOM.findDOMNode(this.refs.SessionLengthMobileInDays).value, 10);
+ }
+ if (SessionLengthMobileInDays < 1) {
+ SessionLengthMobileInDays = 1;
+ }
+ config.ServiceSettings.SessionLengthMobileInDays = SessionLengthMobileInDays;
+ ReactDOM.findDOMNode(this.refs.SessionLengthMobileInDays).value = SessionLengthMobileInDays;
+
+ var SessionLengthSSOInDays = DefaultSessionLength;
+ if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.SessionLengthSSOInDays).value, 10))) {
+ SessionLengthSSOInDays = parseInt(ReactDOM.findDOMNode(this.refs.SessionLengthSSOInDays).value, 10);
+ }
+ if (SessionLengthSSOInDays < 1) {
+ SessionLengthSSOInDays = 1;
+ }
+ config.ServiceSettings.SessionLengthSSOInDays = SessionLengthSSOInDays;
+ ReactDOM.findDOMNode(this.refs.SessionLengthSSOInDays).value = SessionLengthSSOInDays;
+
+ var SessionCacheInMinutes = DefaultSessionCacheInMinutes;
+ if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.SessionCacheInMinutes).value, 10))) {
+ SessionCacheInMinutes = parseInt(ReactDOM.findDOMNode(this.refs.SessionCacheInMinutes).value, 10);
+ }
+ if (SessionCacheInMinutes < -1) {
+ SessionCacheInMinutes = -1;
+ }
+ config.ServiceSettings.SessionCacheInMinutes = SessionCacheInMinutes;
+ ReactDOM.findDOMNode(this.refs.SessionCacheInMinutes).value = SessionCacheInMinutes;
+
Client.saveConfig(
config,
() => {
@@ -417,6 +464,90 @@ export default class ServiceSettings extends React.Component {
</div>
<div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='SessionLengthWebInDays'
+ >
+ {'Session Length for Web in Days:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='SessionLengthWebInDays'
+ ref='SessionLengthWebInDays'
+ placeholder='Ex "30"'
+ defaultValue={this.props.config.ServiceSettings.SessionLengthWebInDays}
+ onChange={this.handleChange}
+ />
+ <p className='help-text'>{'The web session will expire after the number of days specified and will require a user to login again.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='SessionLengthMobileInDays'
+ >
+ {'Session Length for Mobile Device in Days:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='SessionLengthMobileInDays'
+ ref='SessionLengthMobileInDays'
+ placeholder='Ex "30"'
+ defaultValue={this.props.config.ServiceSettings.SessionLengthMobileInDays}
+ onChange={this.handleChange}
+ />
+ <p className='help-text'>{'The native mobile session will expire after the number of days specified and will require a user to login again.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='SessionLengthSSOInDays'
+ >
+ {'Session Length for SSO in Days:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='SessionLengthSSOInDays'
+ ref='SessionLengthSSOInDays'
+ placeholder='Ex "30"'
+ defaultValue={this.props.config.ServiceSettings.SessionLengthSSOInDays}
+ onChange={this.handleChange}
+ />
+ <p className='help-text'>{'The SSO session will expire after the number of days specified and will require a user to login again.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='SessionCacheInMinutes'
+ >
+ {'Session Cache in Minutes:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='SessionCacheInMinutes'
+ ref='SessionCacheInMinutes'
+ placeholder='Ex "30"'
+ defaultValue={this.props.config.ServiceSettings.SessionCacheInMinutes}
+ onChange={this.handleChange}
+ />
+ <p className='help-text'>{'The number of minutes to cache a session in memory.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
<div className='col-sm-12'>
{serverError}
<button
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx
index c190b4dd8..cae94429c 100644
--- a/web/react/components/create_comment.jsx
+++ b/web/react/components/create_comment.jsx
@@ -32,7 +32,6 @@ export default class CreateComment extends React.Component {
this.handleUploadStart = this.handleUploadStart.bind(this);
this.handleFileUploadComplete = this.handleFileUploadComplete.bind(this);
this.handleUploadError = this.handleUploadError.bind(this);
- this.handleTextDrop = this.handleTextDrop.bind(this);
this.removePreview = this.removePreview.bind(this);
this.getFileCount = this.getFileCount.bind(this);
this.handleResize = this.handleResize.bind(this);
@@ -239,11 +238,6 @@ export default class CreateComment extends React.Component {
this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err});
}
}
- handleTextDrop(text) {
- const newText = this.state.messageText + text;
- this.handleUserInput(newText);
- Utils.setCaretPosition(ReactDOM.findDOMNode(this.refs.textbox.refs.message), newText.length);
- }
removePreview(id) {
let previews = this.state.previews;
let uploadsInProgress = this.state.uploadsInProgress;
@@ -344,7 +338,6 @@ export default class CreateComment extends React.Component {
onUploadStart={this.handleUploadStart}
onFileUpload={this.handleFileUploadComplete}
onUploadError={this.handleUploadError}
- onTextDrop={this.handleTextDrop}
postType='comment'
channelId={this.props.channelId}
/>
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index e901b272a..a476863a3 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -40,7 +40,6 @@ export default class CreatePost extends React.Component {
this.handleUploadStart = this.handleUploadStart.bind(this);
this.handleFileUploadComplete = this.handleFileUploadComplete.bind(this);
this.handleUploadError = this.handleUploadError.bind(this);
- this.handleTextDrop = this.handleTextDrop.bind(this);
this.removePreview = this.removePreview.bind(this);
this.onChange = this.onChange.bind(this);
this.onPreferenceChange = this.onPreferenceChange.bind(this);
@@ -281,11 +280,6 @@ export default class CreatePost extends React.Component {
this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: message});
}
}
- handleTextDrop(text) {
- const newText = this.state.messageText + text;
- this.handleUserInput(newText);
- Utils.setCaretPosition(ReactDOM.findDOMNode(this.refs.textbox.refs.message), newText.length);
- }
removePreview(id) {
const previews = Object.assign([], this.state.previews);
const uploadsInProgress = this.state.uploadsInProgress;
@@ -462,7 +456,6 @@ export default class CreatePost extends React.Component {
onUploadStart={this.handleUploadStart}
onFileUpload={this.handleFileUploadComplete}
onUploadError={this.handleUploadError}
- onTextDrop={this.handleTextDrop}
postType='post'
channelId=''
/>
diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx
index 9316ca9a5..a0c930ffb 100644
--- a/web/react/components/file_upload.jsx
+++ b/web/react/components/file_upload.jsx
@@ -109,8 +109,6 @@ export default class FileUpload extends React.Component {
if (typeof files !== 'string' && files.length) {
this.uploadFiles(files);
- } else {
- this.props.onTextDrop(e.originalEvent.dataTransfer.getData('Text'));
}
}
@@ -120,11 +118,19 @@ export default class FileUpload extends React.Component {
if (this.props.postType === 'post') {
$('.row.main').dragster({
- enter() {
- $('.center-file-overlay').removeClass('hidden');
+ enter(dragsterEvent, e) {
+ var files = e.originalEvent.dataTransfer;
+
+ if (utils.isFileTransfer(files)) {
+ $('.center-file-overlay').removeClass('hidden');
+ }
},
- leave() {
- $('.center-file-overlay').addClass('hidden');
+ leave(dragsterEvent, e) {
+ var files = e.originalEvent.dataTransfer;
+
+ if (utils.isFileTransfer(files)) {
+ $('.center-file-overlay').addClass('hidden');
+ }
},
drop(dragsterEvent, e) {
$('.center-file-overlay').addClass('hidden');
@@ -133,11 +139,19 @@ export default class FileUpload extends React.Component {
});
} else if (this.props.postType === 'comment') {
$('.post-right__container').dragster({
- enter() {
- $('.right-file-overlay').removeClass('hidden');
+ enter(dragsterEvent, e) {
+ var files = e.originalEvent.dataTransfer;
+
+ if (utils.isFileTransfer(files)) {
+ $('.right-file-overlay').removeClass('hidden');
+ }
},
- leave() {
- $('.right-file-overlay').addClass('hidden');
+ leave(dragsterEvent, e) {
+ var files = e.originalEvent.dataTransfer;
+
+ if (utils.isFileTransfer(files)) {
+ $('.right-file-overlay').addClass('hidden');
+ }
},
drop(dragsterEvent, e) {
$('.right-file-overlay').addClass('hidden');
@@ -229,6 +243,18 @@ export default class FileUpload extends React.Component {
});
}
+ componentWillUnmount() {
+ let target;
+ if (this.props.postType === 'post') {
+ target = $('.row.main');
+ } else {
+ target = $('.post-right__container');
+ }
+
+ // jquery-dragster doesn't provide a function to unregister itself so do it manually
+ target.off('dragenter dragleave dragover drop dragster:enter dragster:leave dragster:over dragster:drop');
+ }
+
cancelUpload(clientId) {
var requests = this.state.requests;
var request = requests[clientId];
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 33aae7d1e..95eca7c3a 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -1276,3 +1276,9 @@ export function fillArray(value, length) {
return arr;
}
+
+// Checks if a data transfer contains files not text, folders, etc..
+// Slightly modified from http://stackoverflow.com/questions/6848043/how-do-i-detect-a-file-is-being-dragged-rather-than-a-draggable-element-on-my-pa
+export function isFileTransfer(files) {
+ return files.types != null && (files.types.indexOf ? files.types.indexOf('Files') !== -1 : files.types.contains('application/x-moz-file'));
+}
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index 937b08084..31ad586ba 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -417,12 +417,6 @@ body.ios {
background-color: beige;
}
- ul {
- margin: 0;
- padding: 0;
- }
-
-
p {
margin: 0;
line-height: 1.6em;
@@ -671,20 +665,17 @@ body.ios {
@include legacy-pie-clearfix;
width: calc(100% - 75px);
- img {
- max-height: 400px;
- }
-
- ul {
- margin-bottom: 0.6em;
- padding: 5px 0 0 20px;
+ p {
+ margin: 0 0 0.4em;
}
- ul + p {
- margin-top: 1em;
+ img {
+ max-height: 400px;
}
ul, ol {
+ margin-bottom: 0.4em;
+
p {
margin-bottom: 0;
}
diff --git a/web/web.go b/web/web.go
index 30a70ba2e..bf1208adc 100644
--- a/web/web.go
+++ b/web/web.go
@@ -998,7 +998,7 @@ func getAccessToken(c *api.Context, w http.ResponseWriter, r *http.Request) {
return
}
- accessRsp := &model.AccessResponse{AccessToken: session.Token, TokenType: model.ACCESS_TOKEN_TYPE, ExpiresIn: model.SESSION_TIME_OAUTH_IN_SECS}
+ accessRsp := &model.AccessResponse{AccessToken: session.Token, TokenType: model.ACCESS_TOKEN_TYPE, ExpiresIn: int32(*utils.Cfg.ServiceSettings.SessionLengthSSOInDays * 60 * 60 * 24)}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-store")