Ejemplo n.º 1
0
def watch_canary(sigid_base64):
    canary = get_canary(sigid_base64)
    if canary is None:
        return page_not_found('canary')

    if request.method == 'GET':
        return dict(canary=canary)

    if request.method == 'POST':
        try:
            email = request.form['email']
        except KeyError:
            flash(err_messages['incomplete_form'], 'error')
            return dict(canary=canary)
        except Exception as e:
            flash(err_messages['generic'], 'error')
            app.logger.error(e)
            return dict(canary=canary)

        if not re.search('@', email) or len(email) > 254:
            flash(err_messages['invalid_email'], 'error')
            return dict(canary=canary)

        alerts = request.form.getlist('alerts')
        on_publish = 'onPublish' in alerts
        on_overdue = 'onOverdue' in alerts
        # on_delete = list['onDelete']

        delay_days = 0
        if on_overdue:
            delay = int(request.form['delay'])
            delay_type = request.form['delayType']

            if not delay or not delay_type:
                on_overdue = False
            
            allowed_delays = 'day', 'week', 'month'
            in_range = 1 <= delay <= 100

            if delay_type not in allowed_delays or not in_range:
                flash(err_messages['incomplete_form'], 'error')
                return dict(canary=canary)
            # Get the delay in days
            delay_days = days(delay, delay_type)

        # TODO: notify watchers when an canary is deleted
        # on_delete = request.form.get('onDelete') or False
        if not (on_publish or on_overdue):
            flash(err_messages['incomplete_form'], 'error')
            return dict(canary=canary)

        secret = os.urandom(16).encode('hex')
        alert = Alert(email, canary, False, on_overdue, on_publish,
                      delay_days, secret)
        db_session.add(alert)
        db_session.commit()

        # Send verification email
        send_verification_email.delay(alert, canary)
        return redirect(url_for('canary', sigid_base64=sigid_base64))
Ejemplo n.º 2
0
 def setUp(self):
     init_db()
     self.user = User('C2DFB998',
                      '2fa69032cba961ff8387ef06d2969d08c2dfb998',
                      'Foo Bar <*****@*****.**>')
     self.sekrit = os.urandom(16).encode('hex')
     self.chal = Challenge(sha256(self.sekrit).hexdigest())
     db_session.add(self.user)
     db_session.add(self.chal)
     db_session.commit()
Ejemplo n.º 3
0
def verify_watch_canary(code):
    try:
        a = Alert.query.filter(Alert.hash == code).one()
        canary = Canary.query.filter(Canary.id == a.canary_id).one()
    except:
        return page_not_found('canary')

    if request.method == 'GET':
        a.active = True
        db_session.commit()
        flash(messages['alert_verified'], 'message')
        return redirect(url_for('index'))
Ejemplo n.º 4
0
    def check(cls, obj, solution):
        """Check if the SHA-255 hash of ``solution`` matches the
        challenge.
        """
        if not is_hex_challenge(solution):
            return False

        chal = Challenge.query.filter(Challenge.id == obj.chal_id).one()
        # Set obj.chal_id to None so chal is never used again
        obj.chal_id = None
        db_session.commit()
        return chal.hash == hashlib.sha256(solution).hexdigest()
Ejemplo n.º 5
0
 def delete(self, path):
     """Delete the canary in the filesystem and database, and delete
     the canary's publisher if this is the user's only canary.
     """
     os.unlink(path)
     if len(self.user.canaries) == 1:
         db_session.delete(self.user)
         if "uid" in session:
             session.pop("uid")
         if "fp" in session:
             session.pop("fp")
     db_session.delete(self)
     db_session.commit()
Ejemplo n.º 6
0
    def test_verify_watch_canary(self):
        """Test that visiting a verification page changes an Alert from
        unverified to verified.
        """
        c = self._create_canary()
        a = Alert(c.user.uid, c, True, True, True, 3, 'sekrit')
        db_session.add(a)
        db_session.commit()

        with app.app_context():
            r = self.app.get('verify/{}'.format(a.hash),
                follow_redirects=True)
            self.assertTrue(a.active)
            self.assertIn(messages['alert_verified'], r.data)
Ejemplo n.º 7
0
    def generate(cls, obj, fingerprint):
        """Generate a challenge and return the hash of the challenge and 
        the challenge encrypted with GPG.
        """
        secret = os.urandom(16).encode("hex")
        hash = hashlib.sha256(secret).hexdigest()
        ciphertext = gpg.encrypt(secret, fingerprint)

        chal = Challenge(hash)
        db_session.add(chal)
        db_session.commit()
        obj.chal_id = chal.id
        db_session.commit()
        return ciphertext
Ejemplo n.º 8
0
def edit_canary(sigid_base64):
    if not logged_in():
        return redirect(url_for('login'))

    canary = get_canary(sigid_base64)
    if canary is None:
        return page_not_found('canary')
    if logged_in and canary.user.fingerprint == session['fp']:
        freq_num = request.form['frequencyNum']
        freq = request.form['frequency']
        canary.freq_type = freq
        canary.frequency = days(freq_num, freq)
        canary.active = True
        db_session.commit()
        flash(messages['canary_updated'], 'message')
        return redirect(url_for('canary', sigid_base64=sigid_base64))
    else:
        abort(403)
Ejemplo n.º 9
0
def send_overdue_alert(alert, canary, days_overdue):
    """Send a reminder to canary watchers the canary is overdue."""
    if days_overdue >= alert.delay:
        canary_url = config.URL + '/canary/{}'.format(
            canary.sigid_base64)

        msg = format_message('overdue_alert', [alert.email],
                             canary.user.uid, canary.user.uid,
                             days_overdue, canary_url)

        """Set active to False so we don't send another alert unless
        the canary owner republishes or deletes the canary."""
        alert.active = False
        db_session.commit()
        with app.app_context():
            try:
                mail.send(msg)
            except SMTPException as e:
                app.logger.error(e)
Ejemplo n.º 10
0
def remind(canary, user):
    """Send an email to remind ``user`` to republish ``canary``.
    The email includes a link to a page with a challenge to decrypt.
    """
    if not app.testing:
        canary = db_session.merge(canary)
        user = db_session.merge(user)

    canary_url = config.URL + '/canary/{}'.format(canary.sigid_base64)
    msg = format_message('reminder', [user.uid], canary_url,
                         canary.frequency, canary.freq_type)

    """Set active to False so we don't send another reminder email
    unless the user republishes the canary."""
    canary.active = False
    db_session.commit()
    with app.app_context():
        try:
            mail.send(msg)
        except SMTPException as e:
            app.logger.error(e)
Ejemplo n.º 11
0
def new_canary():
    if request.method == 'GET':
        return None

    if request.method == 'POST':
        try:
            signed = request.form['signedMessage']
            frequency_num = int(request.form['frequencyNum'])
            frequency_type = request.form['frequency']
        except KeyError:
            flash(err_messages['incomplete_form'], 'error')
            return None

        allowed_freqs = 'day', 'week', 'month'
        in_range = 1 <= frequency_num <= 100
        if frequency_type not in allowed_freqs or not in_range:
            flash(err_messages['invalid_freq'], 'error')
            return None
        # Get the frequency in days
        frequency = days(frequency_num, frequency_type)

        verified, err = gpg.verify(signed)
        # Start over if the message wasn't verified.
        if err and not verified:
            flash(err, 'error')
            return None

        fp = verified.fingerprint
        sigid_base64 = base64.urlsafe_b64encode(verified.signature_id)

        try:
            canary = Canary(sigid_base64, frequency, frequency_type)
            db_session.add(canary)
            db_session.commit()
        except IntegrityError:
            # Throw an error if a canary with that sigid already exists
            db_session.rollback()
            db_session.flush()
            flash(err_messages['dupe_canary'], 'error')
            return redirect(url_for('new_canary'))
        except Exception as e:
            db_session.rollback()
            db_session.flush()
            app.logger.error(e)
            """An unexpected database error should not reveal any
               error details to the user."""
            flash(err_messages['generic'], 'error')
            return None

        ciphertext = Challenge.generate(canary, fp)
        # TODO: This is sloppy.
        session['canary'] = dict(fp=verified.fingerprint.lower(),
                                 text=signed,
                                 uid=verified.username,
                                 keyid=verified.key_id,
                                 sigid_base64=sigid_base64,
                                 frequency=frequency,
                                 freq_type=frequency_type,
                                 ciphertext=str(ciphertext))

        flash(messages['verified'], 'message')
        return dict(canary=session['canary'])
Ejemplo n.º 12
0
def canary(sigid_base64):
    if request.method == 'GET':
        canary = get_canary(sigid_base64)
        if canary is None:
            return page_not_found('canary')

        pathstr = str(sigid_base64)
        path = os.path.join(app.config.get('CANARIES_DIR'), pathstr)
        f = open(path, 'r')
        text = f.read()
        f.close()
        return dict(canary=canary, text=text)

    if request.method == 'POST':
        if not is_sigid(sigid_base64):
            return redirect(url_for('index'))

        try:
            canary = Canary.query.filter(
                Canary.sigid_base64 == sigid_base64).one()
            decrypted = request.form['decrypted'].strip()
            if not Challenge.check(canary, decrypted):
                raise IncorrectChallengeException
        except KeyError:
            flash(err_messages['incomplete_form'], 'error')
            return None
        except IncorrectChallengeException:
            db_session.delete(canary)
            db_session.commit()
            flash(err_messages['decrypt_fail'], 'error')
            return redirect(url_for('new_canary'))
        except Exception as e:
            flash(err_messages['generic'], 'error')
            app.logger.error(e)
            return redirect(url_for('new_canary'))

        sess = session['canary']
        fp = sess['fp']
        try:
            user = User.query.filter(User.fingerprint == fp).one()
            """Update the existing user's key info, in case the username
            or email address has been edited since we last saw it."""
            user.update(canary['uid'])
        except NoResultFound:
            # Create a new user
            user = User(sess['keyid'], fp, sess['uid'])
            db_session.add(user)
            db_session.commit()

        canary.user_id = user.id
        canary.active = True
        db_session.commit()

        with app.app_context():
            text = sess['text']
            if app.testing:
                notify(canary, user, text)
            else:
                notify.delay(canary, user, text)

        pathstr = str(sigid_base64)
        path = os.path.join(app.config.get('CANARIES_DIR'), pathstr)

        with open(path, 'w') as f:
            f.write(text)
            f.close()

        flash(messages['published'], 'message')
        return redirect(url_for('canary', sigid_base64=sigid_base64))
Ejemplo n.º 13
0
 def _create_user(self):
     """Helper for creating a user."""
     u = User('C2DFB998', self.fp, 'Foo Bar <*****@*****.**>')
     db_session.add(u)
     db_session.commit()
     return u
Ejemplo n.º 14
0
 def republish(self):
     """Republish the canary."""
     self.date_last_updated = datetime.datetime.now()
     self.active = True
     db_session.commit()
Ejemplo n.º 15
0
    def update(self, uid):
        """Updates the user's uid if it has changed."""
        if uid != self.uid:
            self.uid = uid

        db_session.commit()