Prior to this change DeviceFlow and InstalledFlow were used within
Authenticator, while ServiceAccountAccess was used on it's own. AFAICT
this was the case because ServiceAccountAccess never used refresh tokens
and Authenticator assumed all tokens contained refresh tokens.
Authenticator was recently modified to handle the case where a token
does not contain a refresh token so I don't see any reason to keep the
service account access separate anymore. Folding it into the
authenticator provides a nice consistent interface, and the service
account implementation no longer needs to provide it's own caching since
it is now handled by Authenticator.
1) Remove the GetToken trait. The trait seemed to be organically
designed. It appeared to be mostly tailored for simplifying the
implementation since there was no way for users to provide their own
implementation to Authenticator. It sadly seemed to get in the way of
implementations more than it helped. An enum representing the known
implementations is a more straightforward way to accomplish the goal and
also has the benefit of not requiring Boxing when returning features
(which admittedly is a minor concern for this use case).
2) Reduce the number of type parameters by using trait object for
delegates. This simplifies the code considerably and the performance
impact of virtual dispatch for the delegate calls is a non-factor.
3) With the above two simplifications it became easier to unify the
public interface for building an authenticator. See the examples for how
InstalledFlow, DeviceFlow, and ServiceAccount authenticators are now created.
Avoid reading and parsing the private key file on every invocation of
token() in favor or reading it once when the ServiceAccountAccess is
built. Also avoid unnecessary allocations when signing JWT tokens and
renamed sub to subject to avoid any confusion with the std::ops::Sub
trait.
Along with the public facing change the implementation has been modified
to no longer clone the scopes instead using the pointer to the scopes
the user provided. This greatly reduces the number of allocations on
each token() call.
Note that this also changes the hashing method used for token storage in
an incompatible way with the previous implementation. The previous
implementation pre-sorted the vector and hashed the contents to make the
result independent of the ordering of the scopes. Instead we now combine
the hash values of each scope together with XOR, thus producing a hash
value that does not depend on order without needing to allocate another
vector and sort.
Beyond simply moving to the builder pattern for intialization this has a
few other effects.
The DeviceFlow and InstalledFlow can no longer be used without an
associated Authenticator. This is becaus they no longer have any
publicly accessible constructor. All initialization goes through the
Authenticator. This also means that the flows are always initialized
with a clone of the hyper client used by the Authenticator.
The authenticator uses the builder pattern which allows omitting
optional fields. This means that if users simply want a default hyper
client, they don't need to create one explicitly. One will be created
automatically. If users want to specify a hyper client (maybe to allow
sharing a single client between different libraries) they can still do so
by using the hyper_client method on the builder. Additionally for both
AuthenticatorDelegate's and FlowDelegate's if the user does not specify
an override the default ones will be used.
The builders are now exposed publicly with the names of Authenicator,
InstalledFlow, and DeviceFlow. The structs that actually implement those
behaviors are now hidden and only expose the GetToken trait. This means
some methods that were previously publicly accessible are no longer
available, but the methods appeared to be implementation details that
probably shouldn't have been exposed anyway.