Skip to content

Commit a623639

Browse files
committed
Port of v2.1.0 (Resolves SQL injection vulnerability: PR #51)
1 parent 00b8829 commit a623639

File tree

10 files changed

+204
-58
lines changed

10 files changed

+204
-58
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
"laravel": {
4242
"providers": [
4343
"Grimzy\\LaravelMysqlSpatial\\SpatialServiceProvider"
44-
]
44+
],
45+
"require": "5.5.*"
4546
}
4647
}
4748
}

src/Eloquent/BaseBuilder.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Grimzy\LaravelMysqlSpatial\Eloquent;
4+
5+
use Illuminate\Database\Query\Builder as QueryBuilder;
6+
7+
class BaseBuilder extends QueryBuilder
8+
{
9+
protected function cleanBindings(array $bindings)
10+
{
11+
$bindings = array_map(function ($binding) {
12+
return $binding instanceof SpatialExpression ? $binding->getSpatialValue() : $binding;
13+
}, $bindings);
14+
15+
return parent::cleanBindings($bindings);
16+
}
17+
}

src/Eloquent/Builder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ public function update(array $values)
2020

2121
protected function asWKT(GeometryInterface $geometry)
2222
{
23-
return $this->getQuery()->raw("GeomFromText('".$geometry->toWKT()."')");
23+
return new SpatialExpression($geometry);
2424
}
2525
}

src/Eloquent/SpatialExpression.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Grimzy\LaravelMysqlSpatial\Eloquent;
4+
5+
use Illuminate\Database\Query\Expression;
6+
7+
class SpatialExpression extends Expression
8+
{
9+
public function getValue()
10+
{
11+
return 'ST_GeomFromText(?)';
12+
}
13+
14+
public function getSpatialValue()
15+
{
16+
return $this->value->toWkt();
17+
}
18+
}

src/Eloquent/SpatialTrait.php

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Grimzy\LaravelMysqlSpatial\Eloquent;
44

55
use Grimzy\LaravelMysqlSpatial\Exceptions\SpatialFieldsNotDefinedException;
6+
use Grimzy\LaravelMysqlSpatial\Exceptions\UnknownSpatialRelationFunction;
67
use Grimzy\LaravelMysqlSpatial\Types\Geometry;
78
use Grimzy\LaravelMysqlSpatial\Types\GeometryInterface;
89
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
@@ -37,6 +38,17 @@ trait SpatialTrait
3738

3839
public $geometries = [];
3940

41+
protected $stRelations = [
42+
'within',
43+
'crosses',
44+
'contains',
45+
'disjoint',
46+
'equals',
47+
'intersects',
48+
'overlaps',
49+
'touches',
50+
];
51+
4052
/**
4153
* Create a new Eloquent query builder for the model.
4254
*
@@ -49,12 +61,21 @@ public function newEloquentBuilder($query)
4961
return new Builder($query);
5062
}
5163

64+
protected function newBaseQueryBuilder()
65+
{
66+
$connection = $this->getConnection();
67+
68+
return new BaseBuilder(
69+
$connection, $connection->getQueryGrammar(), $connection->getPostProcessor()
70+
);
71+
}
72+
5273
protected function performInsert(EloquentBuilder $query, array $options = [])
5374
{
5475
foreach ($this->attributes as $key => $value) {
5576
if ($value instanceof GeometryInterface) {
5677
$this->geometries[$key] = $value; //Preserve the geometry objects prior to the insert
57-
$this->attributes[$key] = $this->getConnection()->raw(sprintf("GeomFromText('%s')", $value->toWKT()));
78+
$this->attributes[$key] = new SpatialExpression($value);
5879
}
5980
}
6081

@@ -89,30 +110,58 @@ public function getSpatialFields()
89110
}
90111
}
91112

113+
public function isColumnAllowed($geometryColumn)
114+
{
115+
if (!in_array($geometryColumn, $this->getSpatialFields())) {
116+
throw new SpatialFieldsNotDefinedException();
117+
}
118+
return true;
119+
}
120+
92121
public function scopeDistance($query, $geometryColumn, $geometry, $distance, $exclude_self = false)
93122
{
94-
$query->whereRaw("st_distance(`{$geometryColumn}`, GeomFromText('{$geometry->toWkt()}')) <= {$distance}");
123+
$this->isColumnAllowed($geometryColumn);
124+
125+
$query->whereRaw("st_distance(`$geometryColumn`, ST_GeomFromText(?)) <= ?", [
126+
$geometry->toWkt(),
127+
$distance,
128+
]);
95129

96130
if ($exclude_self) {
97-
$query->whereRaw("st_distance(`{$geometryColumn}`, GeomFromText('{$geometry->toWkt()}')) != 0");
131+
$query->whereRaw("st_distance(`$geometryColumn`, ST_GeomFromText(?)) != 0", [
132+
$geometry->toWkt(),
133+
]);
98134
}
99135

100136
return $query;
101137
}
102138

103139
public function scopeDistanceValue($query, $geometryColumn, $geometry)
104140
{
141+
$this->isColumnAllowed($geometryColumn);
142+
105143
$columns = $query->getQuery()->columns;
106144

107145
if (!$columns) {
108146
$query->select('*');
109147
}
110-
$query->selectRaw("st_distance(`{$geometryColumn}`, GeomFromText('{$geometry->toWkt()}')) as distance");
148+
149+
$query->selectRaw("st_distance(`$geometryColumn`, ST_GeomFromText(?)) as distance", [
150+
$geometry->toWkt(),
151+
]);
111152
}
112153

113154
public function scopeComparison($query, $geometryColumn, $geometry, $relationship)
114155
{
115-
$query->whereRaw("st_{$relationship}(`{$geometryColumn}`, GeomFromText('{$geometry->toWkt()}'))");
156+
$this->isColumnAllowed($geometryColumn);
157+
158+
if (!in_array($relationship, $this->stRelations)) {
159+
throw new UnknownSpatialRelationFunction($relationship);
160+
}
161+
162+
$query->whereRaw("st_{$relationship}(`$geometryColumn`, ST_GeomFromText(?))", [
163+
$geometry->toWkt(),
164+
]);
116165

117166
return $query;
118167
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Grimzy\LaravelMysqlSpatial\Exceptions;
4+
5+
class UnknownSpatialRelationFunction extends \RuntimeException
6+
{
7+
}

src/MysqlConnection.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public function __construct($pdo, $database = '', $tablePrefix = '', array $conf
2424
'multilinestring',
2525
'multipolygon',
2626
'geometrycollection',
27+
'geomcollection',
2728
];
2829
$dbPlatform = $this->getDoctrineSchemaManager()->getDatabasePlatform();
2930
foreach ($geometries as $type) {

tests/Integration/SpatialTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ public function createApplication()
2929
$app['config']->set('database.connections.mysql.database', 'spatial_test');
3030
$app['config']->set('database.connections.mysql.username', 'root');
3131
$app['config']->set('database.connections.mysql.password', '');
32+
$app['config']->set('database.connections.mysql.modes', [
33+
'ONLY_FULL_GROUP_BY',
34+
'STRICT_TRANS_TABLES',
35+
'NO_ZERO_IN_DATE',
36+
'NO_ZERO_DATE',
37+
'ERROR_FOR_DIVISION_BY_ZERO',
38+
'NO_ENGINE_SUBSTITUTION',
39+
]);
3240

3341
return $app;
3442
}

tests/Unit/Eloquent/BuilderTest.php

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use BaseTestCase;
66
use Grimzy\LaravelMysqlSpatial\Eloquent\Builder;
7+
use Grimzy\LaravelMysqlSpatial\Eloquent\SpatialExpression;
78
use Grimzy\LaravelMysqlSpatial\Eloquent\SpatialTrait;
89
use Grimzy\LaravelMysqlSpatial\MysqlConnection;
910
use Grimzy\LaravelMysqlSpatial\Types\LineString;
@@ -36,50 +37,39 @@ protected function setUp()
3637

3738
public function testUpdatePoint()
3839
{
39-
$this->queryBuilder
40-
->shouldReceive('raw')
41-
->with("GeomFromText('POINT(2 1)')")
42-
->once();
43-
40+
$point = new Point(1, 2);
4441
$this->queryBuilder
4542
->shouldReceive('update')
43+
->with(['point' => new SpatialExpression($point)])
4644
->once();
4745

48-
$this->builder->update(['point' => new Point(1, 2)]);
46+
$this->builder->update(['point' => $point]);
4947
}
5048

5149
public function testUpdateLinestring()
5250
{
53-
$this->queryBuilder
54-
->shouldReceive('raw')
55-
->with("GeomFromText('LINESTRING(0 0,1 1,2 2)')")
56-
->once();
51+
$linestring = new LineString([new Point(0, 0), new Point(1, 1), new Point(2, 2)]);
5752

5853
$this->queryBuilder
5954
->shouldReceive('update')
55+
->with(['linestring' => new SpatialExpression($linestring)])
6056
->once();
6157

62-
$linestring = new LineString([new Point(0, 0), new Point(1, 1), new Point(2, 2)]);
63-
6458
$this->builder->update(['linestring' => $linestring]);
6559
}
6660

6761
public function testUpdatePolygon()
6862
{
69-
$this->queryBuilder
70-
->shouldReceive('raw')
71-
->with("GeomFromText('POLYGON((0 0,1 0),(1 0,1 1),(1 1,0 0))')")
72-
->once();
73-
74-
$this->queryBuilder
75-
->shouldReceive('update')
76-
->once();
77-
7863
$linestrings[] = new LineString([new Point(0, 0), new Point(0, 1)]);
7964
$linestrings[] = new LineString([new Point(0, 1), new Point(1, 1)]);
8065
$linestrings[] = new LineString([new Point(1, 1), new Point(0, 0)]);
8166
$polygon = new Polygon($linestrings);
8267

68+
$this->queryBuilder
69+
->shouldReceive('update')
70+
->with(['polygon' => new SpatialExpression($polygon)])
71+
->once();
72+
8373
$this->builder->update(['polygon' => $polygon]);
8474
}
8575
}

0 commit comments

Comments
 (0)