-
Notifications
You must be signed in to change notification settings - Fork 824
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
API Create orderBy() method to handle raw SQL #10600
API Create orderBy() method to handle raw SQL #10600
Conversation
ed5a005
to
9d86ee1
Compare
6f94e8f
to
f0c4fd0
Compare
f0c4fd0
to
fbcacf9
Compare
fbcacf9
to
19ae264
Compare
083e7bd
to
b1ea6e5
Compare
b1ea6e5
to
1dea1d6
Compare
1dea1d6
to
7f8f18b
Compare
7f8f18b
to
db42af0
Compare
81da517
to
ddb4075
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should #10582 be revisited as a part of this work?
In my mind we should either set GridFieldSortableHeader
to use orderBy()
(because a developer could want to use some aggregate or join sort column, as discussed in the original PR for that fix), or we could remove the logic that validates SortColumn
exists and still use sort()
.
ddb4075
to
abc1f85
Compare
Out of scope IMO, we can look at this with a new card if need be. It can happen post 5.0.0 |
abc1f85
to
855e65a
Compare
@emteknetnz The CI failure is for an invalid column name which doesn't seem to be related to the other PRs - can you please take a look? Was the installer CI run after the most recent changes? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a couple of minor changes - I should have caught two of these last review so sorry about that.
// Other parts of silverstripe will break if there are commas in column names | ||
foreach (explode(',', $sort) as $colDir) { | ||
// Using regex instead of explode(' ') in case column name includes spaces | ||
if (preg_match('/^(.+) ([^"]+)$/i', trim($colDir), $matches)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will still match against something like "Column Name"
- it's funtionally equivalent to using explode
for any string with only one space in it.
If the column name has a space in it, and no direction was supplied, it should not match here so that the default direction can be added in the else
block.
I think we should explicitly be checking for asc
and desc
here instead.
if (preg_match('/^(.+) ([^"]+)$/i', trim($colDir), $matches)) { | |
if (preg_match('/^(.+) (asc|desc)$/i', trim($colDir), $matches)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It won't match "Column Name"
- it's pinned with $
at the end of the regex
It would match Column Name
- however this will fail on validateSortDirection()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies, I should have omitted the quote marks which were intended to mean "this is the whole string"
My point is that it shouldn't fail on validateSortDirection()
, because Column
on its own won't fail that.
So currently passing Column
is fine but Column Name
is not fine, even if they're both valid columns.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think think we need to over think for edge cases like this. An exception will still be thrown and it should be pretty obvious at that point they need to wrap their column in quotes since they'll get a 'Invalid sort direction Name' exception message
Even if we change this to (asc|desc)
, then it'll still fail on the edge case of someone calling naming column MySortDir ASC
. There's a workaround available which is to wrap the weird column in double quotes e.g. ->sort('"Column Name" ASC')
I think it's reasonable to put the onus on end users to do this here - they'll be well used to do extra steps to get things to actually work. Other parts of Silverstripe will already be broken e.g. calling $myDataObject->{'Column Name'} to get values (I'm assuming that actually works)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree but I'll merge it 'cause it's not worth arguing about. Someone can raise an issue when it causes problems for them.
855e65a
to
ae4d7fa
Compare
Issue #10449
Note: unit-tests won't pass until silverstripe/silverstripe-versioned#385 is merged in - but this was run though an installer CI run: https://github.com/emteknetnz/silverstripe-installer/actions/runs/3617074332
I've only implemented this at the
DataList
level. If we wanted to implement this deeper then forsort()
we'd probably want to do this at the SQLSelect::addOrderBy() which is ultimately the only thing called by DataQuery::sort() - however it feels as though there's going to be too much raw SQL flowing through there to make this practical given that Silverstripe loves to useCASE WHEN
statements when constructing queries.SQLSelect::addOrderBy()
currently has a lot of logic to handle raw sql. At some level we need to fully allow raw SQL because that's the language the database uses.My assumption is that
DataList::sort()
is the entry point for SQL injection attacks because it's the abstraction used for interacting with the ORM i.e.MyDataObject::get()->filter($filter)->sort($sort)->column('Title')
usesDataList::sort()
- while DataQuery is one abstraction level below and SQLSelect is another abstraction below again.The most recently disclosed SQL injection vulnerability was via DataList - 4308a93
The following public methods on DataList do accept raw sql
sort()
where()
andwhereAny()
var_dump(SiteTree::get()->where('MOD(ID,3) = 1')->column('ID'));
public static DataObject::get($callerClass, $filter, ...)
andget_one()
- which will use call DataList::where() e.g. DataObject::get(MyDataObject::class, $rawSql). We should simply change the calls here from->where()
to->filter()
in CMS5 to tighten this up.where() / whereAny()
are essentially the "raw" versions of 'filter() / filterAny()`, so this is similar to sort() / orderBy()PartialMatchFilter::applyMany()
is the only call towhereAny()
that needs further investigationwhere($filter)
leftJoin() / - innerJoin()
var_dump(SiteTree::get()->leftJoin('Member', 'SiteTree.ID = Member.ID AND SiteTree.ID < 5 AND MOD(SiteTree.ID,3) = 1')->column('Member.Email'));
innerJoin()
andleftJoin()
in core and there didn't look to be anything unsafe e.g. could be altered via a url parameter.The following public methods on DataList do not accept raw sql:
filter() / filterAny() / find() / exclude()
var_dump(SiteTree::get()->exclude(['MOD(ID,3)' => 1])->column('ID'));
map()
var_dump(SiteTree::get()->map('ID', 'MOD(ID,3)')->toArray());
max() / min() / avg() / sum()
var_dump(SiteTree::get()->max('MOD(ID,3)'));
column() / columnUnique()
var_dump(SiteTree::get()->column('MOD(ID,3)'));