Skip to content
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

Management: Audit create event. #68

Merged
merged 3 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ gemspec

group :development do
gem 'rubocop', '1.63.0', require: false
gem 'rubocop-rails', '2.24.1', require: false
end

group :test do
Expand Down
6 changes: 6 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ GEM
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.2)
parser (>= 3.3.0.4)
rubocop-rails (2.24.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (1.13.0)
rubyzip (2.3.2)
selenium-webdriver (4.19.0)
Expand Down Expand Up @@ -135,6 +140,7 @@ DEPENDENCIES
rotp (= 6.3.0)
rspec (= 3.13.0)
rubocop (= 1.63.0)
rubocop-rails (= 2.24.1)
selenium-webdriver (= 4.19.0)
simplecov (= 0.22.0)
super_diff (= 0.11.0)
Expand Down
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ These sections show how to use the SDK to perform permission and user management
8. [Manage Flows](#manage-flows-and-theme)
9. [Manage JWTs](#manage-jwts)
10. [Embedded links](#embedded-links)
11. [Search Audit](#search-audit)
11. [Audit](#audit)
12. [Manage ReBAC Authz](#manage-rebac-authz)
13. [Manage Project](#manage-project)

Expand Down Expand Up @@ -871,7 +871,7 @@ This token can then be verified using the magic link 'verify' function, either d
token = descope_client.generate_embedded_link(login_id: 'desmond@descope.com', custom_claims: {'key1':'value1'})
```

### Search Audit
### Audit

You can perform an audit search for either specific values or full-text across the fields. Audit search is limited to the last 30 days.
Below are some examples. For a full list of available search criteria options, see the function documentation.
Expand All @@ -898,6 +898,21 @@ audits = descope_client.audit_search(
audits = descope_client.audit_search(actions: ['LoginSucceed'])
```

You can also create audit event with data

```ruby
descope_client.audit_create_event(
action: "pencil.created",
type: "info", # either: info/warn/error
actor_id: "UXXX", # for example a user ID
tenant_id: "tenant-id", # required
data: {
pencil_id: "PXXX",
pencil_name: "Pencil Name"
}
)
```

### Manage ReBAC Authz

Descope supports full relation based access control (ReBAC) using a [Google Zanzibar](https://research.google/pubs/pub48190/) like schema and operations.
Expand Down
39 changes: 31 additions & 8 deletions examples/ruby/management/audit_app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,36 @@
@client = Descope::Client.new({ project_id: @project_id, management_key: @management_key })

begin
@logger.info('Going to search audit')
text = nil
text = ARGV[0] if ARGV.length > 1
from_ts = nil
from_ts = DateTime.iso8601(ARGV[1]) if ARGV.length > 2
res = @client.audit_search(text: text, from_ts: from_ts)
@logger.info("Audit search result: #{res}")
@logger.info('Do you want to to create a new audit event? [y/n] ')
create_audit = gets.chomp
if create_audit == 'y'
@logger.info('Enter the action for the audit event: ')
action = gets.chomp
@logger.info('Enter the type for the audit event: [info/warn/error] ')
type = gets.chomp
@logger.info('Enter the actorId for the audit event: ')
actor_id = gets.chomp
@logger.info('Enter the tenantId for the audit event: ')
tenant_id = gets.chomp
res = @client.audit_create_event(
action: action,
type: type,
actor_id: actor_id,
tenant_id: tenant_id
)
@logger.info("Audit event created successfully: #{res}")
end

@logger.info('Do you want to search the audit trail? [y/n] ')
search_audit = gets.chomp
if search_audit == 'y'
@logger.info('Enter the text to search: ')
text = gets.chomp
@logger.info('Enter the from_ts in ISO8601 format (2024-01-01 15:00:00.000) to search: ')
from_ts = gets.chomp
res = @client.audit_search(text: text, from_ts: from_ts)
@logger.info("Audit search result: #{res}")
end
rescue Descope::AuthException => e
@logger.error("Audit search failed #{e}")
@logger.error("Audit action failed #{e}")
end
25 changes: 25 additions & 0 deletions lib/descope/api/v1/management/audit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,31 @@ def audit_search(
{ 'audits' => res['audits'].map { |audit| convert_audit_record(audit) } }
end

def audit_create_event(action: nil, type: nil, data: nil, user_id: nil, actor_id: nil, tenant_id: nil)
# Create an audit event
unless %w[info warn error].include?(type)
raise Descope::AuthException, 'type must be either info, warn or error'
end

# validate data
raise Descope::AuthException, 'data must be provided (Hash)' unless data.is_a?(Hash)
dorsha marked this conversation as resolved.
Show resolved Hide resolved
raise Descope::AuthException, 'data must not be empty' if data.empty?

# validate tenant_id
raise Descope::AuthException, 'tenant_id must be provided' if tenant_id.nil?

request_params = {
action:,
dorsha marked this conversation as resolved.
Show resolved Hide resolved
tenantId: tenant_id,
type:,
actorId: actor_id,
dorsha marked this conversation as resolved.
Show resolved Hide resolved
data:
}
request_params[:userId] = user_id unless user_id.nil?

post(AUDIT_CREATE_EVENT, request_params)
end

private

def convert_audit_record(audit)
Expand Down
1 change: 1 addition & 0 deletions lib/descope/api/v1/management/common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ module Common

# Audit
AUDIT_SEARCH = '/v1/mgmt/audit/search'
AUDIT_CREATE_EVENT = '/v1/mgmt/audit/event'

# Authz ReBAC
AUTHZ_SCHEMA_SAVE = '/v1/mgmt/authz/schema/save'
Expand Down
36 changes: 36 additions & 0 deletions spec/integration/lib.descope/api/v1/management/audit_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,48 @@
describe Descope::Api::V1::Management::Audit do
before(:all) do
@client = DescopeClient.new(Configuration.config)
@client.logger.info('Deleting all tenants for Ruby SDK...')
@client.search_all_tenants(names: ['Ruby-SDK-test'])['tenants'].each do |tenant|
@client.logger.info("Deleting tenant: #{tenant['name']}")
@client.delete_tenant(tenant['id'])
end
@client.logger.info('Cleanup completed. Starting tests...')
end

after(:all) do
all_users = @client.search_all_users
all_users['users'].each do |user|
if user['middleName'] == 'Ruby SDK User'
puts "Deleting ruby spec test user #{user['loginIds'][0]}"
@client.delete_user(user['loginIds'][0])
end
end
end

it 'should search the audit trail for user operations' do
res = @client.audit_search(actions: ['LoginSucceed'])
expect(res).to be_a(Hash)
expect(res['audits']).to be_a(Array)
end

it 'should create a new audit event' do
# Create tenants
@client.logger.info('creating Ruby-SDK-test tenant')
tenant_id = @client.create_tenant(name: 'Ruby-SDK-test')['id']

# Create a user (actor)
user = build(:user)
created_user = @client.create_user(**user)['user']

expect do
res = @client.audit_create_event(
action: 'pencil.created',
type: 'info',
tenant_id:,
actor_id: created_user['loginIds'][0],
data: { 'key' => 'value' }
)
expect(res).to eq({})
end.not_to raise_error
end
end
2 changes: 1 addition & 1 deletion spec/lib.descope/api/v1/auth_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@

expect do
exp_in_seconds = 20
puts "Sleeping for #{exp_in_seconds} seconds to test token expiration. Please wait..."
puts "\nAuthSpec.validate_token::Sleeping for #{exp_in_seconds} seconds to test token expiration. Please wait...\n"
sleep(exp_in_seconds)
@instance.send(:validate_token, token)
end.to raise_error(
Expand Down
80 changes: 80 additions & 0 deletions spec/lib.descope/api/v1/management/audit_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,84 @@
expect(res['audits'][0]['projectId']).to eq('abc')
end
end

context '.create_event' do
it 'should respond to .audit_create_event' do
expect(@instance).to respond_to :audit_create_event
end
it 'should raise an error if type is not info, warn or error' do
expect do
@instance.audit_create_event(
action: 'get',
type: 'debug',
data: { key: 'value' },
user_id: 'user_id',
actor_id: 'actor_id',
tenant_id: 'tenant_id'
)
end.to raise_error(Descope::AuthException, 'type must be either info, warn or error')
end

it 'should raise an error if data is not a hash' do
expect do
@instance.audit_create_event(
action: 'get',
type: 'info',
data: 'data',
user_id: 'user_id',
actor_id: 'actor_id',
tenant_id: 'tenant_id'
)
end.to raise_error(Descope::AuthException, 'data must be provided (Hash)')
end

it 'should raise an error if data is an empty hash' do
expect do
@instance.audit_create_event(
action: 'get',
type: 'info',
data: {},
user_id: 'user_id',
actor_id: 'actor_id',
tenant_id: 'tenant_id'
)
end.to raise_error(Descope::AuthException, 'data must not be empty')
end

it 'should raise an error if tenant_id is not provided' do
expect do
@instance.audit_create_event(
action: 'get',
type: 'info',
data: { key: 'value' },
user_id: 'user_id',
actor_id: 'actor_id'
)
end.to raise_error(Descope::AuthException, 'tenant_id must be provided')
end

it 'is expected to create an audit event' do
expect(@instance).to receive(:post).with(
'/v1/mgmt/audit/event',
{
action: 'get',
type: 'info',
actorId: 'actor_id',
data: { key: 'value' },
tenantId: 'tenant_id',
userId: 'user_id'
}
)
expect do
@instance.audit_create_event(
action: 'get',
type: 'info',
data: { key: 'value' },
user_id: 'user_id',
actor_id: 'actor_id',
tenant_id: 'tenant_id'
)
end.not_to raise_error
end
end
end