From 7808cacc226ced8d94bd569ccf8e03f7d866fd35 Mon Sep 17 00:00:00 2001 From: Yohei Kitamura Date: Wed, 15 May 2024 16:45:11 -0400 Subject: [PATCH] Preparing for open-sourcing --- .cargo/config.toml | 5 - .github/workflows/publish.yml | 8 +- CONTRIBUTING.md | 48 ++++----- Cargo.toml | 3 + README.md | 26 ++++- doc/binding_to_ruby.md | 191 ++++++++++++++++++++++++++++++++++ factbook.yaml | 7 -- 7 files changed, 241 insertions(+), 47 deletions(-) delete mode 100644 .cargo/config.toml create mode 100644 doc/binding_to_ruby.md diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index 793aa23..0000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,5 +0,0 @@ -[registry] -default = "artifactory" - -[registries.artifactory] -index = "sparse+https://mdsol.jfrog.io/artifactory/api/cargo/PlatformLibraries-cargo-prod-local/index/" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7200b6e..bc6e003 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,4 +1,4 @@ -name: Publish to Artifactory +name: Publish on: push: @@ -22,8 +22,6 @@ jobs: - name: Setup Rust toolchain uses: dtolnay/rust-toolchain@stable - - name: Publish to Artifactory - run: | - cargo publish --registry artifactory --allow-dirty --token "Bearer $PLATFORM_LIBRARIES_ARTIFACTORY_TOKEN" + - run: cargo publish env: - PLATFORM_LIBRARIES_ARTIFACTORY_TOKEN: "${{secrets.PLATFORM_LIBRARIES_ARTIFACTORY_TOKEN}}" + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6e61d59..618f872 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,42 +1,36 @@ # Contributing ## Requirements - +* Rust ## Installation - +This repo contains the submodule `mauth-protocol-test-suite` so requires a flag when initially cloning in order to clone and init submodules. -## Usage - +* Check out the latest develop to make sure the feature hasn't been implemented or the bug hasn't been fixed yet +* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it +* Fork the project +* Start a feature/bugfix branch +* Commit and push until you are happy with your contribution +* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally. -## Testing - +## Running Benchmark -## Branches & pull requests -We use the git-flow branch strategy. Features should be based off the `develop` branch and merged using GitHub pull requests. +If you make changes which could affect performance, please run the benchmark before and after the change as a sanity check. +``` +cargo bench +``` diff --git a/Cargo.toml b/Cargo.toml index a8495ee..5a62ba8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,9 @@ name = "mauth-core" version = "0.4.1" edition = "2021" +authors = ["Medidata Solutions "] +license = "MIT" +repository = "https://github.com/mdsol/mauth-core" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/README.md b/README.md index e224a18..7340fc5 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,29 @@ A library to generate and verify MAuth signatures ## Usage +Add the following to your `Cargo.toml`: + +```toml +[dependencies] +mauth-core = "0.4" +``` + +Here is an example of generating and verifying a signature: + +```rust +use mauth_core::signer::Signer; +use mauth_core::verifier::Verifier; + +let mauth_version = 2 + +let signer = Signer::new(app_uuid, private_key_data)?; +let signiture = signer.sign_string(mauth_version, verb, path, query, body, timestamp)? + +let verifier = Verifier::new(app_uuid, public_key_data)?; +let is_valid = verifier.verify_signature(mauth_version, verb, path, query, body, timestamp, signature)?; +``` + +You can find an example of binding MAuth Core to Ruby [here](./doc/binding_to_ruby.md). ## Contributing See [CONTRIBUTING](CONTRIBUTING.md). - -## Contact -See the [factbook](factbook.yaml). diff --git a/doc/binding_to_ruby.md b/doc/binding_to_ruby.md new file mode 100644 index 0000000..f6ef48e --- /dev/null +++ b/doc/binding_to_ruby.md @@ -0,0 +1,191 @@ +# Example of binding MAuth Core to Ruby + +This document is an example of how to bind MAuth Core in [MAuth-Client ruby](https://github.com/mdsol/mauth-client-ruby). + +## Required Ruby Gems + +The following Ruby gems are used to bind: +- [Magnus](https://github.com/matsadler/magnus) +- [rb-sys](https://github.com/oxidize-rb/rb-sys) +- [rake-compiler](https://github.com/rake-compiler/rake-compiler) + +## Modifying MAuth-Client ruby + +Add the following gems to [mauth-client.gemspec](https://github.com/mdsol/mauth-client-ruby/blob/master/mauth-client.gemspec): + +``` + spec.add_dependency "rake-compiler" + spec.add_dependency "rb_sys" +``` + +And update [Rakefile](https://github.com/mdsol/mauth-client-ruby/blob/master/Rakefile) to add the following extension task: + +``` +require "rake/extensiontask" + +task build: :compile + +Rake::ExtensionTask.new("mauth_core_binder") do |ext| + ext.lib_dir = "lib/mauth" + ext.source_pattern = "*.{rs,toml}" +end + +task default: %i[compile spec] +``` + +## Generate extension library + +Prepare an extension directory and create a new Cargo package to bind mauth-core: + +```sh +mkdir -p ext/mauth_core_binder +cd ext/mauth_core_binder + +cargo init . --lib +cargo add rb-sys rb-allocator +cargo add magnus --features rb-sys-interop +``` + +Set the crate-type attribute to `cdylib` in Cargo.toml: + +``` +[lib] +crate-type = ["cdylib"] +``` + +Add `ext/mauth_core_binder/extconf.rb` file: + +```ruby +require "mkmf" +require "rb_sys/mkmf" + +create_rust_makefile("mauth_core_binder/mauth_core_binder") +``` + +At this point, you are ready to compile the rust extension by calling: +``` +bundle exec rake compile +``` + +Add the `mauth-core` crate to Cargo.toml: + +``` +[dependencies] +mauth-core = "0.4" +``` + +Then, add some Rust code to the `lib.rs` file to call mauth-core: + +```rust +use magnus::{define_class, exception, function, method, prelude::*, Error}; +use rb_allocator::ruby_global_allocator; + +use mauth_core::signer::Signer; +use mauth_core::verifier::Verifier; + +ruby_global_allocator!(); + +#[magnus::wrap(class = "MAuthCore")] +struct MAuthCore { + signer: Signer, +} + +impl MAuthCore { + fn new(app_uuid: String, private_key_data: String) -> Self { + let signer = + Signer::new(app_uuid, private_key_data).expect("Failed to initialize MAuthCore"); + + Self { signer } + } + + fn sign_string( + &self, + version: u8, + verb: String, + path: String, + query: String, + body: magnus::Value, + timestamp: String, + ) -> Result { + + let body = magnus::RString::from_value(body).ok_or_else(|| Error::new( + exception::standard_error(), + "expected string", + ))?; + + let body_as_slice; + unsafe { + body_as_slice = body.as_slice(); + } + + self.signer + .sign_string(version, verb, path, query, body_as_slice, timestamp) + .map_err(|err| { + Error::new( + exception::standard_error(), + format!("Failed to generate sigunatures: {:?}", err), + ) + }) + } + + fn verify_signature( + &self, + app_uuid: String, + public_key_data: String, + version: u8, + verb: String, + path: String, + query: String, + body: magnus::Value, + timestamp: String, + signature: String, + ) -> Result { + + let body = magnus::RString::from_value(body).ok_or_else(|| Error::new( + exception::standard_error(), + "expected string", + ))?; + + let body_as_slice; + unsafe { + body_as_slice = body.as_slice(); + } + + match Verifier::new(app_uuid, public_key_data) { + Ok(verifier) => verifier + .verify_signature(version, verb, path, query, body, timestamp, signature) + .map_err(|err| { + Error::new( + exception::standard_error(), + format!("Failed to verify sigunatures: {:?}", err), + ) + }), + Err(err) => Err(Error::new( + exception::standard_error(), + format!("Failed to initialize verifier: {:?}", err), + )), + } + } +} + +#[magnus::init] +fn init() -> Result<(), Error> { + let class = define_class("MAuthCore", Default::default())?; + class.define_singleton_method("new", function!(MAuthCore::new, 2))?; + class.define_method("sign_string", method!(MAuthCore::sign_string, 6))?; + class.define_method("verify_signature", method!(MAuthCore::verify_signature, 9))?; + + Ok(()) +} +``` + +By adding the `#[magnus::wrap(class = "MAuthCore")]` annotation, the MAuthCore struct is wrapped in a Ruby object and it is callable from Ruby. + +Using the `#[magnus::init]` attribute to mark the init function so it can be correctly exposed to Ruby. + +Now you can call `mauth-core` from Ruby code by doing this: + +```ruby +mauth_core = MAuthCore.new(app_uuid, public_key_data) +mauth_core.sign_string(mauth_version, verb, path, query, body, timestamp) +``` diff --git a/factbook.yaml b/factbook.yaml index 38d4551..4aa57e3 100644 --- a/factbook.yaml +++ b/factbook.yaml @@ -12,13 +12,6 @@ metadata: teams: - name: Architecture Enablement number: 119 - email: ae@mdsol.com people: - role: technical owner email: ykitamura@mdsol.com - channels: - - url: https://mdsol.slack.com/messages/ae - automated_messaging: false - role: slack - annotations: - jira: https://jira.mdsol.com/secure/RapidBoard.jspa?rapidView=3118