diff --git a/go.mod b/go.mod index 51b7381..f916b3a 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,8 @@ require ( github.com/cs3org/go-cs3apis v0.0.0-20240802083356-d617314e1795 github.com/cs3org/reva v1.27.0 github.com/disintegration/imaging v1.6.2 - github.com/go-chi/chi/v5 v5.0.12 - github.com/go-sql-driver/mysql v1.8.0 + github.com/go-chi/chi/v5 v5.1.0 + github.com/go-sql-driver/mysql v1.8.1 github.com/gomodule/redigo v1.9.2 github.com/juliangruber/go-intersect v1.1.0 github.com/mitchellh/mapstructure v1.5.0 @@ -17,6 +17,9 @@ require ( github.com/rs/zerolog v1.32.0 google.golang.org/genproto v0.0.0-20240314234333-6e1732d8331c google.golang.org/grpc v1.65.0 + gorm.io/datatypes v1.2.4 + gorm.io/driver/mysql v1.5.7 + gorm.io/gorm v1.25.12 ) require ( @@ -41,6 +44,8 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.16 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -53,9 +58,9 @@ require ( golang.org/x/crypto v0.23.0 // indirect golang.org/x/image v0.13.0 // indirect golang.org/x/net v0.25.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect google.golang.org/protobuf v1.34.1 // indirect ) diff --git a/go.sum b/go.sum index 15195f5..ab0bd75 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ github.com/glpatcern/go-mime v0.0.0-20221026162842-2a8d71ad17a9 h1:3um08ooi0/lyR github.com/glpatcern/go-mime v0.0.0-20221026162842-2a8d71ad17a9/go.mod h1:EJaddanP+JfU3UkVvn0rYYF3b/gD7eZRejbTHqiQExA= github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= -github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -51,13 +51,18 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4= -github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s= @@ -71,6 +76,18 @@ github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/juliangruber/go-intersect v1.1.0 h1:sc+y5dCjMMx0pAdYk/N6KBm00tD/f3tq+Iox7dYDUrY= github.com/juliangruber/go-intersect v1.1.0/go.mod h1:WMau+1kAmnlQnKiikekNJbtGtfmILU/mMU6H7AgKbWQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -89,6 +106,10 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE= +github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= github.com/mileusna/useragent v1.3.4 h1:MiuRRuvGjEie1+yZHO88UBYg8YBC/ddF6T7F56i3PCk= github.com/mileusna/useragent v1.3.4/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -156,8 +177,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -170,8 +191,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -183,8 +204,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -193,8 +214,8 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20210112230658-8b4aab62c064/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -212,5 +233,18 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/datatypes v1.2.4 h1:uZmGAcK/QZ0uyfCuVg0VQY1ZmV9h1fuG0tMwKByO1z4= +gorm.io/datatypes v1.2.4/go.mod h1:f4BsLcFAX67szSv8svwLRjklArSHAvHLeE3pXAS5DZI= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U= +gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A= +gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= +gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= +gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= +gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/share/model.go b/share/model.go new file mode 100644 index 0000000..040fa3f --- /dev/null +++ b/share/model.go @@ -0,0 +1,209 @@ +package share + +import ( + "strconv" + "time" + + grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + resourcespb "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + conversions "github.com/cs3org/reva/pkg/cbox/utils" + + "gorm.io/datatypes" + "gorm.io/gorm" +) + +// +---------------------------------+-----------------+------+-----+---------+----------------+ +// | Field | Type | Null | Key | Default | Extra | +// +---------------------------------+-----------------+------+-----+---------+----------------+ +// | id | int | NO | PRI | NULL | auto_increment | +// | share_type | smallint | NO | | 0 | | +// | share_with | varchar(255) | YES | | NULL | | +// | uid_owner | varchar(64) | NO | | | | +// | uid_initiator | varchar(64) | YES | | NULL | | +// | parent | int | YES | | NULL | | +// | item_type | varchar(64) | NO | MUL | | | +// | item_source | varchar(255) | YES | | NULL | | +// | item_target | varchar(255) | YES | | NULL | | +// | file_source | bigint unsigned | YES | MUL | NULL | | +// | file_target | varchar(512) | YES | | NULL | | +// | permissions | smallint | NO | | 0 | | +// | stime | bigint | NO | | 0 | | +// | accepted | smallint | NO | | 0 | | +// | expiration | datetime | YES | | NULL | | +// | token | varchar(32) | YES | MUL | NULL | | +// | mail_send | smallint | NO | | 0 | | +// | fileid_prefix | varchar(255) | YES | | NULL | | +// | orphan | tinyint | YES | | NULL | | +// | share_name | varchar(255) | YES | | NULL | | +// | quicklink | tinyint(1) | NO | | 0 | | +// | description | varchar(1024) | NO | | | | +// | internal | tinyint(1) | NO | MUL | 0 | | +// | notify_uploads | tinyint(1) | NO | | 0 | | +// | notify_uploads_extra_recipients | varchar(2048) | YES | | NULL | | +// +---------------------------------+-----------------+------+-----+---------+----------------+ + +type protoShare struct { + // Including gorm.Model will embed a number of gorm-default fields + // such as creation_time, id etc + gorm.Model + UIDOwner string + UIDInitiator string + ItemType string // file | folder + InitialPath string + Inode string + FileSource int64 + FileTarget string + Permissions uint8 + Instance string + Orphan bool + Description string + Expiration datatypes.Null[datatypes.Date] +} + +type Share struct { + protoShare + ShareWith string + SharedWithIsGroup bool +} + +type PublicLink struct { + protoShare + Token string + // Enforce uniqueness in db re: Itemsource + Quicklink bool + NotifyUploads bool + NotifyUploadsExtraRecipients string + Password string + // Users can give a name to a share + ShareName string +} + +// Unique index on combo of (shareid, user) +type ShareState struct { + gorm.Model + ShareId uint //foreign key to share + // Can not be uid because of lw accs + User string + Synced bool + Hidden bool +} + +func (s *Share) AsCS3Share(granteeType userpb.UserType) *collaboration.Share { + ts := &typespb.Timestamp{ + Seconds: uint64(s.CreatedAt.Unix()), + } + return &collaboration.Share{ + Id: &collaboration.ShareId{ + OpaqueId: strconv.Itoa(int(s.ID)), + }, + //ResourceId: &provider.Reference{StorageId: s.Prefix, NodeId: s.ItemSource}, + ResourceId: &provider.ResourceId{ + StorageId: s.Instance, + OpaqueId: s.Inode, + }, + Permissions: &collaboration.SharePermissions{Permissions: conversions.IntTosharePerm(int(s.Permissions), s.ItemType)}, + Grantee: extractGrantee(s.SharedWithIsGroup, s.ShareWith, granteeType), + Owner: conversions.MakeUserID(s.UIDOwner), + Creator: conversions.MakeUserID(s.UIDInitiator), + Ctime: ts, + Mtime: ts, + } +} + +func (s *Share) AsCS3ReceivedShare(state *ShareState, granteeType userpb.UserType) *collaboration.ReceivedShare { + return &collaboration.ReceivedShare{ + Share: s.AsCS3Share(granteeType), + State: resourcespb.ShareState_SHARE_STATE_ACCEPTED, + Hidden: state.Hidden, + } +} + +func (p *PublicLink) AsCS3PublicShare() *link.PublicShare { + ts := &typespb.Timestamp{ + Seconds: uint64(p.CreatedAt.Unix()), + } + pwd := false + if p.Password != "" { + pwd = true + } + var expires *typespb.Timestamp + if p.Expiration.Valid { + exp, err := p.Expiration.V.Value() + expiration := exp.(time.Time) + if err == nil { + expires = &typespb.Timestamp{ + Seconds: uint64(expiration.Unix()), + } + } + + } + return &link.PublicShare{ + Id: &link.PublicShareId{ + OpaqueId: strconv.Itoa(int(p.ID)), + }, + ResourceId: &provider.ResourceId{ + StorageId: p.Instance, + OpaqueId: p.Inode, + }, + Permissions: &link.PublicSharePermissions{Permissions: conversions.IntTosharePerm(int(p.Permissions), p.ItemType)}, + Owner: conversions.MakeUserID(p.UIDOwner), + Creator: conversions.MakeUserID(p.UIDInitiator), + Token: p.Token, + DisplayName: p.ShareName, + PasswordProtected: pwd, + Expiration: expires, + Ctime: ts, + Mtime: ts, + Quicklink: p.Quicklink, + Description: p.Description, + NotifyUploads: p.NotifyUploads, + NotifyUploadsExtraRecipients: p.NotifyUploadsExtraRecipients, + } +} + +// The package 'conversions' is currently internal in Reva +// It should become public so we can use it here +// Since it generates CS3ResourcePermissions I'm not sure why it would be private + +// IntTosharePerm retrieves read/write permissions from an integer. +func intTosharePerm(p int, itemType string) *provider.ResourcePermissions { + switch p { + case 1: + return conversions.NewViewerRole().CS3ResourcePermissions() + case 15: + if itemType == "folder" { + return conversions.NewEditorRole().CS3ResourcePermissions() + } + return conversions.NewFileEditorRole().CS3ResourcePermissions() + case 4: + return conversions.NewUploaderRole().CS3ResourcePermissions() + default: + // TODO we may have other options, for now this is a denial + return &provider.ResourcePermissions{} + } +} + +// ExtractGrantee retrieves the CS3API Grantee from a grantee type and username/groupname. +// The grantee userType is relevant only for users. +func extractGrantee(sharedWithIsGroup bool, g string, gtype userpb.UserType) *provider.Grantee { + var grantee provider.Grantee + if sharedWithIsGroup { + grantee.Type = provider.GranteeType_GRANTEE_TYPE_USER + grantee.Id = &provider.Grantee_UserId{UserId: &userpb.UserId{ + OpaqueId: g, + Type: gtype, + }} + } else { + grantee.Type = provider.GranteeType_GRANTEE_TYPE_GROUP + grantee.Id = &provider.Grantee_GroupId{GroupId: &grouppb.GroupId{ + OpaqueId: g, + }} + } + + return &grantee +} diff --git a/share/sql/sql.go b/share/sql/sql.go index 285b84a..a1c12a8 100644 --- a/share/sql/sql.go +++ b/share/sql/sql.go @@ -1,4 +1,4 @@ -// Copyright 2018-2023 CERN +// Copyright 2018-2024 CERN // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,29 +20,29 @@ package sql import ( "context" - "database/sql" "fmt" - "path" "strconv" "strings" - "time" + model "github.com/cernbox/reva-plugins/share" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva" "github.com/cs3org/reva/pkg/appctx" conversions "github.com/cs3org/reva/pkg/cbox/utils" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/pkg/share" + revashare "github.com/cs3org/reva/pkg/share" "github.com/cs3org/reva/pkg/sharedconf" "github.com/cs3org/reva/pkg/utils" "github.com/cs3org/reva/pkg/utils/cfg" + "gorm.io/driver/mysql" + "gorm.io/gorm" + // Provides mysql drivers. _ "github.com/go-sql-driver/mysql" "github.com/pkg/errors" @@ -50,9 +50,6 @@ import ( ) const ( - shareTypeUser = 0 - shareTypeGroup = 1 - projectInstancesPrefix = "newproject" projectSpaceGroupsPrefix = "cernbox-project-" projectSpaceAdminGroupsSuffix = "-admins" @@ -80,8 +77,9 @@ type config struct { } type mgr struct { - c *config - db *sql.DB + c *config + //db *sql.DB + db *gorm.DB } func (c *config) ApplyDefaults() { @@ -89,17 +87,22 @@ func (c *config) ApplyDefaults() { } // New returns a new share manager. -func New(ctx context.Context, m map[string]interface{}) (share.Manager, error) { +func New(ctx context.Context, m map[string]interface{}) (revashare.Manager, error) { var c config if err := cfg.Decode(m, &c); err != nil { return nil, err } - db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", c.DBUsername, c.DBPassword, c.DBHost, c.DBPort, c.DBName)) + dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", c.DBUsername, c.DBPassword, c.DBHost, c.DBPort, c.DBName) + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + // db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", c.DBUsername, c.DBPassword, c.DBHost, c.DBPort, c.DBName)) if err != nil { return nil, err } + // Migrate schemas + db.AutoMigrate(&model.Share{}, &model.PublicLink{}, &model.ShareState{}) + return &mgr{ c: &c, db: db, @@ -129,119 +132,93 @@ func (m *mgr) Share(ctx context.Context, md *provider.ResourceInfo, g *collabora return nil, errtypes.AlreadyExists(key.String()) } - now := time.Now().Unix() - ts := &typespb.Timestamp{ - Seconds: uint64(now), + share := &model.Share{ + ShareWith: conversions.FormatUserID(g.Grantee.GetUserId()), + SharedWithIsGroup: g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP, } + share.UIDOwner = conversions.FormatUserID(md.Owner) + share.UIDInitiator = conversions.FormatUserID(user.Id) + share.InitialPath = md.Path + share.ItemType = conversions.ResourceTypeToItem(md.Type) + share.Inode = md.Id.OpaqueId + share.Instance = md.Id.StorageId + share.Permissions = uint8(conversions.SharePermToInt(g.Permissions.Permissions)) + share.Orphan = false - shareType, shareWith := conversions.FormatGrantee(g.Grantee) - itemType := conversions.ResourceTypeToItem(md.Type) - targetPath := path.Join("/", path.Base(md.Path)) - permissions := conversions.SharePermToInt(g.Permissions.Permissions) - prefix := md.Id.StorageId - itemSource := md.Id.OpaqueId - fileSource, err := strconv.ParseUint(itemSource, 10, 64) - if err != nil { - // it can be the case that the item source may be a character string - // we leave fileSource blank in that case - fileSource = 0 - } - - stmtString := "insert into oc_share set share_type=?,uid_owner=?,uid_initiator=?,item_type=?,fileid_prefix=?,item_source=?,file_source=?,permissions=?,stime=?,share_with=?,file_target=?" - stmtValues := []interface{}{shareType, conversions.FormatUserID(md.Owner), conversions.FormatUserID(user.Id), itemType, prefix, itemSource, fileSource, permissions, now, shareWith, targetPath} - - stmt, err := m.db.Prepare(stmtString) - if err != nil { - return nil, err - } - result, err := stmt.Exec(stmtValues...) - if err != nil { - return nil, err - } - lastID, err := result.LastInsertId() - if err != nil { - return nil, err + res := m.db.Save(&share) + if res.Error != nil { + return nil, res.Error } - return &collaboration.Share{ - Id: &collaboration.ShareId{ - OpaqueId: strconv.FormatInt(lastID, 10), - }, - ResourceId: md.Id, - Permissions: g.Permissions, - Grantee: g.Grantee, - Owner: md.Owner, - Creator: user.Id, - Ctime: ts, - Mtime: ts, - }, nil + granteeType, _ := m.getUserType(ctx, share.ShareWith) + return share.AsCS3Share(granteeType), nil } -func (m *mgr) getByID(ctx context.Context, id *collaboration.ShareId, checkOwner bool) (*collaboration.Share, error) { - uid := conversions.FormatUserID(appctx.ContextMustGetUser(ctx).Id) - s := conversions.DBShare{ID: id.OpaqueId} - query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, lower(coalesce(share_with, '')) as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, coalesce(item_type, '') as item_type, stime, permissions, share_type FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND id=?" - params := []interface{}{id.OpaqueId} - if checkOwner { - query += " AND (uid_owner=? or uid_initiator=?)" - params = append(params, uid, uid) - } - if err := m.db.QueryRow(query, params...).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ItemType, &s.STime, &s.Permissions, &s.ShareType); err != nil { - if err == sql.ErrNoRows { - return nil, errtypes.NotFound(id.OpaqueId) - } - return nil, err +// Get Share by ID. Does not return orphans. +func (m *mgr) getByID(ctx context.Context, id *collaboration.ShareId) (*model.Share, error) { + var share model.Share + res := m.db.First(&share, id.OpaqueId) + + if res.RowsAffected == 0 || share.Orphan { + return nil, errtypes.NotFound(id.OpaqueId) } - // the grantee type is resolved afterwards when needed - return conversions.ConvertToCS3Share(s, userpb.UserType_USER_TYPE_INVALID), nil + + return &share, nil } -func (m *mgr) getByKey(ctx context.Context, key *collaboration.ShareKey, checkOwner bool) (*collaboration.Share, error) { +// Get Share by Key. Does not return orphans. +func (m *mgr) getByKey(ctx context.Context, key *collaboration.ShareKey, checkOwner bool) (*model.Share, error) { owner := conversions.FormatUserID(key.Owner) uid := conversions.FormatUserID(appctx.ContextMustGetUser(ctx).Id) - s := conversions.DBShare{} + var share model.Share shareType, shareWith := conversions.FormatGrantee(key.Grantee) - query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, lower(coalesce(share_with, '')) as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, coalesce(item_type, '') as item_type, id, stime, permissions, share_type FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND uid_owner=? AND fileid_prefix=? AND item_source=? AND share_type=? AND lower(share_with)=lower(?)" - params := []interface{}{owner, key.ResourceId.StorageId, key.ResourceId.OpaqueId, shareType, shareWith} + + query := m.db.Model(&share). + Where("orphan = ?", false). + Where("uid_owner = ?", owner). + Where("fileid_prefix = ?", key.ResourceId.StorageId). + Where("item_source = ?", key.ResourceId.OpaqueId). + Where("share_type = ?", shareType). + Where("share_with = ?", strings.ToLower(shareWith)) + if checkOwner { - query += " AND (uid_owner=? or uid_initiator=?)" - params = append(params, uid, uid) + query = query. + Where("uid_owner = ? or uid_initiator = ?", uid, uid) } - if err := m.db.QueryRow(query, params...).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ItemType, &s.ID, &s.STime, &s.Permissions, &s.ShareType); err != nil { - if err == sql.ErrNoRows { - return nil, errtypes.NotFound(key.String()) - } - return nil, err + + res := query.First(&share) + + if res.RowsAffected == 0 { + return nil, errtypes.NotFound(key.String()) } - // the grantee type is resolved afterwards when needed - return conversions.ConvertToCS3Share(s, userpb.UserType_USER_TYPE_INVALID), nil + + return &share, nil } -func (m *mgr) GetShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.Share, error) { - var s *collaboration.Share +func (m *mgr) getShare(ctx context.Context, ref *collaboration.ShareReference) (*model.Share, error) { + var s *model.Share var err error switch { case ref.GetId() != nil: - s, err = m.getByID(ctx, ref.GetId(), false) + s, err = m.getByID(ctx, ref.GetId()) if err != nil { return nil, err } case ref.GetKey() != nil: + //s, err = m.getByKey(ctx, ref.GetKey(), userpb.UserType_USER_TYPE_INVALID, false) s, err = m.getByKey(ctx, ref.GetKey(), false) if err != nil { return nil, err } default: - err = errtypes.NotFound(ref.String()) - } - - // resolve grantee's user type if applicable - if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER { - s.Grantee.GetUserId().Type, _ = m.getUserType(ctx, s.Grantee.GetUserId().OpaqueId) + return nil, errtypes.NotFound(ref.String()) } - path, err := m.getPath(ctx, s.ResourceId) + path, err := m.getPath(ctx, &provider.ResourceId{ + StorageId: s.Instance, + OpaqueId: s.Inode, + }) if err != nil { return nil, err } @@ -251,94 +228,46 @@ func (m *mgr) GetShare(ctx context.Context, ref *collaboration.ShareReference) ( return s, nil } - if s.Owner.OpaqueId == user.Id.OpaqueId && s.Creator.OpaqueId == user.Id.OpaqueId { + if s.UIDOwner == user.Id.OpaqueId && s.UIDInitiator == user.Id.OpaqueId { return s, nil } - return s, errtypes.NotFound("share not found") + return s, nil } -func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) error { - var query string - params := []interface{}{} - switch { - case ref.GetId() != nil: - query = "delete from oc_share where id=?" - params = append(params, ref.GetId().OpaqueId) - case ref.GetKey() != nil: - key := ref.GetKey() - shareType, shareWith := conversions.FormatGrantee(key.Grantee) - owner := conversions.FormatUserID(key.Owner) - query = "delete from oc_share where uid_owner=? AND fileid_prefix=? AND item_source=? AND share_type=? AND lower(share_with)=lower(?)" - params = append(params, owner, key.ResourceId.StorageId, key.ResourceId.OpaqueId, shareType, shareWith) - default: - return errtypes.NotFound(ref.String()) - } - - ctx, err := m.addPathIntoCtx(ctx, ref) +func (m *mgr) GetShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.Share, error) { + share, err := m.getShare(ctx, ref) if err != nil { - return err + return nil, err } - query, params, err = m.appendUidOwnerFilters(ctx, query, params) - if err != nil { - return err - } + granteeType, _ := m.getUserType(ctx, share.ShareWith) + cs3share := share.AsCS3Share(granteeType) - stmt, err := m.db.Prepare(query) - if err != nil { - return err - } - res, err := stmt.Exec(params...) - if err != nil { - return err - } + return cs3share, nil +} - rowCnt, err := res.RowsAffected() +func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) error { + share, err := m.GetShare(ctx, ref) if err != nil { return err } - if rowCnt == 0 { - return errtypes.NotFound(ref.String()) - } - return nil + res := m.db.Delete(&share) + return res.Error } func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions) (*collaboration.Share, error) { permissions := conversions.SharePermToInt(p.Permissions) - - var query string - params := []interface{}{} - switch { - case ref.GetId() != nil: - query = "update oc_share set permissions=?,stime=? where id=?" - params = append(params, permissions, time.Now().Unix(), ref.GetId().OpaqueId) - case ref.GetKey() != nil: - key := ref.GetKey() - shareType, shareWith := conversions.FormatGrantee(key.Grantee) - owner := conversions.FormatUserID(key.Owner) - query = "update oc_share set permissions=?,stime=? where (uid_owner=? or uid_initiator=?) AND fileid_prefix=? AND item_source=? AND share_type=? AND lower(share_with)=lower(?)" - params = append(params, permissions, time.Now().Unix(), owner, owner, key.ResourceId.StorageId, key.ResourceId.OpaqueId, shareType, shareWith) - default: - return nil, errtypes.NotFound(ref.String()) - } - - ctx, err := m.addPathIntoCtx(ctx, ref) + share, err := m.getShare(ctx, ref) if err != nil { return nil, err } - query, params, err = m.appendUidOwnerFilters(ctx, query, params) - if err != nil { - return nil, err - } + share.Permissions = uint8(permissions) - stmt, err := m.db.Prepare(query) - if err != nil { - return nil, err - } - if _, err = stmt.Exec(params...); err != nil { - return nil, err + res := m.db.Save(&share) + if res.Error != nil { + return nil, res.Error } return m.GetShare(ctx, ref) @@ -361,39 +290,6 @@ func (m *mgr) getPath(ctx context.Context, resID *provider.ResourceId) (string, return res.GetPath(), nil } -func (m *mgr) addPathIntoCtx(ctx context.Context, ref *collaboration.ShareReference) (context.Context, error) { - var path string - var err error - switch { - case ref.GetId() != nil: - share, err := m.getByID(ctx, ref.GetId(), false) - if err != nil { - return nil, err - } - - path, err = m.getPath(ctx, share.ResourceId) - if err != nil { - return nil, err - } - case ref.GetKey() != nil: - key := ref.GetKey() - - path, err = m.getPath(ctx, key.ResourceId) - if err != nil { - return nil, err - } - } - return appctx.ContextSetResourcePath(ctx, path), nil -} - -func (m *mgr) isProjectAdminFromCtx(ctx context.Context, u *userpb.User) bool { - path, ok := appctx.ContextGetResourcePath(ctx) - if !ok { - return false - } - return m.isProjectAdmin(u, path) -} - func (m *mgr) isProjectAdmin(u *userpb.User, path string) bool { if strings.HasPrefix(path, projectPathPrefix) { // The path will look like /eos/project/c/cernbox, we need to extract the project name @@ -423,169 +319,125 @@ func (m *mgr) isProjectAdmin(u *userpb.User, path string) bool { } func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.Share, error) { - query := `select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, lower(coalesce(share_with, '')) as share_with, - coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, coalesce(item_type, '') as item_type, - id, stime, permissions, share_type - FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND (share_type=? OR share_type=?)` - params := []interface{}{shareTypeUser, shareTypeGroup} - - groupedFilters := share.GroupFiltersByType(filters) - if len(groupedFilters) > 0 { - filterQuery, filterParams, err := translateFilters(groupedFilters) - if err != nil { - return nil, err - } - params = append(params, filterParams...) - if filterQuery != "" { - query = fmt.Sprintf("%s AND (%s)", query, filterQuery) - } - } + var share model.Share + uid := conversions.FormatUserID(appctx.ContextMustGetUser(ctx).Id) - query, params, err := m.appendUidOwnerFilters(ctx, query, params) - if err != nil { - return nil, err - } + query := m.db.Model(&share). + Where("uid_owner = ? or uid_initiator = ?", uid, uid). + Where("orphan = ?", false) - rows, err := m.db.Query(query, params...) - if err != nil { - return nil, err - } - defer rows.Close() + // Append filters + m.appendFiltersToQuery(query, filters) - var s conversions.DBShare - shares := []*collaboration.Share{} - for rows.Next() { - if err := rows.Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ItemType, &s.ID, &s.STime, &s.Permissions, &s.ShareType); err != nil { - continue - } - gtype, _ := m.getUserType(ctx, s.ShareWith) - // if err != nil { - // failed to resolve grantee's user type, TODO Log - // } - shares = append(shares, conversions.ConvertToCS3Share(s, gtype)) + var shares []model.Share + var cs3shares []*collaboration.Share + res := query.Find(&shares) + if res.Error != nil { + return nil, res.Error } - if err = rows.Err(); err != nil { - return nil, err + + for _, s := range shares { + granteeType, _ := m.getUserType(ctx, s.ShareWith) + cs3shares = append(cs3shares, share.AsCS3Share(granteeType)) } - return shares, nil + return cs3shares, nil } // we list the shares that are targeted to the user in context or to the user groups. func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.ReceivedShare, error) { + var share model.Share user := appctx.ContextMustGetUser(ctx) - uid := conversions.FormatUserID(user.Id) - params := []interface{}{uid, uid, uid, uid} - for _, v := range user.Groups { - params = append(params, v) - } + query := m.db.Model(&share). + Where("orphan = ?", false) - query := `SELECT coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, lower(coalesce(share_with, '')) as share_with, - coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, coalesce(item_type, '') as item_type, - ts.id, stime, permissions, share_type, coalesce(tr.state, 0) as state - FROM oc_share ts LEFT JOIN oc_share_status tr ON (ts.id = tr.id AND tr.recipient = ?) - WHERE (orphan = 0 or orphan IS NULL) AND (uid_owner != ? AND uid_initiator != ?)` - if len(user.Groups) > 0 { - query += " AND ((lower(share_with)=lower(?) AND share_type = 0) OR (share_type = 1 AND lower(share_with) in (?" + strings.Repeat(",?", len(user.Groups)-1) + ")))" - } else { - query += " AND (lower(share_with)=lower(?) AND share_type = 0)" + // Also search by all the groups the user is a member of + innerQuery := m.db.Where("share_with = ? and shared_with_is_group = ?", user.Username, false) + for _, group := range user.Groups { + innerQuery = innerQuery.Or("share_with = ? and shared_with_is_group = ?", group, true) } + query = query.Where(innerQuery) - groupedFilters := share.GroupFiltersByType(filters) - filterQuery, filterParams, err := translateFilters(groupedFilters) - if err != nil { - return nil, err - } - params = append(params, filterParams...) + // Append filters + m.appendFiltersToQuery(query, filters) - if filterQuery != "" { - query = fmt.Sprintf("%s AND (%s)", query, filterQuery) + // Get the shares + var shares []model.Share + res := query.Find(&shares) + if res.Error != nil { + return nil, res.Error } - rows, err := m.db.Query(query, params...) - if err != nil { - return nil, err - } - defer rows.Close() + // Now that we have the shares, we fetch the share state for every share + var receivedShares []*collaboration.ReceivedShare - var s conversions.DBShare - shares := []*collaboration.ReceivedShare{} - for rows.Next() { - if err := rows.Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ItemType, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.State); err != nil { - continue + for _, s := range shares { + shareId := &collaboration.ShareId{ + OpaqueId: strconv.Itoa(int(s.ID)), + } + shareState, err := m.getShareState(ctx, shareId, user) + if err != nil { + return nil, err } - gtype, _ := m.getUserType(ctx, s.ShareWith) - // if err != nil { - // failed to resolve grantee's user type, TODO Log - // } - shares = append(shares, conversions.ConvertToCS3ReceivedShare(s, gtype)) + + granteeType, _ := m.getUserType(ctx, s.ShareWith) + + receivedShares = append(receivedShares, share.AsCS3ReceivedShare(shareState, granteeType)) } - if err = rows.Err(); err != nil { - return nil, err + + return receivedShares, nil +} + +func (m *mgr) getShareState(ctx context.Context, shareId *collaboration.ShareId, user *userpb.User) (*model.ShareState, error) { + var shareState model.ShareState + query := m.db.Model(&shareState). + Where("share_id = ?", shareId.OpaqueId). + Where("user = ?", user.Username) + + res := query.First(&shareState) + + if res.RowsAffected == 0 { + return nil, errtypes.NotFound(shareId.OpaqueId) } - return shares, nil + return &shareState, nil } func (m *mgr) getReceivedByID(ctx context.Context, id *collaboration.ShareId, gtype userpb.UserType) (*collaboration.ReceivedShare, error) { user := appctx.ContextMustGetUser(ctx) - uid := conversions.FormatUserID(user.Id) - - params := []interface{}{uid, id.OpaqueId, uid} - for _, v := range user.Groups { - params = append(params, v) - } - - s := conversions.DBShare{ID: id.OpaqueId} - query := `select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, lower(coalesce(share_with, '')) as share_with, - coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, coalesce(item_type, '') as item_type, - stime, permissions, share_type, coalesce(tr.state, 0) as state - FROM oc_share ts LEFT JOIN oc_share_status tr ON (ts.id = tr.id AND tr.recipient = ?) - WHERE (orphan = 0 or orphan IS NULL) AND ts.id=?` - if len(user.Groups) > 0 { - query += " AND ((lower(share_with)=lower(?) AND share_type = 0) OR (share_type = 1 AND lower(share_with) in (?" + strings.Repeat(",?", len(user.Groups)-1) + ")))" - } else { - query += " AND (lower(share_with)=lower(?) AND share_type = 0)" - } - if err := m.db.QueryRow(query, params...).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ItemType, &s.STime, &s.Permissions, &s.ShareType, &s.State); err != nil { - if err == sql.ErrNoRows { - return nil, errtypes.NotFound(id.OpaqueId) - } + share, err := m.getByID(ctx, id) + if err != nil { return nil, err } - return conversions.ConvertToCS3ReceivedShare(s, gtype), nil + + shareState, err := m.getShareState(ctx, id, user) + if err != nil { + return nil, err + } + + receivedShare := share.AsCS3ReceivedShare(shareState, gtype) + return receivedShare, nil } func (m *mgr) getReceivedByKey(ctx context.Context, key *collaboration.ShareKey, gtype userpb.UserType) (*collaboration.ReceivedShare, error) { user := appctx.ContextMustGetUser(ctx) - uid := conversions.FormatUserID(user.Id) + share, err := m.getByKey(ctx, key, false) + if err != nil { + return nil, err + } - shareType, shareWith := conversions.FormatGrantee(key.Grantee) - params := []interface{}{uid, conversions.FormatUserID(key.Owner), key.ResourceId.StorageId, key.ResourceId.OpaqueId, shareType, shareWith, shareWith} - for _, v := range user.Groups { - params = append(params, v) - } - - s := conversions.DBShare{} - query := `select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, lower(coalesce(share_with, '')) as share_with, - coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, coalesce(item_type, '') as item_type, - ts.id, stime, permissions, share_type, coalesce(tr.state, 0) as state - FROM oc_share ts LEFT JOIN oc_share_status tr ON (ts.id = tr.id AND tr.recipient = ?) - WHERE (orphan = 0 or orphan IS NULL) AND uid_owner=? AND fileid_prefix=? AND item_source=? AND share_type=? AND lower(share_with)=lower(?)` - if len(user.Groups) > 0 { - query += " AND ((lower(share_with)=lower(?) AND share_type = 0) OR (share_type = 1 AND lower(share_with) in (?" + strings.Repeat(",?", len(user.Groups)-1) + ")))" - } else { - query += " AND (lower(share_with)=lower(?) AND share_type = 0)" - } - - if err := m.db.QueryRow(query, params...).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ItemType, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.State); err != nil { - if err == sql.ErrNoRows { - return nil, errtypes.NotFound(key.String()) - } + shareId := &collaboration.ShareId{ + OpaqueId: strconv.Itoa(int(share.ID)), + } + + shareState, err := m.getShareState(ctx, shareId, user) + if err != nil { return nil, err } - return conversions.ConvertToCS3ReceivedShare(s, gtype), nil + + receivedShare := share.AsCS3ReceivedShare(shareState, gtype) + return receivedShare, nil } func (m *mgr) GetReceivedShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.ReceivedShare, error) { @@ -613,131 +465,42 @@ func (m *mgr) GetReceivedShare(ctx context.Context, ref *collaboration.ShareRefe } func (m *mgr) UpdateReceivedShare(ctx context.Context, share *collaboration.ReceivedShare, fieldMask *field_mask.FieldMask) (*collaboration.ReceivedShare, error) { - user := appctx.ContextMustGetUser(ctx) + + // user := appctx.ContextMustGetUser(ctx) rs, err := m.GetReceivedShare(ctx, &collaboration.ShareReference{Spec: &collaboration.ShareReference_Id{Id: share.Share.Id}}) if err != nil { return nil, err } - for i := range fieldMask.Paths { - switch fieldMask.Paths[i] { - case "state": - rs.State = share.State - default: - return nil, errtypes.NotSupported("updating " + fieldMask.Paths[i] + " is not supported") - } - } + // TODO: do actual update based on FieldMask - state := 0 - switch rs.GetState() { - case collaboration.ShareState_SHARE_STATE_REJECTED: - state = -1 - case collaboration.ShareState_SHARE_STATE_ACCEPTED: - state = 1 - } - - params := []interface{}{rs.Share.Id.OpaqueId, conversions.FormatUserID(user.Id), state, state} - query := "insert into oc_share_status(id, recipient, state) values(?, ?, ?) ON DUPLICATE KEY UPDATE state = ?" - - stmt, err := m.db.Prepare(query) - if err != nil { - return nil, err - } - _, err = stmt.Exec(params...) - if err != nil { - return nil, err + res := m.db.Save(&rs.State) + if res.Error != nil { + return nil, res.Error } return rs, nil -} - -func (m *mgr) appendUidOwnerFilters(ctx context.Context, query string, params []interface{}) (string, []interface{}, error) { - uidOwnersQuery, uidOwnersParams, err := m.uidOwnerFilters(ctx) - if err != nil { - return "", nil, err - } - - params = append(params, uidOwnersParams...) - if uidOwnersQuery != "" { - query = fmt.Sprintf("%s AND (%s)", query, uidOwnersQuery) - } - - return query, params, nil -} - -func (m *mgr) uidOwnerFilters(ctx context.Context) (string, []interface{}, error) { - user := appctx.ContextMustGetUser(ctx) - uid := conversions.FormatUserID(user.Id) - - query := "uid_owner=? or uid_initiator=?" - params := []interface{}{uid, uid} - - if m.isProjectAdminFromCtx(ctx, user) { - return "", []interface{}{}, nil - } - - return query, params, nil -} - -func granteeTypeToShareType(granteeType provider.GranteeType) int { - switch granteeType { - case provider.GranteeType_GRANTEE_TYPE_USER: - return shareTypeUser - case provider.GranteeType_GRANTEE_TYPE_GROUP: - return shareTypeGroup - } - return -1 -} - -// translateFilters translates the filters to sql queries. -func translateFilters(filters map[collaboration.Filter_Type][]*collaboration.Filter) (string, []interface{}, error) { - var ( - filterQuery string - params []interface{} - ) - - // If multiple filters of the same type are passed to this function, they need to be combined with the `OR` operator. - // That is why the filters got grouped by type. - // For every given filter type, iterate over the filters and if there are more than one combine them. - // Combine the different filter types using `AND` - var filterCounter = 0 - for filterType, currFilters := range filters { - switch filterType { - case collaboration.Filter_TYPE_RESOURCE_ID: - filterQuery += "(" - for i, f := range currFilters { - filterQuery += "(fileid_prefix =? AND item_source=?)" - params = append(params, f.GetResourceId().StorageId, f.GetResourceId().OpaqueId) - if i != len(currFilters)-1 { - filterQuery += " OR " - } - } - filterQuery += ")" - case collaboration.Filter_TYPE_GRANTEE_TYPE: - filterQuery += "(" - for i, f := range currFilters { - filterQuery += "share_type=?" - params = append(params, granteeTypeToShareType(f.GetGranteeType())) - - if i != len(currFilters)-1 { - filterQuery += " OR " - } - } - filterQuery += ")" - case collaboration.Filter_TYPE_EXCLUDE_DENIALS: - // TODO this may change once the mapping of permission to share types is completed (cf. pkg/cbox/utils/conversions.go) - filterQuery += "(permissions > 0)" - default: - return "", nil, fmt.Errorf("filter type is not supported") - } - if filterCounter != len(filters)-1 { - filterQuery += " AND " - } - filterCounter++ - } - return filterQuery, params, nil + // for i := range fieldMask.Paths { + // switch fieldMask.Paths[i] { + // case "state": + // rs.State = share.State + // default: + // return nil, errtypes.NotSupported("updating " + fieldMask.Paths[i] + " is not supported") + // } + // } + + // state := 0 + // switch rs.GetState() { + // case collaboration.ShareState_SHARE_STATE_REJECTED: + // state = -1 + // case collaboration.ShareState_SHARE_STATE_ACCEPTED: + // state = 1 + // } + + // params := []interface{}{rs.Share.Id.OpaqueId, conversions.FormatUserID(user.Id), state, state} + // query := "insert into oc_share_status(id, recipient, state) values(?, ?, ?) ON DUPLICATE KEY UPDATE state = ?" } func (m *mgr) getUserType(ctx context.Context, username string) (userpb.UserType, error) { @@ -758,3 +521,48 @@ func (m *mgr) getUserType(ctx context.Context, username string) (userpb.UserType return userRes.GetUser().Id.Type, nil } + +func (m *mgr) appendFiltersToQuery(query *gorm.DB, filters []*collaboration.Filter) { + // We want to chain filters of different types with AND + // and filters of the same type with OR + // Therefore, we group them by type + groupedFilters := revashare.GroupFiltersByType(filters) + + for filtertype, filters := range groupedFilters { + switch filtertype { + case collaboration.Filter_TYPE_RESOURCE_ID: + innerQuery := m.db + for i, filter := range filters { + if i == 0 { + innerQuery = innerQuery.Where("instance = ? and inode = ?", filter.GetResourceId().StorageId, filter.GetResourceId().OpaqueId) + } else { + innerQuery = innerQuery.Or("instance = ? and inode = ?", filter.GetResourceId().StorageId, filter.GetResourceId().OpaqueId) + } + } + query = query.Where(innerQuery) + case collaboration.Filter_TYPE_EXCLUDE_DENIALS: + query = query.Where("permissions > ?", 0) + case collaboration.Filter_TYPE_CREATOR: + break + case collaboration.Filter_TYPE_OWNER: + break + case collaboration.Filter_TYPE_STATE: + break + case collaboration.Filter_TYPE_SPACE_ID: + break + case collaboration.Filter_TYPE_GRANTEE_TYPE: + innerQuery := m.db + for i, filter := range filters { + isGroup := filter.GetGranteeType() == provider.GranteeType_GRANTEE_TYPE_GROUP + if i == 0 { + innerQuery = innerQuery.Where("shared_with_is_group = ?", isGroup) + } else { + innerQuery = innerQuery.Or("shared_with_is_group = ? ", isGroup) + } + } + query = query.Where(innerQuery) + default: + break + } + } +}