def func_wrapper(*args, **kwargs):
            if config.LOGIN_DISABLED == 'True':
                return func(*args, **kwargs)
            if current_user.is_authenticated:
                current_app.logger.info('LIST OF ROLES: ' + str(current_user.get_roles()))
                if current_user.has_role(role):
                    return func(*args, **kwargs)
                else:
                    error = errors.get("verification_ui", "ADFS_ROLE_LOGGED_IN_ERROR", filler=str(role))

                    raise ApplicationError(*error)
            else:
                raise ApplicationError(*errors.get("verification_ui", "ADFS_ROLE_LOGGED_OUT_ERROR", filler=str(role)))
Exemple #2
0
def contact_preferences(item_id):
    try:
        verification_api = VerificationAPI()
        case = verification_api.get_item(item_id)
        form = _build_contact_form(case)

        if form.validate_on_submit():
            contactable = request.form['contactable'] == 'yes'

            params = {
                'updated_data': {
                    'contactable':
                    contactable,
                    'contact_preferences':
                    request.form.getlist('contact_preferences')
                },
                'staff_id': _get_user_name()
            }

            verification_api.update_user_details(item_id, params)
            return redirect(url_for('verification.get_item', item_id=item_id))

        return render_template('app/contact_preferences.html',
                               form=form,
                               item_id=item_id)
    except ApplicationError:
        raise ApplicationError(
            'Something went wrong while updating user contact preferences'
            'Please raise an incident quoting the following id: {}'.format(
                g.trace_id))
def finish_login(access_ticket):  # pragma: no cover
    """This method gets the access token, creates a User and saves them (their userid/token) into the session
    for future use when visiting protected routes. Then redirects them back to the original place they
    wanted to go at the start of the whole process.
    """
    try:
        access_token = get_access_token(access_ticket)
        if access_token is None:
            current_app.logger.error('Unable to get access token using access ticket: ' + str(access_ticket))
            raise ApplicationError("Unable to retrieve access token", "LOGIN")
        current_app.logger.debug('Successfully swapped access ticket for access token')

        # Create a user object and tell flask-login to add their userid (access token) to the session.
        # That userid will be used on future page requests (see load_user) to rebuild the user
        # object (and validate the access token)
        user = User(access_token=access_token)
        login_user(user)

        try:
            if session['requested_page'] is None:
                # In the unlikely event we can't remember where we originally wanted to go
                # (maybe went to /login directly)
                # go to the homepage
                redirect(url_for('verification.get_worklist'))
            else:
                current_app.logger.debug('The user is now logged in! \
                    Lets redirect them back to where they wanted to go')
                redirect_uri = session['requested_page']
                session['requested_page'] = None
                return redirect(redirect_uri)
        except Exception:
            redirect(url_for('verification.get_worklist'))
    except Exception:
        redirect(url_for('verification.get_worklist'))
Exemple #4
0
def update_dataset_access():
    try:
        current_app.logger.info('Updating dataset access...')
        item_id = request.form['item_id']
        staff_id = _get_user_name()
        updated_access = request.form

        verification_api = VerificationAPI()
        current_access = verification_api.get_user_dataset_access(item_id)

        # Temporarily filter out ocod/ccod/nps_sample/res_cov_direct from access being updated
        for dataset in current_access:
            if dataset['name'] == 'licenced':
                dataset['licences'] = {}

            # Filter out sample and direct licence entries
            licences = {
                k: v
                for (k, v) in dataset['licences'].items()
                if '_direct' not in k and '_sample' not in k
            }
            dataset['licences'] = licences

        verification_api.update_dataset_access(item_id, staff_id,
                                               current_access, updated_access)

        flash("User's data access was updated")
        return redirect(url_for('verification.get_item', item_id=item_id))
    except ApplicationError:
        raise ApplicationError(
            'Something went wrong when updating users data access. '
            'Please raise an incident quoting the following id: {}'.format(
                g.trace_id))
Exemple #5
0
def decline_worklist_item():
    try:
        session['search_params'] = None
        staff_id = _get_user_name()
        item_id = request.form['item_id']
        if not check_correct_lock_user(item_id, session['username']):
            flash('You are not the current locked user of Worklist item {}'.
                  format(item_id))
            return redirect(url_for('verification.get_worklist'))
        reason = request.form['decline_reason']
        advice = request.form['decline_advice']
        current_app.logger.info('User {} declining worklist item {}...'.format(
            staff_id, item_id))
        verification_api = VerificationAPI()
        verification_api.decline_worklist_item(item_id, staff_id, reason,
                                               advice)
    except ApplicationError:
        raise ApplicationError(
            'Something went wrong when declining the application. '
            'Please raise an incident quoting the following id: {}'.format(
                g.trace_id))
    else:
        current_app.logger.info(
            'Worklist item {} was declined'.format(item_id))
        flash('Application was declined')
        return redirect(url_for('verification.get_worklist'))
Exemple #6
0
    def _request(self, uri, data=None):
        url = '{}/{}'.format(self.base_url, uri)
        headers = {'Accept': 'application/json'}
        timeout = current_app.config['DEFAULT_TIMEOUT']

        try:
            if data is None:
                response = g.requests.get(url,
                                          headers=headers,
                                          timeout=timeout)
            else:
                headers['Content-Type'] = 'application/json'
                response = g.requests.post(url,
                                           headers=headers,
                                           timeout=timeout,
                                           data=data)
            status = response.status_code
            if status == 204:
                return {}
            if status == 404:
                error = 'Not Found'
                raise ApplicationError(*errors.get('verification_ui',
                                                   'API_HTTP_ERROR',
                                                   filler=str(error)),
                                       http_code=status)
            else:
                response.raise_for_status()
                return response.json()

        except requests.exceptions.HTTPError as error:
            current_app.logger.error(
                'Encountered non-2xx HTTP code when accessing {}'.format(url))
            current_app.logger.error('Error: {}'.format(error))
            raise ApplicationError(*errors.get(
                'verification_ui', 'API_HTTP_ERROR', filler=str(error)))
        except requests.exceptions.ConnectionError as error:
            current_app.logger.error(
                'Encountered an error while connecting to Verification API')
            raise ApplicationError(*errors.get(
                'verification_ui', 'API_CONN_ERROR', filler=str(error)))
        except requests.exceptions.Timeout as error:
            current_app.logger.error(
                'Encountered a timeout while accessing {}'.format(url))
            raise ApplicationError(*errors.get(
                'verification_ui', 'API_TIMEOUT', filler=str(error)))
Exemple #7
0
    def test_get_worklist_error(self, mock_api):
        mock_api.return_value.get_worklist.side_effect = ApplicationError(
            'Test error')

        response = self.app.get('/verification/worklist')
        data = response.data.decode()

        self.assertEqual(response.status_code, 500)
        self.assertIn('Something went wrong when retrieving the worklist.',
                      data)
Exemple #8
0
    def test_unlock_error(self, mock_api):
        mock_api.return_value.lock.side_effect = ApplicationError('Test error')
        test_form_data = {'item_id': '1'}

        response = self.app.post('/verification/worklist/unlock',
                                 data=test_form_data,
                                 follow_redirects=False)
        data = response.data.decode()

        self.assertEqual(response.status_code, 500)
        self.assertIn('Something went wrong while unlocking the application.',
                      data)
Exemple #9
0
def unlock_case():
    try:
        session['search_params'] = None
        item_id = request.form['item_id']
        current_app.logger.info('Unlocking worklist item {}'.format(item_id))
        verification_api = VerificationAPI()
        verification_api.unlock(item_id)
    except ApplicationError:
        raise ApplicationError(
            'Something went wrong while unlocking the application. '
            'Please raise an incident quoting the following id: {}'.format(
                g.trace_id))
    else:
        current_app.logger.info('Worklist item successfully unlocked')
        return redirect(url_for('verification.get_worklist'))
Exemple #10
0
    def test_update_contact_preferences_error(self, mock_api):
        mock_api.return_value.update_user_details.side_effect = ApplicationError(
            'error')
        form_data = {'item_id': '123', 'contactable': 'True'}

        response = self.app.post(
            '/verification/worklist/update_contact_preferences',
            data=form_data,
            follow_redirects=False)

        data = response.data.decode()

        self.assertEqual(response.status_code, 500)
        self.assertIn('Something went wrong when updating contact preferences',
                      data)
Exemple #11
0
    def test_add_note_error(self, mock_api):
        mock_api.return_value.add_note.side_effect = ApplicationError(
            'Test error')
        test_form_data = {
            'item_id': '1',
            'note_text': 'This is yet another note'
        }

        response = self.app.post('/verification/worklist/note',
                                 data=test_form_data,
                                 follow_redirects=False)
        data = response.data.decode()

        self.assertEqual(response.status_code, 500)
        self.assertIn('Something went wrong when adding a note.', data)
Exemple #12
0
    def test_close_account_error(self, mock_api):
        mock_api.return_value.close_account.side_effect = ApplicationError(
            'Test error')
        test_form_data = {
            'item_id': '1',
            'close_requester': 'hmlr',
            'close_reason': 'Test close reason'
        }

        response = self.app.post('/verification/worklist/close',
                                 data=test_form_data,
                                 follow_redirects=False)
        data = response.data.decode()

        self.assertEqual(response.status_code, 500)
        self.assertIn('Something went wrong when closing the account.', data)
Exemple #13
0
    def test_decline_worklist_item_error(self, mock_api):
        mock_api.return_value.decline_worklist_item.side_effect = ApplicationError(
            'Test error')
        test_form_data = {
            'item_id': '1',
            'decline_reason': 'Registration number'
        }

        response = self.app.post('/verification/worklist/decline',
                                 data=test_form_data,
                                 follow_redirects=False)
        data = response.data.decode()

        self.assertEqual(response.status_code, 500)
        self.assertIn('Something went wrong when declining the application.',
                      data)
Exemple #14
0
def get_worklist():
    current_app.logger.info('User requested to view worklist...')

    try:
        verification_api = VerificationAPI()
        worklist = verification_api.get_worklist()
        _get_user_name()
    except ApplicationError:
        raise ApplicationError(
            'Something went wrong when retrieving the worklist. '
            'Please raise an incident quoting the following id: {}'.format(
                g.trace_id))
    else:
        current_app.logger.info('Putting worklist into viewable format...')
        worklist_items = [build_row(item) for item in worklist]

        return render_template('app/worklist.html',
                               worklist_items=worklist_items)
Exemple #15
0
def lock_case():
    try:
        session['search_params'] = None
        staff_id = _get_user_name()
        item_id = request.form['item_id']
        current_app.logger.info(
            'Locking worklist item {} to user {}...'.format(item_id, staff_id))
        verification_api = VerificationAPI()
        verification_api.lock(item_id, staff_id)
    except ApplicationError:
        raise ApplicationError(
            'Something went wrong while locking the application. '
            'Please raise an incident quoting the following id: {}'.format(
                g.trace_id))
    else:
        current_app.logger.info(
            'Worklist item lock successfully transferred to user {}'.format(
                staff_id))
        return redirect(url_for('verification.get_item', item_id=item_id))
Exemple #16
0
    def test_post_search_error(self, mock_api, mock_build):
        mock_api.return_value.perform_search.side_effect = ApplicationError(
            'Test error')
        mock_build.return_value = []
        search_entries = {
            "first_name": "test",
            "last_name": "",
            "organisation_name": "",
            "email": "",
        }

        response = self.app.post('/verification/worklist/search',
                                 data=search_entries,
                                 follow_redirects=False)
        data = response.data.decode()

        self.assertEqual(response.status_code, 500)
        self.assertIn('Something went wrong when performing the search.', data)
        self.assertEqual(mock_api.return_value.perform_search.call_count, 1)
        self.assertEqual(mock_build.call_count, 0)
Exemple #17
0
def get_search():
    search_form = SearchForm()
    search_items = []
    has_hit_limit = False
    new_search = False
    try:
        # Default to search params from session
        search_params = session.get('search_params', {})

        # Get search params from form/url for a new search instead search cached on session
        if request.args:
            new_search = True
            search_params = {
                "first_name": request.args.get('first_name'),
                "last_name": request.args.get('last_name'),
                "organisation_name": request.args.get('organisation_name'),
                "email": request.args.get('email')
            }

        # Perform search if any params and store new params on session
        if search_params:
            results = VerificationAPI().perform_search(search_params)
            search_limit = current_app.config.get('VERIFICATION_SEARCH_LIMIT')
            has_hit_limit = len(results) > search_limit
            search_items = [
                build_row(item, for_search=True)
                for item in results[:search_limit]
            ]
            session['search_params'] = search_params

    except ApplicationError:
        raise ApplicationError(
            'Something went wrong when performing last search. '
            'Please raise an incident quoting the following id: {}'.format(
                g.trace_id))
    else:
        return render_template('app/search.html',
                               search_form=search_form,
                               search_items=search_items,
                               has_hit_limit=has_hit_limit,
                               new_search=new_search)
Exemple #18
0
def add_note():
    try:
        session['search_items'] = None
        staff_id = _get_user_name()
        item_id = request.form['item_id']
        note_text = request.form['note_text']
        current_app.logger.info(
            'Adding note "{}" to notepad for worklist item {}...'.format(
                note_text, item_id))

        verification_api = VerificationAPI()
        verification_api.add_note(item_id, staff_id, note_text)
    except ApplicationError:
        raise ApplicationError(
            'Something went wrong when adding a note. '
            'Please raise an incident quoting the following id: {}'.format(
                g.trace_id))
    else:
        current_app.logger.info('Note was added to the notepad')
        flash('Your note was added to the notepad')
        return redirect(url_for('verification.get_item', item_id=item_id))
Exemple #19
0
    def __call__(self, field, **kwargs):
        if self.multiple:
            raise ApplicationError(
                'Please do not render mutliselect elements as a select box'
                ' - you should use checkboxes instead in order to comply with'
                ' the GOV.UK service manual')

        kwargs.setdefault("id", field.id)

        if "required" not in kwargs and "required" in getattr(
                field, "flags", []):
            kwargs["required"] = True

        kwargs['items'] = []

        # Construct select box choices
        for val, label, selected in field.iter_choices():
            item = {'text': label, 'value': val, 'selected': selected}

            kwargs['items'].append(item)

        return super().__call__(field, **kwargs)
    def _get_certs_from_adfs(self, is_a_retry=False):
        """Used by verify_token to connect to ADFS metadata and retrieve new certificates"""
        current_app.logger.info('Refreshing certificates from ADFS')

        # Clear old certs for ease of debugging
        User.public_key_1 = None
        User.public_key_2 = None
        try:
            res = requests.get('{}/federationMetadata/2007-06/federationmetadata.xml'.format(
                               current_app.config.get("ADFS_URL")), verify=False)
        except ConnectionError:
            if not is_a_retry:
                current_app.logger.warning('Failed to connect to ADFS, retrying')
                self._get_certs_from_adfs(is_a_retry=True)
                return
            else:
                raise ApplicationError('Unable to connect to ADFS', 'E802', http_code=500)
        adfs_meta_dict = xmltodict.parse(res.text)
        certs = adfs_meta_dict['EntityDescriptor']['RoleDescriptor'][1]['KeyDescriptor']
        # if there is only one cert then the cert variable will be a dict
        if isinstance(certs, dict):
            User.public_key_1 = self._reformat_adfs_cert(certs['KeyInfo']['X509Data']['X509Certificate'])
        # if there are 2 certs then the cert variable will be a list
        elif isinstance(certs, list):
            User.public_key_1 = self._reformat_adfs_cert(certs[0]['KeyInfo']['X509Data']['X509Certificate'])
            User.public_key_2 = self._reformat_adfs_cert(certs[1]['KeyInfo']['X509Data']['X509Certificate'])
        User.keys_already_swapped = False

        current_app.logger.info('ADFS primary cert:')
        if User.public_key_1:
            current_app.logger.info(User.public_key_1)
        else:
            current_app.logger.info('Unable to retrieve cert from ADFS')

        current_app.logger.info('ADFS secondary cert:')
        if User.public_key_2:
            current_app.logger.info(User.public_key_2)
        else:
            current_app.logger.info('Unable to retrieve cert from ADFS')
Exemple #21
0
def close_account():
    try:
        session['search_params'] = None
        staff_id = _get_user_name()
        item_id = request.form['item_id']
        requester = request.form['close_requester']
        reason = request.form['close_reason']
        current_app.logger.info('User {} closing account {}...'.format(
            staff_id, item_id))

        verification_api = VerificationAPI()
        verification_api.close_account(item_id, staff_id, requester, reason)
    except ApplicationError:
        raise ApplicationError(
            'Something went wrong when closing the account. '
            'Please raise an incident quoting the following id: {}'.format(
                g.trace_id))
    else:
        current_app.logger.info('Account {} was closed'.format(item_id))
        if requester == 'customer':
            flash('Account was closed and email was sent to account holder')
        else:
            flash('Account was closed')
        return redirect(url_for('verification.get_worklist'))
Exemple #22
0
def get_item(item_id):
    current_app.logger.info(
        'User requested to view item {}...'.format(item_id))
    try:
        _get_user_name()
        verification_api = VerificationAPI()
        case = verification_api.get_item(item_id)
        lock = _handle_lock(case, verification_api) if case['status'] in [
            'Pending', 'In Progress'
        ] else None

        if case['status'] == 'Approved':
            dataset_activity = verification_api.get_dataset_activity(item_id)
            dataset_access = verification_api.get_user_dataset_access(item_id)

        from_search = request.args.get('from', None) == 'search'
        if not from_search:
            session['search_params'] = None

    except ApplicationError as error:
        if error.http_code == 404:
            return render_template('app/errors/unhandled.html', http_code=404)

        raise ApplicationError(
            'Something went wrong when requesting the application details. '
            'Please raise and incident quoting the following id: {}'.format(
                g.trace_id))
    else:
        current_app.logger.info(
            'Putting worklist item information into viewable format...')

        case_data_for_template = {
            'id':
            item_id,
            'status':
            case['status'],
            'info':
            build_details_table(case),
            'notes': [{
                'text': note['note_text'],
                'meta_data': format_note_metadata(note)
            } for note in case['notes']]
        }

        if case['status'] in ['Pending', 'In Progress', 'Declined']:
            forms = _build_app_forms(item_id, case['status'], lock is None,
                                     verification_api)
            return render_template('app/application_details.html',
                                   case_data=case_data_for_template,
                                   forms=forms,
                                   search=from_search,
                                   lock=lock)
        else:
            account_name = ' '.join([
                case['registration_data']['title'],
                case['registration_data']['first_name'],
                case['registration_data']['last_name']
            ])
            if case['status'] == 'Approved':
                forms = _build_acc_forms(item_id, case['status'],
                                         dataset_access)
                activity = build_dataset_activity(dataset_activity)
                return render_template('app/account_details.html',
                                       case_data=case_data_for_template,
                                       forms=forms,
                                       search=from_search,
                                       activity=activity,
                                       account_name=account_name,
                                       dataset_access=dataset_access)
            else:
                forms = _build_acc_forms(item_id, case['status'])
                return render_template('app/account_details.html',
                                       case_data=case_data_for_template,
                                       forms=forms,
                                       search=from_search,
                                       account_name=account_name)
    def _verify_token(self, is_a_retry=False):
        """Handles checking that the jwt the user is using is actually from adfs and hasnt been tampered with.
        this includes retries and error handling to deal with expired/missing certificates and retrieving the
        latest certificates from adfs metadata
        """
        # If there are no certificate to use (on app first startup) go and get them
        if not User.public_key_1:
            self._get_certs_from_adfs()
        try:
            # try to decode with primary certificate
            decoded_token = self._decode_jwt(User.public_key_1)
            return decoded_token
        except JWTError as e:
            if str(e) == 'Signature verification failed.':
                # The certificate might have expired (and been swapped to secondary on ADFS itself),
                # so if we have a second try to use it
                if User.public_key_2:
                    try:
                        decoded_token = self._decode_jwt(User.public_key_2)
                        if not User.keys_already_swapped:
                            current_app.logger.info('Verified jwt with secondary certificate,'
                                                    'promoting to primary for future use.')
                            # If the second certificate worked promote it to primary
                            # Store the old primary as secondary to handle any users with leftover sessions
                            # We only want to swap them once otherwise we might just continually be switching as old
                            # and new users use the system
                            User.public_key_1, User.public_key_2 = User.public_key_2, User.public_key_1
                            User.keys_already_swapped = True
                        return decoded_token
                    except JWTError as e:
                        # if this errored with Signature verification failed we can retry with new certificates from
                        # adfs - any other error and we will need to throw an exception
                        if not str(e) == 'Signature verification failed.':
                            raise ApplicationError(e, 'E803', http_code=403)
                    except ApplicationError as e:
                        # It's possible there was an error getting the certificates from ADFS if so we will want to
                        # persist it
                        raise e
                    except Exception:
                        # If something went wrong with the second cert that we can't deal with we will want to know
                        # about it
                        raise ApplicationError('Unknown error occured while trying to validate login',
                                               'E803', http_code=500)

                if not is_a_retry:
                    # If this is the first time we have totally failed to validate the JWT we refresh both
                    # certificates and try again (Most likely scenario is that a new certificate has been introduced
                    # then subsequently promoted without us knowing)
                    # If we have alredy tried again we skip this step to avoid being stuck in a loop and raise an error
                    current_app.logger.error('Unable to validate jwt from ADFS with stored certificates')
                    self._get_certs_from_adfs()
                    return self._verify_token(is_a_retry=True)
                else:
                    # We get to this exception after trying the stored certificates AND new ones from ADFS
                    # Something is probably wrong with the JWT that ADFS returned
                    raise ApplicationError('Signature verification failed with new ADFS certificates',
                                           'E803', http_code=403)
            else:
                # We get to this exception if there is a problem validating the JWT but its not a signature
                # verification error
                raise ApplicationError(str(e), 'E803', http_code=403)
        except ApplicationError as e:
            # It's possible there was an error getting the certificates from ADFS if so we will want to persist it
            raise e
        except Exception as e:
            current_app.logger.error(e)
            raise ApplicationError('Unknown error occured while trying to validate login', 'E803', http_code=500)