@@ -285,24 +285,28 @@ def make_map(config):
#LOGIN/LOGOUT/REGISTER/SIGN IN
rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
action='logout')
rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
action='register')
rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
controller='login', action='password_reset')
rmap.connect('reset_password_confirmation',
'%s/password_reset_confirmation' % ADMIN_PREFIX,
controller='login', action='password_reset_confirmation')
#FEEDS
rmap.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
controller='feed', action='rss',
conditions=dict(function=check_repo))
rmap.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
controller='feed', action='atom',
#==========================================================================
# REPOSITORY ROUTES
@@ -120,32 +120,49 @@ class LoginController(BaseController):
prefix_error=False,
encoding="UTF-8")
return render('/register.html')
def password_reset(self):
user_model = UserModel()
if request.POST:
password_reset_form = PasswordResetForm()()
try:
form_result = password_reset_form.to_python(dict(request.POST))
user_model.reset_password(form_result)
h.flash(_('Your new password was sent'),
user_model.reset_password_link(form_result)
h.flash(_('Your password reset link was sent'),
category='success')
return redirect(url('login_home'))
except formencode.Invalid, errors:
return htmlfill.render(
render('/password_reset.html'),
defaults=errors.value,
errors=errors.error_dict or {},
return render('/password_reset.html')
def password_reset_confirmation(self):
if request.GET and request.GET.get('key'):
user = User.get_by_api_key(request.GET.get('key'))
data = dict(email=user.email)
user_model.reset_password(data)
h.flash(_('Your password reset was successful, '
'new password has been sent to your email'),
except Exception, e:
log.error(e)
return redirect(url('reset_password'))
def logout(self):
del session['rhodecode_user']
session.save()
log.info('Logging out and setting user as Empty')
redirect(url('home'))
@@ -25,25 +25,25 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from celery.decorators import task
import os
import traceback
import logging
from os.path import dirname as dn, join as jn
from time import mktime
from operator import itemgetter
from string import lower
from pylons import config
from pylons import config, url
from pylons.i18n.translation import _
from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
__get_lockkey, LockHeld, DaemonLock
from rhodecode.lib.helpers import person
from rhodecode.lib.smtp_mailer import SmtpMailer
from rhodecode.lib.utils import add_cache
from rhodecode.lib.odict import OrderedDict
from rhodecode.model import init_model
from rhodecode.model import meta
from rhodecode.model.db import RhodeCodeUi, Statistics, Repository
@@ -240,24 +240,63 @@ def get_commits_stats(repo_name, ts_min_
#final release
lock.release()
#execute another task if celery is enabled
if len(repo.revisions) > 1 and CELERY_ON:
run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
return True
except LockHeld:
log.info('LockHeld')
return 'Task with key %s already running' % lockkey
@task(ignore_result=True)
def send_password_link(user_email):
log = reset_user_password.get_logger()
except:
log = logging.getLogger(__name__)
from rhodecode.lib import auth
from rhodecode.model.db import User
sa = get_session()
user = sa.query(User).filter(User.email == user_email).scalar()
if user:
link = url('reset_password_confirmation', key=user.api_key,
qualified=True)
tmpl = """
Hello %s
We received a request to create a new password for your account.
You can generate it by clicking following URL:
%s
If you didn't request new password please ignore this email.
"""
run_task(send_email, user_email,
"RhodeCode password reset link",
tmpl % (user.short_contact, link))
log.info('send new password mail to %s', user_email)
log.error('Failed to update user password')
log.error(traceback.format_exc())
return False
def reset_user_password(user_email):
@@ -271,26 +310,26 @@ def reset_user_password(user_email):
user.api_key = auth.generate_api_key(user.username)
sa.add(user)
sa.commit()
log.info('change password for %s', user_email)
if new_passwd is None:
raise Exception('unable to generate new password')
sa.rollback()
"Your new rhodecode password",
'Your new rhodecode password:%s' % (new_passwd))
"Your new RhodeCode password",
'Your new RhodeCode password:%s' % (new_passwd))
def send_email(recipients, subject, body):
@@ -65,31 +65,37 @@ class SmtpMailer(object):
if self.debug:
smtp_serv.set_debuglevel(1)
smtp_serv.ehlo()
#if server requires authorization you must provide login and password
#but only if we have them
if self.user and self.passwd:
smtp_serv.login(self.user, self.passwd)
date_ = formatdate(localtime=True)
msg = MIMEMultipart()
msg.set_type('multipart/alternative')
msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
text_msg = MIMEText(body)
text_msg.set_type('text/plain')
text_msg.set_param('charset', 'UTF-8')
msg['From'] = self.mail_from
msg['To'] = ','.join(recipients)
msg['Date'] = date_
msg['Subject'] = subject
msg.attach(MIMEText(body))
msg.attach(text_msg)
if attachment_files:
self.__atach_files(msg, attachment_files)
smtp_serv.sendmail(self.mail_from, recipients, msg.as_string())
logging.info('MAIL SEND TO: %s' % recipients)
smtp_serv.quit()
except sslerror:
# sslerror is raised in tls connections on closing sometimes
pass
@@ -234,24 +234,29 @@ class User(Base, BaseModel):
return "<%s('id:%s:%s')>" % (self.__class__.__name__,
self.user_id, self.username)
return self.__class__.__name__
@classmethod
def by_username(cls, username, case_insensitive=False):
if case_insensitive:
return Session.query(cls).filter(cls.username.like(username)).one()
else:
return Session.query(cls).filter(cls.username == username).one()
def get_by_api_key(cls, api_key):
return Session.query(cls).filter(cls.api_key == api_key).one()
def update_lastlogin(self):
"""Update user lastlogin"""
self.last_login = datetime.datetime.now()
Session.add(self)
Session.commit()
log.debug('updated user %s lastlogin', self.username)
class UserLog(Base, BaseModel):
__tablename__ = 'user_logs'
__table_args__ = {'extend_existing':True}
@@ -204,24 +204,28 @@ class UserModel(BaseModel):
raise UserOwnsReposException(_('This user still owns %s '
'repositories and cannot be '
'removed. Switch owners or '
'remove those repositories') \
% user.repositories)
self.sa.delete(user)
self.sa.commit()
self.sa.rollback()
raise
def reset_password_link(self, data):
from rhodecode.lib.celerylib import tasks, run_task
run_task(tasks.send_password_link, data['email'])
def reset_password(self, data):
run_task(tasks.reset_user_password, data['email'])
def fill_data(self, auth_user, user_id=None, api_key=None):
Fetches auth_user by user_id,or api_key if present.
Fills auth_user attributes with those taken from database.
Additionally set's is_authenitated if lookup fails
present in database
:param auth_user: instance of user to set attributes
@@ -189,25 +189,26 @@ border-bottom-right-radius: 8px;
margin:0;
padding:0 10px;
}
#header ul#logged-user{
margin-bottom:5px !important;
-webkit-border-radius: 0px 0px 8px 8px;
-khtml-border-radius: 0px 0px 8px 8px;
-moz-border-radius: 0px 0px 8px 8px;
border-radius: 0px 0px 8px 8px;
height:37px;
background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367
background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
#header ul#logged-user li {
list-style:none;
float:left;
margin:8px 0 0;
padding:4px 12px;
border-left: 1px solid #316293;
#header ul#logged-user li.first {
border-left:none;
@@ -1374,24 +1375,31 @@ margin:10px 0 0;
padding:0 0 2px;
#quick_login{
top: 31px;
background-color: rgb(0, 51, 103);
z-index: 999;
height: 150px;
position: absolute;
margin-left: -16px;
width: 281px;
border-radius: 0 0 8px 8px;
#quick_login .password_forgoten{
padding-right:10px;
padding-top:10px;
#quick_login div.form div.fields{
padding-top: 2px;
padding-left:10px;
#quick_login div.form div.fields div.field{
padding: 5px;
#quick_login div.form div.fields div.field div.label label{
@@ -21,25 +21,25 @@
</div>
<div class="field">
<div class="label">
<label for="password">${_('Password')}:</label>
<div class="input">
${h.password('password',class_='focus',size=40)}
<div class="buttons">
${h.submit('sign_in','Sign In',class_="ui-button")}
<div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>${h.submit('sign_in','Sign In',class_="ui-button")}
${h.end_form()}
<script type="text/javascript">
YUE.on('quick_login_link','click',function(e){
if(YUD.hasClass('quick_login_link','enabled')){
YUD.setStyle('quick_login','display','none');
YUD.removeClass('quick_login_link','enabled');
else{
@@ -19,25 +19,25 @@
<label for="email">${_('Email address')}:</label>
${h.text('email')}
<div class="nohighlight">
${h.submit('send','Reset my password',class_="ui-button")}
<div class="activation_msg">${_('Your new password will be send to matching email address')}</div>
<div class="activation_msg">${_('Password reset link will be send to matching email address')}</div>
YUE.onDOMReady(function(){
YUD.get('email').focus();
})
</script>
new file 100644
Status change: