diff --git a/app/views/admin/audits/_setting_row.html.erb b/app/views/admin/audits/_setting_row.html.erb
new file mode 100644
index 0000000..f3efbae
--- /dev/null
+++ b/app/views/admin/audits/_setting_row.html.erb
@@ -0,0 +1,22 @@
+
+ <%= link_to fa_icon('eye', title: 'Show'), admin_audit_path(model.id) %>
+ |
+<%= model.user.try(:username) %> |
+
+<%- model_attributes.each do |attr_name| %>
+
+ <%- data = model.send(attr_name) %>
+ <%- if data.is_a? ActiveRecord::Associations::CollectionProxy %>
+ <%- data = data.join ", " %>
+ <%- end %>
+ <%- if attr_name == 'audited_changes' %>
+ <%- change = data["value"].is_a?(Array) ? data["value"].last : data["value"]%>
+ <%- if model.auditable.respond_to?(:maybe_hide_attribute) %>
+ <%- data = { 'setting' => model.auditable.var, 'value' => model.auditable.maybe_hide_attribute(data) } %>
+ <%- end %>
+ <%= redact(data).to_yaml %>
+ <%- else %>
+ <%= data %>
+ <%- end %>
+ |
+<%- end %>
diff --git a/spec/controllers/admin/audits_controller_spec.rb b/spec/controllers/admin/audits_controller_spec.rb
new file mode 100644
index 0000000..f0bd318
--- /dev/null
+++ b/spec/controllers/admin/audits_controller_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::AuditsController, type: :controller do
+ include ActiveJob::TestHelper
+ render_views
+
+ let(:user) do
+ user = User.create!(username: 'user', email: 'user@localhost', password: 'test123456')
+ user.confirm!
+ user
+ end
+ let(:group) { Group.create!(name: 'administrators', admin: true) }
+ let(:admin) do
+ user = User.create!(username: 'admin', email: 'admin@localhost', password: 'test123456')
+ user.groups << group
+ user.confirm!
+ user
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'does not show encrypted passwords' do
+ user.password = 'new password 123'
+ user.save
+ get :index
+ expect(response.status).to eq(200)
+ expect(response.body).to include('encrypted_password: "<REDACTED>"')
+ end
+
+ it 'does not show password reset tokens' do
+ # The user creation above will trigger the password reset token
+ get :index
+ expect(response.status).to eq(200)
+ expect(response.body).to include('reset_password_token: "<REDACTED>"')
+ end
+
+ it 'does not show oidc_signing_key' do
+ secret1 = 'this is a secret!'
+ Setting.oidc_signing_key = secret1
+
+ get :index
+ expect(response.status).to eq(200)
+ sha = OpenSSL::Digest::SHA1.hexdigest(secret1)
+ expect(response.body).to include("setting: oidc_signing_key\nvalue: 'Sha1 of secret: #{sha}'")
+
+ secret2 = 'this is also a secret!'
+ Setting.oidc_signing_key = secret2
+
+ get :index
+ expect(response.status).to eq(200)
+ sha = OpenSSL::Digest::SHA1.hexdigest(secret2)
+ expect(response.body).to include("setting: oidc_signing_key\nvalue: 'Sha1 of secret: #{sha}'")
+ end
+
+ it 'does not show SAML key' do
+ secret1 = 'this is a secret!'
+ Setting.saml_key = secret1
+
+ get :index
+ expect(response.status).to eq(200)
+ sha = OpenSSL::Digest::SHA1.hexdigest(secret1)
+ expect(response.body).to include("setting: saml_key\nvalue: 'Sha1 of secret: #{sha}'")
+
+ secret2 = 'this is also a secret!'
+ Setting.saml_key = secret2
+
+ get :index
+ expect(response.status).to eq(200)
+ sha = OpenSSL::Digest::SHA1.hexdigest(secret2)
+ expect(response.body).to include("setting: saml_key\nvalue: 'Sha1 of secret: #{sha}'")
+ end
+
+ it 'does show SAML certificate' do
+ Setting.saml_certificate = 'this is not a secret!'
+
+ get :index
+ expect(response.status).to eq(200)
+ # binding.pry
+ expect(response.body).to include("setting: saml_certificate\nvalue: this is not a secret!")
+ end
+end