def set_password(user, new_token, delay_commit=False): """ Set password WARNING! We assume the user has already been authenticated - remove old password (if found) - create new password record """ # search for existing record and remove it # try: for existing_login in [login for login in user.login_details if login.type == 'password']: log.debug("removing password for %s" % user.username) #if existing_login.token == old_token: raise Exception('old password token does not match - aborting password change') Session.delete(existing_login) log.debug("removed ok") #try: Session.execute(UserLogin.__table__.delete().where(and_(UserLogin.__table__.c.member_id == user.id, UserLogin.__table__.c.token == token))) except Exception: pass # Set new password u_login = UserLogin() u_login.user = user u_login.type = 'password' u_login.token = new_token Session.add(u_login) if not delay_commit: Session.commit()
def associate_janrain_account(user, type, token): """ Associate a login record for a Janrain account This is called at: 1.) Registration 2.) Linking multiple login accounts to a single Civicboom account """ login = None try: login = Session.query(UserLogin).filter(UserLogin.token == token).filter(UserLogin.type == type).one() except: pass if login: if login.user == user: return # If login already belongs to this user then abort if login.user: # Warn existing user that account is being reallocated login.user.send_email(subject=_('login account reallocated'), content_text=_('your %s account has been allocated to the user %s') % (type, user.username)) if not config['development_mode']: janrain('unmap', identifier=login.token, primaryKey=login.member_id) login.user = user else: login = UserLogin() login.user = user login.type = type login.token = token Session.add(login) Session.commit() if not config['development_mode']: janrain('map', identifier=login.token, primaryKey=login.member_id) # Let janrain know this users primary key id, this is needed for agrigation posts
def respond_assignment(parent_content, member, delay_commit=False): """ When creating a response, the accepted record should be flagged as responded """ member = get_member(member) parent_content = get_content(parent_content) if not member: raise action_error(_("cant find user"), code=404) if not parent_content: raise action_error(_("cant find parent content"), code=404) if parent_content.__type__ != 'assignment': return # Nothing to do if parent is not assignment try: # upgrade an 'accepted' record to 'responded' member_assignment = Session.query(MemberAssignment).filter_by( member_id=member.id, content_id=parent_content.id).one() member_assignment.status = "responded" except: # otherwise create 'responded' record member_assignment = MemberAssignment() member_assignment.content = parent_content member_assignment.member = member #assignment_accepted.member_id = member.id member_assignment.status = "responded" Session.add(member_assignment) if not delay_commit: Session.commit() #invalidate_accepted_assignment(member) return True
def rate_content(content, member, rating): content = get_content(content) member = get_member(member) rating_value = int(rating) if not content: raise action_error(_('unable to find content'), code=404) if not member: raise action_error(_('unable to find member'), code=404) if rating and rating_value < 0 or rating_value > 5: raise action_error(_("Ratings can only be in the range 0 to 5"), code=400) # remove any existing ratings # we need to commit after removal, otherwise SQLAlchemy # will optimise remove->add as modify-existing, and the # SQL trigger will break try: existing = Session.query(Rating).filter( Rating.content == content).filter(Rating.member == member).one() Session.delete(existing) Session.commit() except NoResultFound: pass # rating = 0 = remove vote # add a new one if rating_value > 0: r = Rating() r.content = content r.member = member r.rating = rating_value Session.add(r) Session.commit()
def follower_invite_trusted(followed, follower, delay_commit=False): followed = get_member(followed) follower = get_member(follower) if not followed: raise action_error(_('unable to find followed'), code=404) if not follower: raise action_error(_('unable to find follower'), code=404) if follower == followed: raise action_error(_('may not follow yourself'), code=400) #if followed in follower.following: if follower.is_following(followed): raise action_error(_('already following'), code=400) if follower.is_follow_trusted_inviter(followed): raise action_error(_('already invited to follow as trusted')) follow = Follow() follow.member = followed follow.follower = follower follow.type = 'trusted_invite' Session.add(follow) if not delay_commit: Session.commit() #invalidate_member(follower) #invalidate_member(followed) follower.send_notification( messages.follow_invite_trusted(member=followed, you=follower)) return True
def get_tag(tag): """ Returns a tag object for the string passed to it If it does not appear in the database then return a new tag object If it does exisit in the data then return the database object """ tag = tag.lower() try: return Session.query(Tag).filter_by(name=unicode(tag)).one() except NoResultFound as nrf: t = Tag(unicode(tag)) Session.add(t) return t
def accept_assignment(assignment, member, status="accepted", delay_commit=False): member = get_member(member) assignment = get_content(assignment) if not member: raise action_error(_("cant find user"), code=404) if not assignment: raise action_error(_("cant find assignment"), code=404) if not issubclass(assignment.__class__, AssignmentContent): raise action_error(_("only _assignments can be accepted"), code=400) # all permissins hendled by controler action - so this is unneeded here #if not assignment.viewable_by(c.logged_in_persona): # raise action_error(_('_assignment is not visible to your user and therefor cannot be accepted'), code=403) if assignment_previously_accepted_by(assignment, member): raise action_error(_( '_assignment has been previously accepted and cannot be accepted again' ), code=400) #if assignment.creator == member: # raise action_error(_("cannot accept your own _assignment"), code=400) assignment_accepted = MemberAssignment() assignment.assigned_to.append(assignment_accepted) assignment_accepted.member = member assignment_accepted.status = status Session.add(assignment_accepted) if not delay_commit: Session.commit() #invalidate_accepted_assignment(member) if status == "accepted": assignment.creator.send_notification( messages.assignment_accepted(member=member, assignment=assignment, you=assignment.creator)) if status == "pending": member.send_notification( messages.assignment_invite(member=assignment.creator, assignment=assignment, you=member)) return True
def set_payment_account(member, value, delay_commit=False): member = get_member(member) #account = None if isinstance(value, PaymentAccount): member.payment_account = value elif value in account_types.enums: if value == 'free': account = None else: account = PaymentAccount() account.type = value Session.add(account) member.payment_account = account else: raise action_error('unknown account type: %s' % value) if not delay_commit: Session.commit() return True
def follow(follower, followed, delay_commit=False): followed = get_member(followed) follower = get_member(follower) if not followed: raise action_error(_('unable to find followed'), code=404) if not follower: raise action_error(_('unable to find follower'), code=404) if follower == followed: raise action_error(_('may not follow yourself'), code=400) #if followed in follower.following: if follower.is_following(followed): raise action_error(_('already following'), code=400) # AllanC - I wanted to use is_following and remove the following reference - but as this code is run by base test populator before the site is running it cant be # GregM: Change invite to follow if is invited, otherwise follow: if follower.is_follow_trusted_inviter(followed): follow = Session.query(Follow).filter( Follow.member_id == followed.id).filter( Follow.follower_id == follower.id).filter( Follow.type == 'trusted_invite').one() follow.type = 'trusted' else: #follower.following.append(followed) follow = Follow() follow.member = followed follow.follower = follower Session.add(follow) if not delay_commit: Session.commit() #invalidate_member(follower) #invalidate_member(followed) followed.send_notification( messages.followed_by(member=follower, you=followed)) return True
def boom_content(content, member, delay_commit=False): # Validation if content.private == True: raise action_error(_("cannot boom private content"), code=400) if has_boomed(content, member): raise action_error(_("You have previously boomed this _content"), code=400) boom = Boom() #boom.content_id = content.id #boom.member_id = member.id boom.content = content boom.member = member Session.add(boom) if not delay_commit: Session.commit() if content.__type__ == 'article': member.send_notification_to_followers( messages.boom_article(member=member, article=content)) elif content.__type__ == 'assignment': member.send_notification_to_followers( messages.boom_assignment(member=member, assignment=content))
def test_summary_emails(self): def task_summary_notification_email(): response = self.app.get(url(controller='task', action='summary_notification_email')) self.assertIn(response_completed_ok, response.body) now_start = self.server_datetime() now = now_start # No summary emails should trigger yet because no users have setup an interval num_emails = getNumEmails() task_summary_notification_email() self.assertEquals(num_emails, getNumEmails()) # Setup test data ------------------------------------------------------ self.setting('summary_email_interval', 'advanced', 'hours=1') # Setup summary date # Execute timed task --------------------------------------------------- num_emails = getNumEmails() task_summary_notification_email() # Add message that should trigger in last hour and send notification m1 = Message() m1.target = Session.query(User).get('unittest') m1.subject = 'Test summary_notification_email notification' m1.content = 'Test summary_notification_email notification' m1.timestamp = now + datetime.timedelta(minutes=30, hours=1) Session.add(m1) m2 = Message() m2.source = Session.query(User).get('kitten') m2.target = Session.query(User).get('unittest') m2.subject = 'Test summary_notification_email message' m2.content = 'Test summary_notification_email message' m2.timestamp = now + datetime.timedelta(minutes=15, hours=1) Session.add(m2) m3 = Message() m3.source = Session.query(User).get('unittest') m3.target = Session.query(User).get('kitten') m3.subject = 'To Amy kitten' m3.content = 'To Amy kitten' m3.timestamp = now + datetime.timedelta(minutes=20, hours=1) Session.add(m2) Session.commit() now = self.server_datetime(now + datetime.timedelta(hours=2)) num_emails = getNumEmails() task_summary_notification_email() # Check sent emails ---------------------------------------------------- self.assertEquals(getNumEmails(), num_emails + 1) email = getLastEmail().content_text self.assertIn ('summary_notification_email notification', email) self.assertIn ('summary_notification_email message' , email) self.assertIn ('To Amy' , email) # Check sent message appears in summary self.assertIn ('Amy M' , email) # Check the avatar and names of targets are aquired from DB self.assertNotIn('unitfriend' , email) # Loose check to try to catch if any other notifications bleed over into this summary email # Check no emails sent if outside interval ----------------------------- now = self.server_datetime(now + datetime.timedelta(hours=20)) num_emails = getNumEmails() task_summary_notification_email() self.assertEquals(num_emails, getNumEmails()) # Cleanup -------------------------------------------------------------- # Reset db state #self.setting('summary_email_interval', 'advanced', 'hours=0', assert_set_value=False) # hours=0 turns into None and this breaks the auto assertion at the end of set value self.setting('summary_email_interval', 'advanced', '', assert_set_value=False) #AllanC - an empty string should default to None # No summary emails should trigger yet because no users have setup an interval num_emails = getNumEmails() task_summary_notification_email() self.assertEquals(num_emails, getNumEmails()) # AllanC - this fails .. investigation needs to be made # Reset server datetime self.server_datetime(now_start) # Delete test messages Session.delete(m1) Session.delete(m2) Session.delete(m3) Session.commit()
def send_notification(members, message): #members, rendered_message """ Threaded message system Save and handles propogating the message to different technologies for all members of a group or an indvidual """ #log.debug('Running send_notification to members=%s message=%s' % (members,message)) message['source'] = get_member( message.get('source') or message.get('source_id')) or message.get( 'source') # Attempt to normalize source member # Multiple memebrs if isinstance(members, list): for member in get_members(members, expand_group_members=True): message[ 'target_username'] = member.id # Overlay the direct target member of this message as an additional param send_notification(member, message) #if member.__type__ == 'group': # send_notification(get_group_member_username_list(member), **kwargs) Session.commit( ) # No need to commits as all workers commit at end by default # Single member else: member = get_member(members) # AllanC - Messages can be passed without a default_route or name if they are not auto generated notifications # in this case they are deemed "user to user" messages and have enforced default template and route if 'default_route' not in message: message['default_route'] = 'e' if 'name' not in message: message['name'] = 'message' # Get propergate settings - what other technologies is this message going to be sent over # Attempt to get routing settings from the member's config; if that fails, use the # message's default routing message_tech_options = member.config.get( "route_" + message.get('name', 'default'), message.get('default_route')) # Iterate over each letter of tech_options - each letter is a code for the technology to send it to # e.g 'c' will send to Comufy # 'et' will send to email and twitter # 'n' is a normal notification # '' will dispose of the message because it has nowhere to go for route in message_tech_options: # -- Comufy -------------------------------------------------------- # very unused, untested, commented out for coverage #if route == 'c': # pass # -- Email --------------------------------------------------------- if route == 'e': if member.__type__ == 'user': # Only send emails to individual users # Feature #498 - Check for existing email template and attempt to render def notification_template(template): template_path = os.path.join("email", "notifications", template + ".mako") #if os.path.exists(os.path.join(config['path.templates'], template_path)): if os.path.exists( os.path.join("civicboom/templates", template_path)): return template_path return 'email/notifications/default.mako' #if config['debug'] == True or config['debug'] == "true": # debug is a string, so config['debug'] == "false" == Trues if config['worker.queue.type'] == 'inline': c = render_mako(notification_template( message.get('name')), extra_vars={ "kwargs": message, }) else: # pragma: no cover - test mode uses inline rendering, this is stand-alone l = TemplateLookup( directories=['.', 'civicboom/templates'], input_encoding='utf-8', output_encoding='utf-8') f = os.path.join( "civicboom/templates", notification_template(message.get('name'))) t = Template(filename=f, lookup=l) c = t.render_unicode(kwargs=message, h=helpers) send_email( member, subject=message.get( 'subject'), #, _('_site_name notification') content_html=c, ) # -- Notification -------------------------------------------------- # Save message in message table (to be seen as a notification) if route == 'n': m = Message() m.subject = message['subject'] m.content = message['content'] m.target = member Session.add(m) #member.messages_to.append(m) #invalidate_list_version('mesages_index', 'notification', m.target.id) # AllanC - we can replace this invalidation with a sqlalchemy list watching trigger maybe? # ------------------------------------------------------------------ log.debug("%s was sent the message '%s', routing via %s" % (member.username, message.get('name'), message_tech_options)) return True
def flag(obj, raising_member=None, type="automated", comment=None, url_base=None, delay_commit=False, moderator_address=None): """ if url_base is included an alternate URL generator to avert the use of the pylons one """ flag = FlaggedEntity() flag.raising_member = get_member(raising_member) if isinstance(obj, Content): flag.offending_content = obj if isinstance(obj, Member): flag.offending_member = obj if isinstance(obj, Message): flag.offending_message = obj flag.comment = strip_html_tags(comment) flag.type = type Session.add(flag) if not delay_commit: Session.commit() else: Session.flush() # Send email to alert moderator raising_member_username = '******' try: raising_member_username = flag.raising_member.id except: pass # Base email text email_text_dict = { "raising_member": raising_member_username, "type": type, "comment": flag.comment, "action_ignore": '', "action_delete": '', } email_text = """ --- Report --- Reporter: %(raising_member)s Category: %(type)s %(comment)s --- Actions --- If the content is ok, click here to remove the flag: %(action_ignore)s If the content is not ok, click here to hide it from the site: %(action_delete)s """ # Additional Content text if flag.offending_content: email_text_dict.update({ "creator_name": flag.offending_content.creator.id, "creator_url": url('member', id=flag.offending_content.creator.id, qualified=True, sub_domain="www"), "content_url": url('content', id=flag.offending_content.id, qualified=True, sub_domain="www"), "content_title": flag.offending_content.title, "content_body": flag.offending_content.content, "action_ignore": url("admin/moderate?kay=yay&content_id=%s" % flag.offending_content.id, qualified=True), #sub_domain="www"), "action_delete": url("admin/moderate?kay=nay&content_id=%s" % flag.offending_content.id, qualified=True), #sub_domain="www"), }) email_text = email_text + """ --- Reported Content --- Title: %(content_title)s %(content_url)s Author: %(creator_name)s %(creator_url)s %(content_body)s """ if flag.offending_member: log.error('member flaging not implemented yet') if flag.offending_message: log.error('message flaging not implemented yet') email_text = email_text % email_text_dict send_email( moderator_address, subject='flagged content [%s]' % type, content_text=email_text, content_html="<pre>" + email_text + "</pre>", )
def upgrade_user_to_group(member_to_upgrade_to_group, new_admins_username, new_group_username=None): """ Only to be called by admins/power users This handled the migration of users to groups at an SQL level """ to_group = get_member(member_to_upgrade_to_group) admin_user = get_member(new_admins_username) # Validation if not to_group or to_group.__type__ != 'user': raise action_error('member_to_upgrade_to_group not a group', code=404) if get_member(new_group_username): raise action_error('new_group_username already taken', code=404) if admin_user: raise action_error('new_admins_username already taken', code=404) # Create new admin user admin_user = User() admin_user.id = new_admins_username admin_user.name = new_admins_username admin_user.status = 'active' admin_user.email = to_group.email admin_user.email_unverifyed = to_group.email_unverified Session.add(admin_user) Session.commit() # needs to be commited to get id sql_cmds = [] if new_group_username: sql_cmds += [ Member.__table__.update().where( Member.__table__.c.id == to_group.id).values( username=new_group_username), ] sql_cmds += [ # UPDATE member set username='******', __type__='group' WHERE id=533; Member.__table__.update().where(Member.__table__.c.id == to_group.id ).values(__type__='group'), # Reassign the login details from the old member to the new admin user UserLogin.__table__.update().where( UserLogin.__table__.c.member_id == to_group.id ).values(member_id=admin_user.id), # DELETE the matching user record that pairs with the member record User.__table__.delete().where(User.__table__.c.id == to_group.id), # INSERT matching group record to pair group name Group.__table__.insert().values(id=to_group.id, join_mode='invite', member_visibility='private', default_content_visibility='public', default_role='admin', num_members=1), # INSERT admin_user as as admin of the group GroupMembership.__table__.insert().values(group_id=to_group.id, member_id=admin_user.id, role='admin', status='active'), ] for sql_cmd in sql_cmds: Session.execute(sql_cmd) Session.commit()