-
Notifications
You must be signed in to change notification settings - Fork 147
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
251 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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 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 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,94 @@ | ||
# Foodsoft API | ||
|
||
Foodsoft currently provides a JSON REST API that gives access to _member_ operations | ||
like listing open orders, updating the ordergroup's order, and listing financial | ||
transactions. (Later versions of the API might include admin-related functionality.) | ||
|
||
The API is documented using [Open API 2.0](https://github.com/OAI/OpenAPI-Specification) | ||
/ [Swagger](http://swagger.io/) in [swagger.v1.yml](swagger.v1.yml). | ||
This provides a machine-readable reference that is used for | ||
[running tests](https://github.com/westfieldlabs/apivore) and providing documentation. | ||
|
||
## API endpoint documentation | ||
|
||
>> [View API documentation](https://petstore.swagger.io/?url=https%3A%2F%2Fraw.githubusercontent.com%2Ffoodcoops%2Ffoodsoft%2Fmaster%2Fdoc%2Fswagger.v1.yml) << | ||
|
||
The above documentation can communicate with the API directly on a local development | ||
installation of Foodsoft at [http://localhost:3000/f](http://localhost:3000/f). You'll need | ||
to give access to the application by running in the rails console | ||
|
||
```ruby | ||
app = Doorkeeper::Application.new | ||
app.name = 'Swagger'; app.scopes = 'all'; app.uid = 'your-client-id'; app.redirect_uri = 'https://petstore.swagger.io/o2c.html' | ||
app.save! | ||
``` | ||
|
||
|
||
## Security | ||
|
||
Uses the [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper) gem, | ||
which provides an OAuth2 provider. | ||
|
||
|
||
### Authorization code flow | ||
|
||
This is the recommended flow for server-side web applications, where | ||
members login with Foodsoft, then redirected to the app, which then obtains | ||
an access token using the authorization code supplied at redirection. | ||
|
||
Before you can obtain an access token, the client needs to obtain an id and secret. | ||
(You can currently skip this for the password credentials flow.) This needs to be | ||
done for each Foodsoft scope by an admin. | ||
|
||
1. Click on the _Apps_ button at the right in Foodsoft's configuration screen. | ||
2. Click on _New application_ | ||
3. Enter any _Name_ and put the website of your app in _Redirect URI_ and _Submit_. | ||
4. Click on the new applications' name for the app id and secret. | ||
5. To quickly test, logging into the app, press _Authorize_. | ||
|
||
Not that the user doesn't need to confirm that he is giving the app access to his | ||
Foodsoft account by default, since apps can only be created by admins. If you | ||
want to change that, see disable `skip_authorization` in `config/initializers/doorkeeper.rb`. | ||
|
||
[Read more](https://github.com/doorkeeper-gem/doorkeeper/wiki/Authorization-Code-Flow). | ||
|
||
|
||
### Implicit flow | ||
|
||
This is the recommended flow for client-side web applications. It looks a lot | ||
like the authorization code flow, but when redirecting back to the app, the | ||
access token is available directly as part of the url _fragment_ (`window.location.hash`). | ||
|
||
This flow also needs to be registered in Foodsoft as in the authorization code flow. | ||
You only need the `client_id` though, not the secret. | ||
|
||
**note** please make sure you understand sections | ||
[4.4.2](http://tools.ietf.org/html/rfc6819#section-4.4.2) and | ||
[4.4.3](http://tools.ietf.org/html/rfc6819#section-4.4.3) of the OAuth2 Threat | ||
Model document before using this flow. | ||
|
||
You may find Doorkeeper's [implicit_grant_test](https://github.com/doorkeeper-gem/doorkeeper/blob/master/spec/requests/flows/implicit_grant_spec.rb) useful. | ||
|
||
|
||
### Password credentials flow | ||
|
||
The API uses OAuth2 based on [Doorkeeper](https://github.com/doorkeeper-gem). | ||
To obtain a token using a username/password directly, you can do this: | ||
|
||
```ruby | ||
require 'oauth2' | ||
c = OAuth2::Client.new('client_id', 'secret', site: 'http://localhost:3002/f/', authorize_url: 'oauth/authorize', token_url: 'oauth/token') | ||
c.password.get_token('admin', 'secret').token | ||
# => "1234567890abcdef1234567890abcdef1234567890abcdef123456790abcdef1" | ||
``` | ||
|
||
Now use this token as value for the `access_token` when accessing the API, like | ||
http://localhost:3002/f/api/v1/financial_transactions/1?access_token=12345... | ||
|
||
[Read more](https://github.com/doorkeeper-gem/doorkeeper/wiki/Client-Credentials-flow). | ||
|
||
|
||
## Logout | ||
|
||
When the user logs out of Foodsoft, all access tokens are destroyed, except when | ||
the token's scope includes `offline_access` (so offline applications are possible). |
This file contains 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,90 @@ | ||
swagger: '2.0' | ||
info: | ||
title: Foodsoft API v1 | ||
version: '1.0.0' | ||
description: > | ||
[Foodsoft](https://github.com/foodcoops/foodsoft) is web-based software to manage | ||
a non-profit food coop (product catalog, ordering, accounting, job scheduling). | ||
This is a description of Foodsoft's API v1, which exposes operations that a member | ||
can do (later API versions may include admin functionality). It includes listing | ||
open orders, and changing what the member wants to order (in the ordergroup). | ||
Note that each food cooperative typically has their own instance (on a shared | ||
server or their own installation), and there are just as many APIs (if the Foodsoft | ||
version is recent enough). | ||
This API description points to the default development url with the default | ||
Foodsoft scope - that would be [http://localhost:3000/f](http://localhost:3000/f). | ||
You may find the search parameters for index endpoints lacking. They are not | ||
documented here, because there are too many combinations. For now, you'll need | ||
to resort to [Ransack](https://github.com/activerecord-hackery/ransack) and | ||
looking at Foodsoft's `ransackable_*` model class methods. | ||
externalDocs: | ||
description: General Foodsoft API documentation | ||
url: https://github.com/foodcoops/foodsoft/blob/master/doc/API.md | ||
|
||
# development url with default scope | ||
host: localhost:3000 | ||
schemes: | ||
- 'http' | ||
basePath: /f/api/v1 | ||
|
||
produces: | ||
- 'application/json' | ||
|
||
paths: | ||
|
||
definitions: | ||
Error: | ||
type: object | ||
properties: | ||
error: | ||
type: string | ||
description: error code | ||
error_description: | ||
type: string | ||
description: human-readable error message (localized) | ||
Error404: | ||
type: object | ||
properties: | ||
error: | ||
type: string | ||
description: '<tt>not_found</tt>' | ||
error_description: | ||
$ref: '#/definitions/Error/properties/error_description' | ||
Error401: | ||
type: object | ||
properties: | ||
error: | ||
type: string | ||
description: '<tt>unauthorized</tt>' | ||
error_description: | ||
$ref: '#/definitions/Error/properties/error_description' | ||
Error403: | ||
type: object | ||
properties: | ||
error: | ||
type: string | ||
description: '<tt>forbidden</tt>' | ||
error_description: | ||
$ref: '#/definitions/Error/properties/error_description' | ||
Error422: | ||
type: object | ||
properties: | ||
error: | ||
type: string | ||
description: '<tt>not_acceptable</tt>' | ||
error_description: | ||
$ref: '#/definitions/Error/properties/error_description' | ||
|
||
securityDefinitions: | ||
foodsoft_auth: | ||
type: oauth2 | ||
flow: implicit | ||
authorizationUrl: http://localhost:3000/f/oauth/authorize | ||
scopes: | ||
all: full access to user functions | ||
offline_access: retain access after user has logged out |
This file contains 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,19 @@ | ||
require 'spec_helper' | ||
require 'apivore' | ||
|
||
# we want to load a local file in YAML-format instead of a served JSON file | ||
class SwaggerCheckerFile < Apivore::SwaggerChecker | ||
def fetch_swagger! | ||
YAML.load(File.read(swagger_path)) | ||
end | ||
end | ||
|
||
describe 'API v1', type: :apivore, order: :defined do | ||
include ApiHelper | ||
|
||
subject { SwaggerCheckerFile.instance_for Rails.root.join('doc', 'swagger.v1.yml') } | ||
|
||
it 'tests all documented routes' do | ||
is_expected.to validate_all_paths | ||
end | ||
end |
This file contains 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,15 @@ | ||
require 'factory_bot' | ||
require 'doorkeeper' | ||
|
||
FactoryBot.define do | ||
|
||
factory :oauth2_application, class: Doorkeeper::Application do | ||
name { Faker::App.name } | ||
redirect_uri 'https://example.com:1234/app' | ||
end | ||
|
||
factory :oauth2_access_token, class: Doorkeeper::AccessToken do | ||
application factory: :oauth2_application | ||
end | ||
|
||
end |
This file contains 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,18 @@ | ||
module ApiHelper | ||
extend ActiveSupport::Concern | ||
|
||
included do | ||
let(:user) { create :user } | ||
let(:access_token) { create(:oauth2_access_token, resource_owner_id: user.id).token } | ||
let(:authorization) { "Bearer #{token}" } | ||
end | ||
|
||
# Add authentication to parameters for {Swagger::RspecHelpers#validate} | ||
# @param params [Hash] Query parameters | ||
# @return Query parameters with authentication header | ||
# @see Swagger::RspecHelpers#validate | ||
def auth(params = {}) | ||
{'_headers' => {'Authorization' => "Bearer #{access_token}" }}.deep_merge(params) | ||
end | ||
|
||
end |