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

Audit module (Change Tracking) #390

Merged
merged 24 commits into from
Dec 22, 2023
Merged

Conversation

litvinovg
Copy link
Contributor

@litvinovg litvinovg commented May 19, 2023

VIVO GitHub issue 1
VIVO GitHub issue 2

VIVO PR

What does this pull request do?

The pull request adds a module that enables tracking of changes being made in the triple store by users and non-person entitites.
Changes are recorded in a triple store, with the users ID (URI), the time, and the changes that have been made.
A user interface (/audit) is also provided that, when logged in, displays the changes.

What's new?

Adds a new AuditModule application module
TDBAuditModule can be configured / enabled via the applicationSetup.n3
RDFService change listener capture any changes made to the content and configuration stores, and record those changes in a dedicated audit store.
Tests included

How should this be tested?

  • Enable AuditModule in applicationSetup.n3 (uncomment :hasAuditModule and :tdbAuditModule section)
  • Login as admin or root user, open /audit page,
  • View changes presented on the page.
  • Try different filters
  • Run tests
  • Try using n3 generators to add/delete data in profiles

Supersedes

#369
#81

Interested parties

@chenejac @brianjlowe

Copy link
Contributor

@chenejac chenejac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@litvinovg thanks for this PR. It looks nice. However, I have posted three comments. There is also an issue with running tests on Windows. It is throwing an exception with permissions for deletion temporary folders. I have posted exception in a comment.

@brianjlowe
Copy link
Member

Commenting at Georgy's request: Georgy and I discussed the possibility of refactoring the audit model to use a supplied RDFService instead of having its own triple store implementations. This would allow us to avoid the Windows temp file problems by having the tests use an in-memory triple store and would also allow for users to configure remote/cloud triple stores for their audit data if desired.

@chenejac chenejac linked an issue Aug 31, 2023 that may be closed by this pull request
@chenejac chenejac requested review from wwelling and removed request for brianjlowe November 16, 2023 16:01
chenejac
chenejac previously approved these changes Dec 5, 2023
Copy link
Contributor

@chenejac chenejac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@litvinovg well done.

Copy link
Contributor

@milospp milospp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and tested the changes on MacOS M1. Everything appears to be functioning as expected. Great job on the functionality implementation!

The only suggestion I have pertains to the UI design. Additionally, it's important to avoid using StringBuilder or string concatenation for building SPARQL queries due to their susceptibility to injection vulnerabilities.


section#audit table.history td{
padding: 2px 5px 2px 5px;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Triplets in a table column cause text overflow on the right side of the screen. This could be solved by enabling text to wrap or by adding horizontal scroll inside the table.

Suggested change
}
}
section#audit table.history pre{
text-wrap: balance;
line-height: normal;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added additional css rules to improve the view.
Didn't use text-wrap: balance as it is not widely supported yet.

queryString.append(" ?dataset a <").append(AuditVocabulary.TYPE_CHANGESET).append("> . ");
queryString.append(" ?dataset <").append(AuditVocabulary.PROP_DATE).append("> ?date . ");
if (!StringUtils.isBlank(userUri)) {
queryString.append(" ?dataset <").append(AuditVocabulary.PROP_USER).append("> <").append(userUri)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part of the code is vulnerable to SparQL Injection.
For example I could send a filter param for userUri like this
http://vivoweb.org/audit/resource/unknown> %7d LIMIT 999999 #

It might be possible to request any data in db through this. But since this is admin only feature I guess it's not a problem at this moment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching that. I think I made it safer now. Please check again.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Injection resolved. Great job!

@milospp
Copy link
Contributor

milospp commented Dec 14, 2023

I noticed that everytime admin makes a change his uri is unknown.

http://vivoweb.org/audit/resource/unknown

image

Does anyone have simmilar problem?

@litvinovg
Copy link
Contributor Author

litvinovg commented Dec 14, 2023

Does anyone have simmilar problem?

Yes. It can happen as some code still uses raw statement modifications.
For example code that logs authentication count in http://vitro.mannlib.cornell.edu/default/vitro-kb-userAccounts.
At the same time most changes related to http://vitro.mannlib.cornell.edu/default/vitro-kb-2 has correct editor uri for me.
Please, try again. I rebased PR branches on top of main.

@milospp
Copy link
Contributor

milospp commented Dec 20, 2023

Does anyone have simmilar problem?

Yes. It can happen as some code still uses raw statement modifications. For example code that logs authentication count in http://vitro.mannlib.cornell.edu/default/vitro-kb-userAccounts. At the same time most changes related to http://vitro.mannlib.cornell.edu/default/vitro-kb-2 has correct editor uri for me. Please, try again. I rebased PR branches on top of main.

I tested again now, and it works for graph: http://vitro.mannlib.cornell.edu/default/vitro-kb-2
Thank you!

* Results object for retrieving entries from the audit store
*/
public class AuditResults {
private long total;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make these final as they are set in the constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

private long limit;

private List<AuditChangeSet> datasets;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you provide a private default constructor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a private default constructor here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good practice. But I have tested with https://www.jdoodle.com/online-java-compiler/ and confirmed that the default constructor is not able to be called outside of reflection or classpath overlay when a constructor is declared.

private List<AuditChangeSet> datasets;

public AuditResults(long total, long offset, long limit, List<AuditChangeSet> datasets) {
this.total = total;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Call default constructor before setting class members.

private static final Log log = LogFactory.getLog(AuditController.class);

// Template for the UI
private static final String TEMPLATE_DEFAULT = "auditHistory.ftl";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these constants or their matching literal in use anywhere else? I may be ideal for package level constant files to avoid building up the static memory across the application.

Copy link
Contributor Author

@litvinovg litvinovg Dec 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, TEMPLATE_DEFAULT used only in this class.

}
// Get the offset parameter (or default if unset)
int offset = getOffset(vreq);
body.put("offset", offset);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use the string literal when the constant is above?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

offset = Integer.parseInt(vreq.getParameter(PARAM_OFFSET));
} catch (Throwable e) {
log.debug(e, e);
offset = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a redundant reassignment of offset variable as if the exception was thrown it would not of reassigned in the try block.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

try {
if (!StringUtils.isBlank(end)) {
Date endDate = sdf.parse(end);
return endDate;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just return the response to the parse method call instead of temporarily allocating memory for garbage cleanup?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

// We expect two arguments
// 1 - an AuditChangeSet
// 2 - a graph URI
if (arguments.size() == 2) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to require a null check.

A Freemarker SimpleMethodModel
https://github.com/litvinovg/Vitro/blob/c0b48511e2b7b4446b90a4096ed19de3bae6d7bd/api/src/main/java/freemarker/ext/dump/BaseDumpDirective.java#L433C25-L433C43 calling with null. May be a red herring.

However, the API does not indicate the argument to be non-null.

https://freemarker.apache.org/docs/api/freemarker/ext/beans/SimpleMethodModel.html#exec-java.util.List-

Copy link
Contributor

@chenejac chenejac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@litvinovg well done.

@chenejac chenejac merged commit 440d4e8 into vivo-project:main Dec 22, 2023
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants