Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send notification for new device #6555

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 57 additions & 6 deletions frontend/src/components/user-settings/email-notice.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import React from 'react';
import { gettext } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { userAPI } from '../../utils/user-api';
import { Utils } from '../../utils/utils';
import toaster from '../toast';

const {
fileUpdatesEmailInterval,
collaborateEmailInterval
collaborateEmailInterval,
enableLoginEmail,
enablePasswordUpdateEmail,

} = window.app.pageOptions;

class EmailNotice extends React.Component {
Expand All @@ -28,9 +31,21 @@ class EmailNotice extends React.Component {
{ interval: 3600, text: gettext('Per hour') + ' (' + gettext('If notifications have not been read within one hour, they will be sent to your mailbox.') + ')' }
];

this.passwordOption = [
{ enabled: 0, text: gettext('Don\'t send emails') },
{ enabled: 1, text: gettext('Send email after changing password') }
];

this.loginOption = [
{ enabled: 0, text: gettext('Don\'t send emails') },
{ enabled: 1, text: gettext('Send an email when a new device or browser logs in for the first time') }
];

this.state = {
fileUpdatesEmailInterval: fileUpdatesEmailInterval,
collaborateEmailInterval: collaborateEmailInterval
collaborateEmailInterval: collaborateEmailInterval,
enableLoginEmail: enableLoginEmail,
enablePasswordUpdateEmail: enablePasswordUpdateEmail
};
}

Expand All @@ -50,10 +65,26 @@ class EmailNotice extends React.Component {
}
};

inputPasswordEmailEnabledChange = (e) => {
if (e.target.checked) {
this.setState({
enablePasswordUpdateEmail: parseInt(e.target.value)
});
}
};

inputLoginEmailEnabledChange = (e) => {
if (e.target.checked) {
this.setState({
enableLoginEmail: parseInt(e.target.value)
});
}
};

formSubmit = (e) => {
e.preventDefault();
let { fileUpdatesEmailInterval, collaborateEmailInterval } = this.state;
seafileAPI.updateEmailNotificationInterval(fileUpdatesEmailInterval, collaborateEmailInterval).then((res) => {
let { fileUpdatesEmailInterval, collaborateEmailInterval, enablePasswordUpdateEmail, enableLoginEmail } = this.state;
userAPI.updateEmailNotificationConfig(fileUpdatesEmailInterval, collaborateEmailInterval, enablePasswordUpdateEmail, enableLoginEmail).then((res) => {
toaster.success(gettext('Success'));
}).catch((error) => {
let errorMsg = Utils.getErrorMsg(error);
Expand All @@ -62,7 +93,7 @@ class EmailNotice extends React.Component {
};

render() {
const { fileUpdatesEmailInterval, collaborateEmailInterval } = this.state;
const { fileUpdatesEmailInterval, collaborateEmailInterval, enableLoginEmail, enablePasswordUpdateEmail } = this.state;
return (
<div className="setting-item" id="email-notice">
<h3 className="setting-item-heading">{gettext('Email Notification')}</h3>
Expand All @@ -88,6 +119,26 @@ class EmailNotice extends React.Component {
</div>
);
})}

<h4 className="mt-3 h6">{gettext('Notifications of change password')}</h4>
{this.passwordOption.map((item, index) => {
return (
<div className="d-flex align-items-start" key={`password-${index}`}>
<input type="radio" name="pwd-interval" value={item.enabled} className="mt-1" id={`password-interval-option-${index + 1}`} checked={enablePasswordUpdateEmail == item.enabled} onChange={this.inputPasswordEmailEnabledChange} />
<label className="m-0 ml-2" htmlFor={`password-interval-option-${index + 1}`}>{item.text}</label>
</div>
);
})}

<h4 className="mt-3 h6">{gettext(' Send a mail as soon as a new device or browser has signed into the account')}</h4>
{this.loginOption.map((item, index) => {
return (
<div className="d-flex" key={`login-updates-${index}`}>
<input type="radio" name="login-interval" value={item.enabled} id={`login-updates-interval-option-${index + 1}`} checked={enableLoginEmail == item.enabled} onChange={this.inputLoginEmailEnabledChange} />
<label className="m-0 ml-2" htmlFor={`login-updates-interval-option-${index + 1}`}>{item.text}</label>
</div>
);
})}
<button type="submit" className="btn btn-outline-primary mt-4">{gettext('Submit')}</button>
</form>
</div>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/dashboard/activity-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ class ActivityItem extends Component {
this.setState({
isHighlighted: true
});
}
};

onMouseLeave = () => {
this.setState({
isHighlighted: false
});
}
};

render() {
const { isHighlighted } = this.state;
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/utils/user-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ class UserAPI {
};
return this.req.post(url, data);
}

updateEmailNotificationConfig(fileUpdatesEmailInterval, collaborateEmailInterval, enablePasswordUpdateEmail, enableLoginEmail) {
let url = this.server + '/api2/account/info/';
let data = {
'file_updates_email_interval': fileUpdatesEmailInterval,
'collaborate_email_interval': collaborateEmailInterval,
'enable_password_update_email': enablePasswordUpdateEmail,
'enable_login_email': enableLoginEmail
};
return this.req.put(url, data);
}

}

let userAPI = new UserAPI();
Expand Down
20 changes: 19 additions & 1 deletion seahub/api2/endpoints/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@
from seahub.organizations.settings import ORG_AUTO_URL_PREFIX
from seahub.organizations.views import gen_org_url_prefix
from seahub.password_session import update_session_auth_hash
from seahub.utils import is_valid_email
from seahub.utils import is_valid_email, send_html_email, get_site_name, IS_EMAIL_CONFIGURED
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error
from seahub.base.templatetags.seahub_tags import email2nickname, \
email2contact_email
from seahub.profile.models import Profile, DetailedProfile
from seahub.settings import ENABLE_UPDATE_USER_INFO, ENABLE_USER_SET_CONTACT_EMAIL, ENABLE_CONVERT_TO_TEAM_ACCOUNT
from seahub.options.models import UserOptions


import seaserv
from seaserv import ccnet_api, seafile_api
Expand Down Expand Up @@ -245,6 +247,22 @@ def post(self, request):

user.set_password(new_password)
user.save()
enable_pwd_email = bool(UserOptions.objects.get_password_update_email_enable_status(user.username))

if IS_EMAIL_CONFIGURED and enable_pwd_email:
email_template_name = 'registration/password_change_email.html'
send_to = email2contact_email(request.user.username)
site_name = get_site_name()
c = {
'email': send_to,
'name': email2nickname(user.username)
}
try:
send_html_email(_("[%s]Your Password Has Been Successfully Updated") % site_name,
email_template_name, c, None,
[send_to])
except Exception as e:
logger.error('Failed to send notification to %s' % send_to)

if not request.session.is_empty():
# update session auth hash
Expand Down
17 changes: 14 additions & 3 deletions seahub/api2/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
get_group, seafserv_threaded_rpc
from pysearpc import SearpcError

from seahub.auth.utils import send_login_email
from seahub.base.templatetags.seahub_tags import email2nickname, \
translate_seahub_time, file_icon_filter, email2contact_email
from seahub.constants import REPO_TYPE_WIKI
Expand All @@ -30,10 +31,12 @@
from seahub.api2.models import Token, TokenV2, DESKTOP_PLATFORMS
from seahub.avatar.settings import AVATAR_DEFAULT_SIZE
from seahub.avatar.templatetags.avatar_tags import api_avatar_url
from seahub.utils import get_user_repos
from seahub.utils import get_user_repos, IS_EMAIL_CONFIGURED
from seahub.utils.mail import send_html_email_with_dj_template
from django.utils.translation import gettext as _
from seahub.settings import SECRET_KEY
from seahub.utils import send_html_email, get_site_name
from seahub.options.models import UserOptions

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -188,6 +191,7 @@ def get_token_v1(username):
return token

_ANDROID_DEVICE_ID_PATTERN = re.compile('^[a-f0-9]{1,16}$')

def get_token_v2(request, username, platform, device_id, device_name,
client_version, platform_version):

Expand All @@ -207,10 +211,17 @@ def get_token_v2(request, username, platform, device_id, device_name,
raise serializers.ValidationError('invalid device id')
else:
raise serializers.ValidationError('invalid platform')

return TokenV2.objects.get_or_create_token(
enable_new_device_email = bool(UserOptions.objects.get_login_email_enable_status(username))

has_device = TokenV2.objects.filter(user=username, device_id=device_id, platform=platform).first()
token = TokenV2.objects.get_or_create_token(
username, platform, device_id, device_name,
client_version, platform_version, get_client_ip(request))

if IS_EMAIL_CONFIGURED and enable_new_device_email and (not has_device):
send_login_email(username)

return token

def get_api_token(request, keys=None, key_prefix='shib_'):

Expand Down
23 changes: 23 additions & 0 deletions seahub/api2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,21 @@ def put(self, request, format=None):
return api_error(status.HTTP_400_BAD_REQUEST,
'collaborate_email_interval invalid')

enable_login_email = request.data.get("enable_login_email", None)
if enable_login_email is not None:
try:
enable_login_email = int(enable_login_email)
except ValueError:
return api_error(status.HTTP_400_BAD_REQUEST,
'enable_login_email invalid')

enable_password_update_email = request.data.get("enable_password_update_email", None)
if enable_password_update_email is not None:
try:
enable_password_update_email = int(enable_password_update_email)
except ValueError:
return api_error(status.HTTP_400_BAD_REQUEST,
'enable_password_update_email invalid')
# update user info

if name is not None:
Expand All @@ -403,6 +418,14 @@ def put(self, request, format=None):
UserOptions.objects.set_collaborate_email_interval(
username, collaborate_email_interval)

if enable_password_update_email is not None:
UserOptions.objects.set_password_update_email_enable_status(
username, enable_password_update_email)

if enable_login_email is not None:
UserOptions.objects.set_login_email_enable_status(
username, enable_login_email)

return Response(self._get_account_info(request))


Expand Down
6 changes: 6 additions & 0 deletions seahub/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from constance import config

SESSION_KEY = '_auth_user_name'
SESSION_USERS_LOGIN = '_already_loging_users'
BACKEND_SESSION_KEY = '_auth_user_backend_2'
REDIRECT_FIELD_NAME = 'next'

Expand Down Expand Up @@ -106,8 +107,13 @@ def logout(request):
session data.
Also remove all passwords used to decrypt repos.
"""
already_logged_list = request.session.get(SESSION_USERS_LOGIN, [])
request.session.flush()
if hasattr(request, 'user'):
username = request.user.username
if username not in already_logged_list:
already_logged_list.append(username)
request.session[SESSION_USERS_LOGIN] = already_logged_list
from seahub.base.accounts import User
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

把 if request.user.username 写到if里面来

if isinstance(request.user, User):
# Do not directly/indirectly import models in package root level.
Expand Down
22 changes: 20 additions & 2 deletions seahub/auth/utils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# Copyright (c) 2012-2016 Seafile Ltd.
import logging
from django.core.cache import cache
from django.conf import settings
from django.utils.translation import gettext as _

from seahub.profile.models import Profile
from seahub.utils import normalize_cache_key
from seahub.utils import normalize_cache_key, get_site_name, send_html_email
from seahub.utils.ip import get_remote_ip

LOGIN_ATTEMPT_PREFIX = 'UserLoginAttempt_'

logger = logging.getLogger(__name__)

def get_login_failed_attempts(username=None, ip=None):
"""Get login failed attempts base on username and ip.
Expand Down Expand Up @@ -84,3 +86,19 @@ def get_virtual_id_by_email(email):
return email
else:
return p.user


def send_login_email(username):
from seahub.base.templatetags.seahub_tags import email2contact_email, email2nickname
email_template_name = 'registration/login_email.html'
send_to = email2contact_email(username)
site_name = get_site_name()
c = {
'name': email2nickname(username)
}
try:
send_html_email(_("Welcome to %s") % site_name,
email_template_name, c, None,
[send_to])
except Exception as e:
logger.error('Failed to send notification to %s, %s' % (send_to, e))
17 changes: 13 additions & 4 deletions seahub/auth/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from seaserv import seafile_api, ccnet_api

from seahub.auth import REDIRECT_FIELD_NAME, get_backends
from seahub.auth import login as auth_login
from seahub.auth import login as auth_login, SESSION_USERS_LOGIN
from seahub.auth.models import SocialAuthUser
from seahub.auth.decorators import login_required
from seahub.auth.forms import AuthenticationForm, CaptchaAuthenticationForm, \
Expand All @@ -29,12 +29,12 @@
from seahub.auth.tokens import default_token_generator
from seahub.auth.utils import (
get_login_failed_attempts, incr_login_failed_attempts,
clear_login_failed_attempts)
clear_login_failed_attempts, send_login_email)
from seahub.base.accounts import User, UNUSABLE_PASSWORD
from seahub.options.models import UserOptions
from seahub.profile.models import Profile
from seahub.two_factor.views.login import is_device_remembered
from seahub.utils import render_error, get_site_name, is_valid_email, get_service_url
from seahub.utils import render_error, get_site_name, is_valid_email, get_service_url, IS_EMAIL_CONFIGURED
from seahub.utils.http import rate_limit
from seahub.utils.ip import get_remote_ip
from seahub.utils.file_size import get_quota_from_string
Expand All @@ -50,6 +50,10 @@

from seahub.onlyoffice.settings import ONLYOFFICE_DESKTOP_EDITOR_HTTP_USER_AGENT

from seahub.utils import send_html_email
from django.utils.translation import gettext_lazy as _
from seahub.base.templatetags.seahub_tags import email2contact_email, email2nickname

# Get an instance of a logger
logger = logging.getLogger(__name__)

Expand All @@ -73,6 +77,11 @@ def log_user_in(request, user, redirect_to):

# Okay, security checks complete. Log the user in.
auth_login(request, user)
enable_login_email = bool(UserOptions.objects.get_login_email_enable_status(user.username))
already_login_users = request.session.get(SESSION_USERS_LOGIN, [])

if IS_EMAIL_CONFIGURED and (user.username not in already_login_users) and enable_login_email:
send_login_email(user.username)

return HttpResponseRedirect(redirect_to)

Expand All @@ -89,6 +98,7 @@ def _handle_login_form_valid(request, user, redirect_to, remember_me):

# password is valid, log user in
request.session['remember_me'] = remember_me

return log_user_in(request, user, redirect_to)

@csrf_protect
Expand All @@ -107,7 +117,6 @@ def login(request, template_name='registration/login.html',
return HttpResponseRedirect(reverse(redirect_if_logged_in))

ip = get_remote_ip(request)

if request.method == "POST":
login = request.POST.get('login', '').strip()
failed_attempt = get_login_failed_attempts(username=login, ip=ip)
Expand Down
Loading
Loading