# Notebook Registration Challenges

Notebook Registration Challenges

This module includes support for challenge-response tests posed to users registering for new Sage notebook accounts. These Completely Automated Public Turing tests to tell Computers and Humans Apart, or CAPTCHAs, may be simple math questions, requests for special registration codes, or reCAPTCHAs.

AUTHORS:

- reCAPTCHA is written by Ben Maurer and maintained by Josh Bronson. It is licensed under a MIT/X11 license. The reCAPTCHA challenge implemented in `reCAPTCHAChallenge` is adapted from this Python API, which is also available here.

class `sagenb.notebook.challenge.``AbstractChallenge`(conf, **kwargs)

  Bases: `object`

  An abstract class with a suggested common interface for specific challenge-response schemes.

  `html`(**kwargs)

    Returns HTML for the challenge, e.g., to insert into a new account registration page.

    INPUT:

    - `kwargs` - a dictionary of keywords arguments

    OUTPUT:

    - a string; HTML form representation of the challenge, including a field for the response, supporting hidden fields, JavaScript code, etc.

    TESTS:

        sage: from sagenb.notebook.challenge import AbstractChallenge
        sage: tmp = tmp_dir(ext='.sagenb')
        sage: import sagenb.notebook.notebook as n
        sage: nb = n.Notebook(tmp)
        sage: chal = AbstractChallenge(nb.conf())
        sage: chal.html()
        Traceback (most recent call last):
        ...
        NotImplementedError

  `is_valid_response`(**kwargs)

    Returns the status of a challenge response.

    INPUT:

    - `kwargs` - a dictionary of keyword arguments

    OUTPUT:

    - a `ChallengeResponse` instance

    TESTS:

        sage: from sagenb.notebook.challenge import AbstractChallenge
        sage: tmp = tmp_dir(ext='.sagenb')
        sage: import sagenb.notebook.notebook as n
        sage: nb = n.Notebook(tmp)
        sage: chal = AbstractChallenge(nb.conf())
        sage: chal.is_valid_response()
        Traceback (most recent call last):
        ...
        NotImplementedError

class `sagenb.notebook.challenge.``ChallengeDispatcher`(conf, **kwargs)

  Bases: `object`

  A simple dispatcher class that provides access to a specific challenge.

class `sagenb.notebook.challenge.``ChallengeResponse`(is_valid, error_code=None)

  Bases: `object`

  A simple challenge response class that indicates whether a response is empty, correct, or incorrect, and, if it’s incorrect, includes an optional error code.

class `sagenb.notebook.challenge.``NotConfiguredChallenge`(conf, **kwargs)

  Bases: `sagenb.notebook.challenge.AbstractChallenge`

  A fallback challenge used when an administrator has not configured a specific method.

  `html`(**kwargs)

    Returns a suggestion to inform the Notebook server’s administrator about the misconfigured challenge.

    INPUT:

    - `conf` - a `ServerConfiguration`; an instance of the server’s configuration
    - `kwargs` - a dictionary of keyword arguments

    OUTPUT:

    - a string

    TESTS:

        sage: from sagenb.notebook.challenge import NotConfiguredChallenge
        sage: tmp = tmp_dir(ext='.sagenb')
        sage: import sagenb.notebook.notebook as n
        sage: nb = n.Notebook(tmp)
        sage: chal = NotConfiguredChallenge(nb.conf())
        sage: print(chal.html())
        Please ask the server administrator to configure a challenge!

  `is_valid_response`(**kwargs)

    > Always reports a failed response, for the sake of security.

    > INPUT:

    > - `kwargs` - a dictionary of keyword arguments

    > OUTPUT:

    > - a `ChallengeResponse` instance

    TESTS:

        sage: from sagenb.notebook.challenge import NotConfiguredChallenge
        sage: tmp = tmp_dir(ext='.sagenb')
        sage: import sagenb.notebook.notebook as n
        sage: nb = n.Notebook(tmp)
        sage: chal = NotConfiguredChallenge(nb.conf())
        sage: chal.is_valid_response().is_valid
        False
        sage: chal.is_valid_response().error_code
        ''

class `sagenb.notebook.challenge.``SimpleChallenge`(conf, **kwargs)

  Bases: `sagenb.notebook.challenge.AbstractChallenge`

  A simple question and answer challenge.

  `html`(**kwargs)

    Returns a HTML form posing a randomly chosen question.

    INPUT:

    - `kwargs` - a dictionary of keyword arguments

    OUTPUT:

    - a string; the HTML form

    TESTS:

        sage: from sagenb.notebook.challenge import SimpleChallenge
        sage: tmp = tmp_dir(ext='.sagenb')
        sage: import sagenb.notebook.notebook as n
        sage: nb = n.Notebook(tmp)
        sage: chal = SimpleChallenge(nb.conf())
        sage: chal.html() # random
        '...What is the largest prime factor of 1001?...'

  `is_valid_response`(req_args={}, **kwargs)

    Returns the status of a user’s answer to the challenge question.

    INPUT:

    - `req_args` - a string:list dictionary; the arguments of the remote client’s HTTP POST request
    - `kwargs` - a dictionary of extra keyword arguments

    OUTPUT:

    - a `ChallengeResponse` instance

    TESTS:

        sage: from sagenb.notebook.challenge import SimpleChallenge
        sage: tmp = tmp_dir(ext='.sagenb')
        sage: import sagenb.notebook.notebook as n
        sage: nb = n.Notebook(tmp)
        sage: chal = SimpleChallenge(nb.conf())
        sage: req = {}
        sage: chal.is_valid_response(req).is_valid
        sage: chal.is_valid_response(req).error_code
        ''
        sage: from sagenb.notebook.challenge import QUESTIONS
        sage: ques, ans = sorted(QUESTIONS.items())[0]
        sage: ans = ans.split('|')[0]
        sage: print(ques)
        How many bits are in one byte?
        sage: print(ans)
        8
        sage: req['simple_response_field'] = ans
        sage: chal.is_valid_response(req).is_valid
        False
        sage: chal.is_valid_response(req).error_code
        ''
        sage: req['simple_challenge_field'] = ques
        sage: chal.is_valid_response(req).is_valid
        True
        sage: chal.is_valid_response(req).error_code
        ''

`sagenb.notebook.challenge.``agree`(response, answer)

  Returns whether a challenge response agrees with the answer.

  INPUT:

  - `response` - a string; the user’s response to a posed challenge
  - `answer` - a string; the challenge’s right answer as a regular expression

  OUTPUT:

  - a boolean; whether the response agrees with the answer

  TESTS:

      sage: from sagenb.notebook.challenge import agree
      sage: agree('0', r'0|zero')
      True
      sage: agree('eighty', r'8|eight')
      False

`sagenb.notebook.challenge.``challenge`(conf, **kwargs)

  Wraps an instance of `ChallengeDispatcher` and returns an instance of a specific challenge.

  INPUT:

  - `conf` - a `ServerConfiguration`; a server configuration instance
  - `kwargs` - a dictionary of keyword arguments

  OUTPUT:

  - an instantiated subclass of `AbstractChallenge`

  TESTS:

      sage: from sagenb.notebook.challenge import challenge
      sage: tmp = tmp_dir(ext='.sagenb')
      sage: import sagenb.notebook.notebook as n
      sage: nb = n.Notebook(tmp)
      sage: nb.conf()['challenge_type'] = 'simple'
      sage: chal = challenge(nb.conf())
      sage: chal.html() # random
      '<p>...'

class `sagenb.notebook.challenge.``reCAPTCHAChallenge`(conf, remote_ip='', is_secure=False, lang='en', **kwargs)

  Bases: `sagenb.notebook.challenge.AbstractChallenge`

  A reCAPTCHA challenge adapted from this Python API, also hosted here, written by Ben Maurer and maintained by Josh Bronson.

  `html`(error_code=None, **kwargs)

    Returns HTML and JavaScript for a reCAPTCHA challenge and response field.

    INPUT:

    - `error_code` - a string (default: None); an optional error code to embed in the HTML, giving feedback about the user’s previous response
    - `kwargs` - a dictionary of extra keyword arguments

    OUTPUT:

    - a string; HTML and JavaScript to render the reCAPTCHA challenge

    TESTS:

        sage: from sagenb.flask_version import base # random output -- depends on warnings issued by other sage packages
        sage: app = base.create_app(tmp_dir(ext='.sagenb'))
        sage: ctx = app.app_context()
        sage: ctx.push()
        sage: nb = base.notebook
        sage: from sagenb.notebook.challenge import reCAPTCHAChallenge
        sage: chal = reCAPTCHAChallenge(nb.conf(), remote_ip = 'localhost')
        sage: chal.html()
        u'...recaptcha...'
        sage: chal.html('incorrect-captcha-sol')
        u'...incorrect-captcha-sol...'

  `is_valid_response`(req_args={}, **kwargs)

    Submits a reCAPTCHA request for verification and returns its status.

    INPUT:

    - `req_args` - a dictionary; the arguments of the responding user’s HTTP POST request
    - `kwargs` - a dictionary of extra keyword arguments

    OUTPUT:

    - a `ChallengeResponse` instance; whether the user’s response is empty, accepted, or rejected, with an optional error string

    TESTS:

        sage: from sagenb.notebook.challenge import reCAPTCHAChallenge
        sage: tmp = tmp_dir(ext='.sagenb')
        sage: import sagenb.notebook.notebook as n
        sage: nb = n.Notebook(tmp)
        sage: chal = reCAPTCHAChallenge(nb.conf(), remote_ip = 'localhost')
        sage: req = {}
        sage: chal.is_valid_response(req).is_valid
        sage: chal.is_valid_response(req).error_code
        ''
        sage: req['recaptcha_response_field'] = ['subplotTimes']
        sage: chal.is_valid_response(req).is_valid
        False
        sage: chal.is_valid_response(req).error_code
        'incorrect-captcha-sol'
        sage: req['simple_challenge_field'] = ['VBORw0KGgoANSUhEUgAAAB']
        sage: chal.is_valid_response(req).is_valid # random
        False
        sage: chal.is_valid_response(req).error_code # random
        'incorrect-captcha-sol'