Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inbound and outbound anchors #157

Open
jace opened this issue Nov 24, 2017 · 6 comments
Open

Inbound and outbound anchors #157

jace opened this issue Nov 24, 2017 · 6 comments

Comments

@jace
Copy link
Member

jace commented Nov 24, 2017

In the process of defining abstractions around our core data models, we've arrived on the following three:

  1. Documents, which are the concern of the mixin classes provided in coaster.sqlalchemy.
  2. Principals, which were originally just user objects, but increasingly cover a spread of user-like objects with properties such as authorship and ownership. These are discussed in Merge User, Organization and Team models into a new Principal model lastuser#91.
  3. Anchors, which are points of interaction with the user. Anchors may be inbound, for users to communicate with the app, or outbound, for the app to communicate with a user.

Anchors do not indicate the existence of a user/principal, but activity that connects two or more anchors can help infer the presence of a user or other principal. Anchors also are not necessarily database objects. By their nature they are also not guaranteed to exist long term.

Inbound anchors (↓)

These are anchors that allow the user to connect to the app. Examples include:

  • Browser sessions. May or may not be authenticated with a user login (and thereby associated with a principal), but browser sessions can independently create an activity trail. We use this in Hasjob for tracking A/B testing impressions, and previously allowed creation of job posts as well.

  • Private URLs. A URL that is not published to the public can be used to grant an access role. Hasjob used to use it to allow edits to a job post from anyone with the edit URL. Boxoffice currently uses it to manage ticket assignments. Lastuser uses them to send password reset links. (Access control currently happens in the view layer, but RoleMixin needs to accept principals and anchors #156 describes a mechanism for pushing it into RoleMixin.)

  • Access tokens. An access token allows a principal to delegate limited access to an anonymous principal (usually a client app that presents it as a Bearer token, but the exact identity of the caller cannot be determined).

  • OTP. A short lived code that confirms some activity, similar to one of the use cases for private URLs.

Outbound anchors (↑)

  • Email address. Allows sending an email to what is presumably a principal at the receiving end.

  • Phone number. Allows sending an SMS or making a phone call to a presumed principal.

  • Push notification/Browser socket. Allows communication with a client app or browser session.

Outbound anchors like email/phone aren't necessarily verified identifiers. Verification requires a multi-step process as outlined below.

Inferring principals

Activity that creates a combination of an outbound anchor with an inbound anchor (in that order) can confirm that outbound anchor, implying the existence of a principal (if one is not already known). Examples:

  • Email ↑ + verification link ↓: If the link is accessed, it confirms the email address.
  • Browser session ↓ + phone ↑ + OTP ↓ + browser session ↓: Associates the phone anchor with the browser session, and by extension with any principal associated with the browser session.
  • Document + email ↑ + verification link ↓: Publishes the document.

Linking

From these examples, documents, principals and anchors can all be linked to each other.

While the implementation details are beyond the scope of Coaster, it should provide a basic framework as it has done for documents and principals (since #154).

@jace
Copy link
Member Author

jace commented Nov 24, 2017

Example of how the URL inbound anchor could be used:

@app.route('/mydocuments/<doc>/edit/<secret_key>', methods=['GET', 'POST'])
@load_model(MyDocument, {'name': 'doc'}, 'rawdoc', kwargs=True)
@url_anchor(document='rawdoc', anchor='kwargs.secret_key')
def document_edit(rawdoc, kwargs):
    doc = rawdoc.access_for(current_auth.user, current_auth.anchors)
    # Further activity is with `doc`
    form = MyDocumentForm(obj=doc)
    if form.validate_on_submit():
        form.populate_obj(doc)
        return redirect(doc.url_for('view'), code=303)
    return render_form(form, title="Edit document")

This one is lacking an outright mechanism by which to issue a 401 Forbidden. It instead limits edit access to whatever is provided by a valid anchor, using logic determined by the model's roles_for method.

@jace
Copy link
Member Author

jace commented Nov 24, 2017

The TeamEmail model in hasgeek/lastuser#185 describes an anchor chain. While that model is likely to be deprecated by the switch to principals in hasgeek/lastuser#91, here's the outline:

  1. An outbound anchor (TeamEmail) is sent an email containing what is known to be a shared link.
  2. The shared link (an inbound URL anchor) is now associated with the browser session (another inbound anchor) and by extension with any principal or document associated with that inbound anchor.
  3. Since the shared link is assumed to be for more than one recipient, the associations formed from it (browser sessions or principals) are assumed to form a group principal (currently Organization or Team).

Contrast this with the very similar flow used for merging user accounts:

  1. An outbound anchor (OAuth login) is invoked to send the user away. The anchor could be associated with a User principal (login as a user) or an Organization principal (connect an organization's Twitter account).
  2. The user returns with an inbound anchor (OAuth token grant) and this is (a) exchanged for an OAuth token, and (b) associated with the browser session. (Unless the OAuth token allows sending messages to the user, it's not an outbound anchor.)
  3. If there is an existing User principal attached to the browser session, one of two things happens:
    1. If the first outbound anchor was for a User, it's assumed there is a conflicting User account and the user is redirected to the merge flow. At present this is the only flow available.
    2. If the first outbound anchor was for an Organization or Team, the OAuth token and the current User principal are now associated with that principal. At present this flow is not available.

@jace
Copy link
Member Author

jace commented Nov 24, 2017

These use cases suggest that a Flow model that connects documents, principals and anchors needs to be defined, distinct from just anchors.

@jace
Copy link
Member Author

jace commented Nov 24, 2017

The @url_anchor decorator above feels a bit superfluous. It adds nothing to what @app.route achieves. It doesn't even help validate the URL. If the access key is defined on the document model, @load_models can do the validation by itself.

While we need the URL anchor to map into a flow, it needs a more useful form.

@jace
Copy link
Member Author

jace commented Oct 16, 2019

Inbound anchors are all tokens of one form or the other. A cookie token, an Authorisation header token, a URL query parameter token or some other.

There's no need for a new term like "inbound anchor". Let's just call them tokens.

This frees up the word "anchor" for the outbound variety. An anchor is where you can contact someone. An anchor is not a subsidiary of a user, but a first class entity that may be currently affiliated to one or more users, but could previously be affiliated to someone else.

In Lastuser, that will mean a new EmailAnchor (or Email) model that has no dependencies, while the existing UserEmail and UserEmailClaim will be recast as join models, transferring their natural keys over.

Further, since an anchor contains PII and is subject to privacy/right to be forgotten concerns (including in the form of an unsubscribe), the anchor should not be indexed using the natural key, but using a hash of it. The hash will need an uncommon salt to defend against reversal using rainbow tables. Unfortunately, the salt here cannot be random as with passwords. It has to either derive from the natural key itself, or should be fixed for the data table, since the salt must be known before the record can be looked up.

@jace
Copy link
Member Author

jace commented May 31, 2021

Lastuser was merged into Funnel in 2020. Email anchors were implemented as an EmailAddress model in hasgeek/funnel#714. A phone anchor model is pending. "Forgotten" email anchors (for eg, a user requested a block) are tracked using a hash. This is harder for phone numbers because the limited search space is vulnerable to a rainbow table attack (only 10 digits in India, with a smaller active subset). Phone number hashing will require more entropy for protection.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant