Repository navigation
Feat/python sdk cipher #1453
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
Merged
yujiyokoo
merged 3 commits into
feat/python-sdk-declaration-meaning
from
feat/python-sdk-cipher
Nov 13, 2026
Merged
Feat/python sdk cipher #1453
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| //! The credentials a `Cipher` is built with, found as every SDK finds them. | ||
| //! | ||
| //! `stack_auth::AutoStrategy` reads `CS_CLIENT_ACCESS_KEY` and | ||
| //! `CS_WORKSPACE_CRN`, then the profile store (`~/.cipherstash`, or | ||
| //! `CS_CONFIG_PATH`). With a profile and no access key, it reads and parses | ||
| //! the profile's token file. It makes no request. The Python tests cover it: | ||
| //! a Rust test would set environment variables that other tests in the same | ||
| //! process read. | ||
|
|
||
| use stack_auth::{AuthError, AutoStrategy, HttpRequest, HttpResponse, HttpTransport, RequestError}; | ||
|
|
||
| use crate::refusal::Refusal; | ||
|
|
||
| /// Check that the credentials are there. | ||
| /// | ||
| /// The strategy `AutoStrategy` finds is built over [`NoRequest`], so no HTTP | ||
| /// client, TLS configuration or root store is built, and nothing is kept. | ||
| /// stack-auth's `http` feature is off (`Cargo.toml`), so none is compiled | ||
| /// either. The ticket that makes the first request turns it on and keeps a | ||
| /// strategy with a real transport. The work here is the environment, the | ||
| /// service URL for the CRN's region, and one small file where there is a | ||
| /// profile. | ||
| /// | ||
| /// # Errors | ||
| /// | ||
| /// The engine's refusal, such as `stack_auth_not_authenticated` when there | ||
| /// are no credentials. The message names the variable or file to fix, for | ||
| /// a Python user. | ||
| pub fn detect() -> Result<(), Refusal> { | ||
| AutoStrategy::builder() | ||
| .transport(NoRequest) | ||
| .detect() | ||
| .map(drop) | ||
| .map_err(|error| { | ||
| let refused = Refusal::of(&error); | ||
| Refusal { | ||
| message: message(&error).unwrap_or(refused.message.clone()), | ||
| ..refused | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| /// The message for a Python user, where the engine's names a Rust API or | ||
| /// no variable at all. `None` keeps the engine's message and help. | ||
| fn message(error: &AuthError) -> Option<String> { | ||
| let text = match error { | ||
| AuthError::NotAuthenticated(_) => "no credentials were found. Set CS_CLIENT_ACCESS_KEY \ | ||
|
yujiyokoo marked this conversation as resolved.
Resolved
|
||
| and CS_WORKSPACE_CRN, or log in with `stash auth login`." | ||
| .to_owned(), | ||
| AuthError::MissingWorkspaceCrn(_) => { | ||
| "CS_CLIENT_ACCESS_KEY is set and CS_WORKSPACE_CRN is not. Set both.".to_owned() | ||
| } | ||
| AuthError::InvalidAccessKey(_) => { | ||
| format!("{error}. An access key has the form CSAK<key-id>.<secret>.") | ||
| } | ||
| AuthError::InvalidCrn(_) => format!( | ||
| "{error}. CS_WORKSPACE_CRN has the form crn:<region>:<workspace-id>, \ | ||
| such as crn:ap-southeast-2.aws:ZVATKW3VHMFG27DY." | ||
| ), | ||
| AuthError::Region(_) => { | ||
| format!("{error}. The region is the middle part of CS_WORKSPACE_CRN.") | ||
| } | ||
| AuthError::InvalidUrl(_) => format!("{error}. CS_CTS_HOST, when set, must be a URL."), | ||
| AuthError::Store(_) => format!( | ||
| "{error}. The profile is in ~/.cipherstash, or in CS_CONFIG_PATH when set. \ | ||
| Log in again with `stash auth login`, or set CS_CLIENT_ACCESS_KEY." | ||
| ), | ||
| _ => return None, | ||
| }; | ||
| Some(text) | ||
| } | ||
|
|
||
| /// A transport that sends nothing. A strategy cannot be built without one. | ||
| struct NoRequest; | ||
|
|
||
| impl HttpTransport for NoRequest { | ||
| async fn send(&self, _request: HttpRequest) -> Result<HttpResponse, RequestError> { | ||
| Err(RequestError(Box::new(Refused))) | ||
| } | ||
| } | ||
|
|
||
| #[derive(Debug)] | ||
| struct Refused; | ||
|
|
||
| impl std::fmt::Display for Refused { | ||
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| f.write_str("this strategy only checks that the credentials exist; it sends no request") | ||
| } | ||
| } | ||
|
|
||
| impl std::error::Error for Refused {} | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| //! An engine error, in the four fields every SDK error has. | ||
|
|
||
| use miette::Diagnostic; | ||
|
|
||
| /// An engine error as the Python side raises it. | ||
| #[derive(Clone, Debug, PartialEq, Eq)] | ||
| pub struct Refusal { | ||
| /// The engine's diagnostic code, with `::` replaced by `_`: | ||
| /// `stack_encrypt_dynamic_plan`. | ||
| pub code: String, | ||
| /// The engine's message, with its help after it where it gives one. A | ||
| /// caller whose engine help names a Rust API writes its own message. | ||
| pub message: String, | ||
| /// The plan field at fault, where the engine names one. | ||
| pub field: Option<String>, | ||
| /// The engine's reason token, such as `no_fields`, where it gives one. | ||
| pub reason: Option<String>, | ||
| } | ||
|
|
||
| impl Refusal { | ||
| /// The code and message of `error`, with no field or reason. | ||
| pub fn of(error: &dyn Diagnostic) -> Self { | ||
| let message = match error.help() { | ||
| Some(help) => format!("{error} {help}"), | ||
|
yujiyokoo marked this conversation as resolved.
Resolved
|
||
| None => error.to_string(), | ||
| }; | ||
| Self { | ||
| code: error.code().map_or_else( | ||
| || "stack_unknown".to_owned(), | ||
| |code| python_code(&code.to_string()), | ||
| ), | ||
| message, | ||
| field: None, | ||
| reason: None, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// An engine diagnostic code as the SDK's `code`: `stack_encrypt::aead` | ||
| /// becomes `stack_encrypt_aead`. | ||
| pub fn python_code(engine_code: &str) -> String { | ||
| engine_code.replace("::", "_") | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::python_code; | ||
|
|
||
| #[test] | ||
| fn engine_codes_replace_the_path_separator() { | ||
| assert_eq!(python_code("stack_encrypt::aead"), "stack_encrypt_aead"); | ||
| assert_eq!( | ||
| python_code("stack_kms::load_keyset_forbidden"), | ||
| "stack_kms_load_keyset_forbidden" | ||
| ); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.