Commit Graph

250 Commits

Author SHA1 Message Date
Steven 'Steve' Kendall
76ecfea853 Merge remote-tracking branch 'upstream/master' into two-steps-docfix 2020-01-17 14:45:16 -05:00
Steven 'Steve' Kendall
aedac1b423 fixup // to /// for DeviceFlow doc 2020-01-14 15:05:26 -05:00
Glenn Griffin
9238153723 Move to hyper 0.13.1!!!! 2019-12-18 09:07:45 -08:00
Glenn Griffin
348a59d96e Create the token file with more secure permissions on unix.
This creates files with 0600 permissions on unix. Still the default
permissions on non-unix platforms.
2019-12-18 09:07:45 -08:00
Glenn Griffin
5c0334ee6f Add debug logging.
Could be helpful when troubleshooting issues with various providers if
the user is able to turn on debug logging. The most critical logging
provided is the request and responses sent and received from the oauth
servers.
2019-12-18 09:07:45 -08:00
Glenn Griffin
36d186deb4 Authenticator now returns an AccessToken.
What was previously called Token is now TokenInfo and is merely an
internal implementation detail. The publicly visible type is now called
AccessToken and differs from TokenInfo by not including the refresh
token. This makes it a smaller type for users to pass around as well as
reducing the ways that a refresh token may be leaked. Since the
Authenticator is responsible for refreshing the tokens there isn't any
reason users should need to concern themselves with refresh tokens.
2019-12-18 09:07:45 -08:00
Glenn Griffin
045c3e7735 Move all the end to end tests into an integration test
All the same functionality can be tested through the publicly exposed
API providing more extensive coverage.
2019-12-18 09:07:45 -08:00
Glenn Griffin
5e39a81894 Go back to waiting for disk writes on every token set.
Defering disk writes is still probably a good idea, but unfortunately
there are some tradeoffs with rust's async story that make it non-ideal.
Ideally we would defer writes, but have a Drop impl on DiskStorage that
waited for all the deferred writes to complete. While it's trival to
create a future that waits for all deferred writes to finish it's not
currently possible to write a Drop impl that waits on a future.

It would be possible to write an inherent async fn that takes self by
value and waits for the writes, but that method would need to be
propogated up all the way to users of the library and they would need to
remember to invoke it before dropping the Authenticator.
2019-12-18 09:07:45 -08:00
Glenn Griffin
1b39ce4413 Refactor storage to only use a BTreeMap.
Keeping the same tokens in a Vec and BTreeMap created more overhead than
was warranted. It makes much more sense to simply iterator over the
BTreeMap than keep a separate Vec.
2019-12-18 09:07:45 -08:00
Glenn Griffin
497ebf61c5 Add a test to ensure that Authenticator is Send+Sync 2019-12-18 09:07:45 -08:00
Glenn Griffin
c829fb453d cargo fmt 2019-12-18 09:07:45 -08:00
Glenn Griffin
50824c7777 Use Arc<Mutex<T>> rather than Rc<RefCell<T>> in DiskStorage.
This keeps DiskStorage Sync + Send and therefore Authenticator Sync +
Send. The DiskStorage was threadsafe because JSONTokens contains a Mutex
around all the Rc<RefCell<T>> objects, but there's no way to prove to
the type system that none of the Rc's get cloned to an alias used
outside the Mutex so it's not provably safe. I'll probably reevaluate
the design here, but in the meantime the double locking is fine.
2019-12-18 09:07:45 -08:00
Glenn Griffin
635bd5e21a Fix a bug introduced in the storage layer.
When bloom filters were added the btreemap values changed to be a
vector of tokens to accomodate the possibility of bloom filter
collisions. The implementation naively just pushed new tokens onto the
vec even if they were replacing previous tokens meaning old tokens were
still kept around even after a refresh has replaced it. To fix this
efficiently the storage layer now tracks both a hash value and a bloom
filter along with each token. Their is a map keyed by hash for every
token that points to a reference counted version of the token, and each
token also exists in a separate vector. Updates to existing tokens
happens in place, when new entries are added they are added to both data
structures.
2019-12-18 09:07:45 -08:00
Glenn Griffin
0a4c1e79d2 Make DeviceFlowDelegate::present_user_code return a Future.
This is to allow for implementations to use async code. The returned
Future will be awaited before polling for the token begins.
2019-12-18 09:07:45 -08:00
Glenn Griffin
4521e2f246 Rename PollInformation DeviceAuthResponse.
Have it correctly handle either verification_uri or verification_url and
deserialize into a struct that has the data types desired.
2019-12-18 09:07:45 -08:00
Glenn Griffin
0525926bb2 Improve Token
Remove expires_in in favor of only having an expires_at DateTime field.
Add a from_json method that deserializes from json data into the
appropriate Token (or Error) and use that consistently throughout the
codebase.
2019-12-18 09:07:45 -08:00
Glenn Griffin
d0880d07db Refactor error handling and as a consequence delegates.
This Removes RefreshError and PollError. Both those types can be fully
represented within Error and there seems little value in distinguishing
that they were resulting from device polling or refreshes. In either
case the user will need to handle the response from token() calls
similarly. This also removes the AuthenticatorDelegate since it only
served to notify users when refreshes failed, which can already be done
by looking at the return code from token. DeviceFlow no longer has the
ability to set a wait_timeout. This is trivial to do by wrapping the
token() call in a tokio::Timeout future so there's little benefit for
users specifying this value. The DeviceFlowDelegate also no longer has
the ability to specify when to abort, or alter the interval polling
happens on, but it does gain understanding of the 'slow_down' response
as documented in the oauth rfc. It seemed very unlikely the delegate was
going to do anything other that timeout after a given time and that's
already possible using tokio::Timeout so it needlessly complicated the
implementation.
2019-12-18 09:07:45 -08:00
Glenn Griffin
fe5ea9bdb2 Rename Error::ClientError and RefreshError::ConnectionError to HttpError.
PollError already contained an HttpError variant so this makes all
variants that contain a hyper::Error consistently named.
2019-12-18 09:07:45 -08:00
Glenn Griffin
ae2258bc7a Remove code to strip trailing newlines for backwards compatibility.
Based on the comment in the code the justification for the change was
because old FlowDelegates used to contain newlines and changing how the
returned string from the delegate was handled would be a breaking
change. In this case it should be safe to remove the hack because we're
breaking compatibility. All users that once implemented FlowDelegate
will now need to implement InstalledFlowDelegate and uphold the new
contract which implicitly means the authcode returned should represent
the authcode and nothing more. No manipulation of the returned string
will be done.
2019-12-18 09:07:45 -08:00
Glenn Griffin
2253c60b89 InstalledFlowDelegate::present_user_url should return a String error.
Prior to this change the only place present_user_url is called overwrote
the error with a static string. After this change the error returned is
appended to the message. No need to make the signature more complicated
when the error is always going to be flattened to a string anyway.
2019-12-18 09:07:45 -08:00
Glenn Griffin
8030d31da9 Reconsider Error variants
BadServerResponse didn't seem to be adequately different from
NegativeServerResponse so it's been removed. NegativeServerResponse is
now a struct variant with field names 'error' and 'error_description' to
self document what it contains.

InvalidClient and InvalidScope were only ever created based on string
parsing of the returned error message from the server. This seemed
overly specific to a particular implementation and didn't provide much
value to callers. Printing a NegativeServerResponse would provide users
the same information.

The caching layer never returns errors anymore so remove that variant.
2019-12-18 09:07:45 -08:00
Glenn Griffin
25ba7f0b1f Move the impls for PollError into error.rs
These should have been moved when error.rs was created but were missed.
2019-12-18 09:07:45 -08:00
Glenn Griffin
d63396a740 Split FlowDelegate into DeviceFlowDelegate and InstalledFlowDelegate.
Each flow invokes a non-overlapping set of methods. There doesn't appear
to be any benefit in having both flows use a common trait. The benefit
of splitting the traits is that it makes it clear which methods need to
be updated for each flow type where previously comments were required to
communicate that information.
2019-12-18 09:07:45 -08:00
Glenn Griffin
8e38d3976b Make helpers that read from disk async 2019-12-18 09:07:45 -08:00
Glenn Griffin
1d25341c66 Remove AuthenticatorDelegate::{client_error, request_failure}
These methods are never called.
2019-12-18 09:07:45 -08:00
Glenn Griffin
73af51bab6 Remove what appears to be an unnecessary replace.
decode_rsa_key does a .replace('\\n', '\n') which replaces any literal
2 byte sequence '\n' with a newline character. The original commit that
added it is 38fd851493 but there's no
mention of why it's needed and there are no test cases that fail when
it's omitted. Any file that has a literal 2 byte sequence of '\\n' is
surely not a valid pkcs8 file so it seems perfectly valid to skip the
replace (and allocation) and return an error if one is encountered.

If it's determined that this check is needed for some reason please add
a unit test that explains why for future contributors.
2019-12-18 09:07:45 -08:00
Glenn Griffin
e72b4c2335 Rename service_account_key_from_file to read_service_account_key
This makes the name consistent with the other helper
read_application_secret.
2019-12-18 09:07:45 -08:00
Glenn Griffin
5256f642d7 Tie ServiceAccount's into Authenticator.
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.
2019-12-18 09:07:45 -08:00
Glenn Griffin
d4b80a0c5c Fix a bug in refactoring the storage layer.
Attempting to load from disk when the file does not exist should not
return an error and should continue with an empty set of tokens.
2019-12-18 09:07:45 -08:00
Glenn Griffin
8f84553769 Use a bloom filter to track scopes.
Each token is stored along with a 64bit bloom filter that is created
from the set of scopes associated with that token. When retrieving
tokens for a set of scopes a new bloom filter is calculated for the
requested scopes and compared to the filters of all previously fetched
scopes. The bloom filter allows for efficiently skipping entries that
are definitely not a superset.
2019-12-18 09:07:45 -08:00
Glenn Griffin
7c1664142c Don't serialize the scope hash.
Seahash is a stable hash, but there isn't any value in serializing it's
value. Instead calculate the value of the hash when deserializing and
only serialize the scopes and tokens. This provides flexibility to
change the hash value in the future without breaking the on-disk format.
2019-12-18 09:07:45 -08:00
Glenn Griffin
5be2eadeca Use the storage token hash more effectively.
Use a BTreeMap to key the tokens by the hash value. On retrieval first
look for a matching hash value and return it if it exists. Only if it
does not exist does it fallback to the subset matching. This makes the
common case where an application uses a consistent set of scopes more
efficient without detrimentally impacting the less common cases.
2019-12-18 09:07:45 -08:00
Glenn Griffin
089c6ba212 Use seahash rather that DefaultHasher.
DefaultHasher is not documented as being consistent. It's best to not
trust that the resulting hash value is consistent even across different
executions of the same binary and even more so across different
versions.
2019-12-18 09:06:33 -08:00
Glenn Griffin
baa8d56653 JSONToken should always contain scopes.
This is already the case when writing a token file. Presumably the only
reason it was an Option was for backwards compatibility, but we're
already breaking compatibility with the change to the hash value so this
seems like an appropriate time to make the change.

This change also highlights how unused the hash value has been
previously. Future changes plan to use the hash value for more efficient
handling.
2019-12-18 09:03:35 -08:00
Glenn Griffin
b70d07aac2 storage set method should just accept a Token rather than Option<Token>.
No caller ever provided a None value. Presumably a None value should
delete the token, but it didn't do that and that would be more clearly
done with a remove or delete method.
2019-12-18 09:03:35 -08:00
Glenn Griffin
4b4b2fe3f4 refactor storage get and set methods.
These previously accepted a hash and scopes. The hash was required to be
a hash of the provided scopes but that wasn't enforced by the compiler.
We now have the compiler enforce that by creating a HashedScopes type
that ties the scopes and the hash together and pass that into the
storage methods.
2019-12-18 09:03:34 -08:00
Glenn Griffin
f76dea5dbd Add header styling to the AuthenticatorBuilder rustdoc 2019-12-18 08:59:43 -08:00
Glenn Griffin
d17c760276 Remove an obsolete todo. 2019-12-18 08:59:43 -08:00
Glenn Griffin
68a30ea0fe Tidy up tests. 2019-12-18 08:59:43 -08:00
Glenn Griffin
ca453c056c Improve documentation 2019-12-18 08:59:43 -08:00
Glenn Griffin
e5aa32b3cf Tidy up some imports.
No more need to macro_use serde. Order the imports consistently (albeit
somewhat arbitrary), starting with items from this crate, followed by
std, followed by external crates.
2019-12-18 08:59:43 -08:00
Glenn Griffin
ba0b8f366a Rename RequestError to Error
RequestError is the error value that encompasses all errors from the
authenticators. Their is an established convention of using Error as the
name for those types.
2019-12-18 08:57:24 -08:00
Glenn Griffin
0fe66619dd Minimize the number of items on the rustdoc landing page.
Restructure the modules and imports to increase the signal to noise
ration on the cargo doc landing page. This includes exposing some
modules as public so that they can contain things that need to be public
but that users will rarely need to interact with. Most items from
types.rs were moved into an error.rs module that is now exposed
publicly.
2019-12-18 08:57:24 -08:00
Glenn Griffin
3aadc6b0ef Major refactor of the public API.
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.
2019-12-18 08:57:24 -08:00
Glenn Griffin
911fec82f1 Make FlowDelegate object safe. 2019-12-18 08:57:24 -08:00
Glenn Griffin
c0919bee86 allow setting grant_type for device code 2019-12-18 08:57:24 -08:00
Glenn Griffin
88a8f74406 Refactor token storage.
The current code uses standard blocking i/o operations (std::fs::*) this
is problematic as it would block the entire futures executor waiting for
i/o.

This change is a major refactoring to make the token storage mechansim
async i/o friendly. The first major decision was to abandon the GetToken
trait. The trait is only implemented internally and there was no
mechanism for users to provide their own, but async fn's are not
currently supported in trait impls so keeping the trait would have
required Boxing futures. This probably would have been fine, but seemed
unnecessary. Instead of a trait the storage mechanism is just an enum
with a choice between Memory and Disk storage.

The DiskStorage works primarily as it did before, rewriting the entire
contents of the file on every set() invocation. The only difference is
that we now defer the actual writing to a separate task so that it does
not block the return of the Token to the user. If disk i/o is too slow
to keep up with the rate of incoming writes it will push back and
will eventually block the return of tokens, this is to prevent a buildup
of in-flight requests. One major drawback to this approach is that any
errors that happen on write are simply logged and no delegate function
is invoked on error because the delegate no longer has the ability to
say to sleep, retry, etc.
2019-12-18 08:57:24 -08:00
Glenn Griffin
e1f0819156 Authenticator should handle the server not returning a refresh_token.
Currently the authenticator will panic when trying to refresh an expired
token that does not have a refresh token. This change handles it so that
the authenticator will only attempt a refresh when a refresh_token
exists, and otherwise will attempt to retrieve a fresh token.
2019-12-18 08:53:22 -08:00
Glenn Griffin
060eb92bf7 Refactor JWT handling in ServiceAccountAccess.
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.
2019-12-18 08:53:22 -08:00
Glenn Griffin
05f7c10533 Remove unnecessary 'static bounds 2019-12-18 08:53:22 -08:00