diff --git a/Dockerfile b/Dockerfile index 6a9f5f6..a4f95cc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,37 @@ -FROM golang:1.13-buster AS buildcontainer +ARG SIGNAL_CLI_VERSION=0.7.2 +ARG ZKGROUP_VERSION=0.7.0 -ARG SIGNAL_CLI_VERSION=0.6.12 ARG SWAG_VERSION=1.6.7 +FROM golang:1.14-buster AS buildcontainer + +ARG SIGNAL_CLI_VERSION +ARG ZKGROUP_VERSION +ARG SWAG_VERSION + ENV GIN_MODE=release +COPY ext/libraries/zkgroup/v${ZKGROUP_VERSION} /tmp/zkgroup-libraries + +RUN ls -la /tmp/zkgroup-libraries/x86-64 + +RUN arch="$(uname -m)"; \ + case "$arch" in \ + aarch64) cp /tmp/zkgroup-libraries/arm64/libzkgroup.so /tmp/libzkgroup.so ;; \ + armv7l) cp /tmp/zkgroup-libraries/armv7/libzkgroup.so /tmp/libzkgroup.so ;; \ + x86_64) cp /tmp/zkgroup-libraries/x86-64/libzkgroup.so /tmp/libzkgroup.so ;; \ + esac; + RUN apt-get update \ - && apt-get install -y --no-install-recommends wget default-jre software-properties-common git locales \ + && apt-get install -y --no-install-recommends wget default-jre software-properties-common git locales zip file \ && rm -rf /var/lib/apt/lists/* RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \ dpkg-reconfigure --frontend=noninteractive locales && \ update-locale LANG=en_US.UTF-8 +ENV PATH="/root/.cargo/bin:${PATH}" + ENV LANG en_US.UTF-8 RUN cd /tmp/ \ @@ -29,7 +48,21 @@ RUN cd /tmp/ \ && git checkout v${SIGNAL_CLI_VERSION} \ && ./gradlew build \ && ./gradlew installDist \ - && ln -s /tmp/signal-cli-${SIGNAL_CLI_VERSION}/build/install/signal-cli/ /tmp/signal-cli + && ./gradlew distTar + +RUN ls /tmp/signal-cli-${SIGNAL_CLI_VERSION}/build/install/signal-cli/lib/zkgroup-java-${ZKGROUP_VERSION}.jar || (echo "\n\nzkgroup jar file with version ${ZKGROUP_VERSION} not found. Maybe the version needs to be bumped in the signal-cli-rest-api Dockerfile?\n\n" && echo "Available version: \n" && ls /tmp/signal-cli-${SIGNAL_CLI_VERSION}/build/install/signal-cli/lib/zkgroup-java-* && echo "\n\n" && exit 1) + +RUN cd /tmp/ \ + && zip -u /tmp/signal-cli-${SIGNAL_CLI_VERSION}/build/install/signal-cli/lib/zkgroup-java-${ZKGROUP_VERSION}.jar libzkgroup.so + +RUN cd /tmp/signal-cli-${SIGNAL_CLI_VERSION}/build/distributions/ \ + && mkdir -p signal-cli-${SIGNAL_CLI_VERSION}/lib/ \ + && cp /tmp/signal-cli-${SIGNAL_CLI_VERSION}/build/install/signal-cli/lib/zkgroup-java-${ZKGROUP_VERSION}.jar signal-cli-${SIGNAL_CLI_VERSION}/lib/ \ + # update zip + && zip -u /tmp/signal-cli-${SIGNAL_CLI_VERSION}/build/distributions/signal-cli-${SIGNAL_CLI_VERSION}.zip signal-cli-${SIGNAL_CLI_VERSION}/lib/zkgroup-java-${ZKGROUP_VERSION}.jar \ + # update tar + && tar --delete -vPf /tmp/signal-cli-${SIGNAL_CLI_VERSION}/build/distributions/signal-cli-${SIGNAL_CLI_VERSION}.tar signal-cli-${SIGNAL_CLI_VERSION}/lib/zkgroup-java-${ZKGROUP_VERSION}.jar \ + && tar --owner='' --group='' -rvPf /tmp/signal-cli-${SIGNAL_CLI_VERSION}/build/distributions/signal-cli-${SIGNAL_CLI_VERSION}.tar signal-cli-${SIGNAL_CLI_VERSION}/lib/zkgroup-java-${ZKGROUP_VERSION}.jar COPY src/api /tmp/signal-cli-rest-api-src/api COPY src/main.go /tmp/signal-cli-rest-api-src/ @@ -41,20 +74,27 @@ RUN cd /tmp/signal-cli-rest-api-src && swag init && go build # Start a fresh container for release container FROM adoptopenjdk:11-jdk-hotspot-bionic +ARG SIGNAL_CLI_VERSION + RUN apt-get update \ && apt-get install -y --no-install-recommends setpriv \ && rm -rf /var/lib/apt/lists/* COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/signal-cli-rest-api /usr/bin/signal-cli-rest-api -COPY --from=buildcontainer /tmp/signal-cli /opt/signal-cli +COPY --from=buildcontainer /tmp/signal-cli-${SIGNAL_CLI_VERSION}/build/distributions/signal-cli-${SIGNAL_CLI_VERSION}.tar /tmp/signal-cli-${SIGNAL_CLI_VERSION}.tar COPY entrypoint.sh /entrypoint.sh +RUN tar xf /tmp/signal-cli-${SIGNAL_CLI_VERSION}.tar -C /opt +RUN rm -rf /tmp/signal-cli-${SIGNAL_CLI_VERSION} + RUN groupadd -g 1000 signal-api \ && useradd --no-log-init -M -d /home -s /bin/bash -u 1000 -g 1000 signal-api \ - && ln -s /opt/signal-cli/bin/signal-cli /usr/bin/signal-cli \ + && ln -s /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli /usr/bin/signal-cli \ && mkdir -p /signal-cli-config/ \ && mkdir -p /home/.local/share/signal-cli EXPOSE 8080 ENTRYPOINT ["/entrypoint.sh"] + + diff --git a/README.md b/README.md index 9566bc7..8b7f193 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ At the moment, the following functionality is exposed via REST: - Receive messages - Link devices - Create/Liste/Remove groups +- List/Serve/Delete attachments +- Update profile ## Examples diff --git a/ext/libraries/zkgroup/README.md b/ext/libraries/zkgroup/README.md new file mode 100644 index 0000000..eef4615 --- /dev/null +++ b/ext/libraries/zkgroup/README.md @@ -0,0 +1,11 @@ +# HOWTO BUILD + +[cross](https://github.com/rust-embedded/cross) is used for cross compiling zkgroup. + +* download new release from `https://github.com/signalapp/zkgroup/releases` +* unzip + change into directory +* run `cross build --target x86_64-unknown-linux-gnu --release` + run `cross build --target armv7-unknown-linux-gnueabihf --release` + run `cross build --target aarch64-unknown-linux-gnu --release` +to build the library for `x86-64`, `armv7` and `arm64` +* the built library will be in the `target//release` folder diff --git a/ext/libraries/zkgroup/v0.7.0/arm64/libzkgroup.so b/ext/libraries/zkgroup/v0.7.0/arm64/libzkgroup.so new file mode 100644 index 0000000..0df5486 Binary files /dev/null and b/ext/libraries/zkgroup/v0.7.0/arm64/libzkgroup.so differ diff --git a/ext/libraries/zkgroup/v0.7.0/armv7/libzkgroup.so b/ext/libraries/zkgroup/v0.7.0/armv7/libzkgroup.so new file mode 100644 index 0000000..c87e02d Binary files /dev/null and b/ext/libraries/zkgroup/v0.7.0/armv7/libzkgroup.so differ diff --git a/ext/libraries/zkgroup/v0.7.0/x86-64/libzkgroup.so b/ext/libraries/zkgroup/v0.7.0/x86-64/libzkgroup.so new file mode 100644 index 0000000..83adddb Binary files /dev/null and b/ext/libraries/zkgroup/v0.7.0/x86-64/libzkgroup.so differ diff --git a/ext/libraries/zkgroup/v0.7.1/arm64/libzkgroup.so b/ext/libraries/zkgroup/v0.7.1/arm64/libzkgroup.so new file mode 100644 index 0000000..53d7ed9 Binary files /dev/null and b/ext/libraries/zkgroup/v0.7.1/arm64/libzkgroup.so differ diff --git a/ext/libraries/zkgroup/v0.7.1/armv7/libzkgroup.so b/ext/libraries/zkgroup/v0.7.1/armv7/libzkgroup.so new file mode 100644 index 0000000..56313da Binary files /dev/null and b/ext/libraries/zkgroup/v0.7.1/armv7/libzkgroup.so differ diff --git a/ext/libraries/zkgroup/v0.7.1/x86-64/libzkgroup.so b/ext/libraries/zkgroup/v0.7.1/x86-64/libzkgroup.so new file mode 100644 index 0000000..e564c51 Binary files /dev/null and b/ext/libraries/zkgroup/v0.7.1/x86-64/libzkgroup.so differ diff --git a/src/api/api.go b/src/api/api.go index 30a08ca..f7aa643 100644 --- a/src/api/api.go +++ b/src/api/api.go @@ -72,6 +72,11 @@ type CreateGroup struct { Id string `json:"id"` } +type UpdateProfileRequest struct { + Name string `json:"name"` + Base64Avatar string `json:"base64_avatar"` +} + func convertInternalGroupIdToGroupId(internalId string) string { return groupPrefix + base64.StdEncoding.EncodeToString([]byte(internalId)) } @@ -289,12 +294,14 @@ func runSignalCli(wait bool, args []string, stdin string) (string, error) { type Api struct { signalCliConfig string attachmentTmpDir string + avatarTmpDir string } -func NewApi(signalCliConfig string, attachmentTmpDir string) *Api { +func NewApi(signalCliConfig string, attachmentTmpDir string, avatarTmpDir string) *Api { return &Api{ signalCliConfig: signalCliConfig, attachmentTmpDir: attachmentTmpDir, + avatarTmpDir: avatarTmpDir, } } @@ -694,7 +701,7 @@ func (a *Api) GetAttachments(c *gin.Context) { // @Tags Attachments // @Description Remove the attachment with the given id from filesystem. // @Produce json -// @Success 200 {string} OK +// @Success 204 {string} OK // @Failure 400 {object} Error // @Param attachment path string true "Attachment ID" // @Router /v1/attachments/{attachment} [delete] @@ -759,3 +766,87 @@ func (a *Api) ServeAttachment(c *gin.Context) { return } } + +// @Summary Update Profile. +// @Tags Profiles +// @Description Set your name and optional an avatar. +// @Produce json +// @Success 204 {string} OK +// @Failure 400 {object} Error +// @Param data body UpdateProfileRequest true "Profile Data" +// @Param number path string true "Registered Phone Number" +// @Router /v1/profiles/{number} [put] +func (a *Api) UpdateProfile(c *gin.Context) { + number := c.Param("number") + + var req UpdateProfileRequest + err := c.BindJSON(&req) + if err != nil { + c.JSON(400, Error{Msg: "Couldn't process request - invalid request"}) + log.Error(err.Error()) + return + } + + if req.Name == "" { + c.JSON(400, Error{Msg: "Couldn't process request - profile name missing"}) + return + } + cmd := []string{"--config", a.signalCliConfig, "-u", number, "updateProfile", "--name", req.Name} + + avatarTmpPaths := []string{} + if req.Base64Avatar == "" { + cmd = append(cmd, "--remove-avatar") + } else { + u, err := uuid.NewV4() + if err != nil { + c.JSON(400, Error{Msg: err.Error()}) + return + } + + avatarBytes, err := base64.StdEncoding.DecodeString(req.Base64Avatar) + if err != nil { + c.JSON(400, Error{Msg: "Couldn't decode base64 encoded avatar"}) + return + } + + fType, err := filetype.Get(avatarBytes) + if err != nil { + c.JSON(400, Error{Msg: err.Error()}) + return + } + + avatarTmpPath := a.avatarTmpDir + u.String() + "." + fType.Extension + + f, err := os.Create(avatarTmpPath) + if err != nil { + c.JSON(400, Error{Msg: err.Error()}) + return + } + defer f.Close() + + if _, err := f.Write(avatarBytes); err != nil { + cleanupTmpFiles(avatarTmpPaths) + c.JSON(400, Error{Msg: err.Error()}) + return + } + if err := f.Sync(); err != nil { + cleanupTmpFiles(avatarTmpPaths) + c.JSON(400, Error{Msg: err.Error()}) + return + } + f.Close() + + cmd = append(cmd, []string{"--avatar", avatarTmpPath}...) + avatarTmpPaths = append(avatarTmpPaths, avatarTmpPath) + } + + _, err = runSignalCli(true, cmd, "") + if err != nil { + cleanupTmpFiles(avatarTmpPaths) + c.JSON(400, Error{Msg: err.Error()}) + return + } + + cleanupTmpFiles(avatarTmpPaths) + c.Status(http.StatusNoContent) +} diff --git a/src/docs/docs.go b/src/docs/docs.go index 3028120..d592327 100644 --- a/src/docs/docs.go +++ b/src/docs/docs.go @@ -127,8 +127,8 @@ var doc = `{ } ], "responses": { - "200": { - "description": "OK", + "204": { + "description": "No Content", "schema": { "type": "string" } @@ -264,6 +264,50 @@ var doc = `{ } } }, + "/v1/profiles/{number}": { + "put": { + "description": "Set your name and optional an avatar.", + "produces": [ + "application/json" + ], + "tags": [ + "Profiles" + ], + "summary": "Update Profile.", + "parameters": [ + { + "description": "Profile Data", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.UpdateProfileRequest" + } + }, + { + "type": "string", + "description": "Registered Phone Number", + "name": "number", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.Error" + } + } + } + } + }, "/v1/qrcodelink": { "get": { "description": "Link device and generate QR code", @@ -609,6 +653,17 @@ var doc = `{ } } }, + "api.UpdateProfileRequest": { + "type": "object", + "properties": { + "base64_avatar": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, "api.VerifyNumberSettings": { "type": "object", "properties": { @@ -638,6 +693,10 @@ var doc = `{ { "description": "List and Delete Attachments.", "name": "Attachments" + }, + { + "description": "Update Profile.", + "name": "Profiles" } ] }` diff --git a/src/docs/swagger.json b/src/docs/swagger.json index 2f1c557..7a1dd58 100644 --- a/src/docs/swagger.json +++ b/src/docs/swagger.json @@ -112,8 +112,8 @@ } ], "responses": { - "200": { - "description": "OK", + "204": { + "description": "No Content", "schema": { "type": "string" } @@ -249,6 +249,50 @@ } } }, + "/v1/profiles/{number}": { + "put": { + "description": "Set your name and optional an avatar.", + "produces": [ + "application/json" + ], + "tags": [ + "Profiles" + ], + "summary": "Update Profile.", + "parameters": [ + { + "description": "Profile Data", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.UpdateProfileRequest" + } + }, + { + "type": "string", + "description": "Registered Phone Number", + "name": "number", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.Error" + } + } + } + } + }, "/v1/qrcodelink": { "get": { "description": "Link device and generate QR code", @@ -594,6 +638,17 @@ } } }, + "api.UpdateProfileRequest": { + "type": "object", + "properties": { + "base64_avatar": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, "api.VerifyNumberSettings": { "type": "object", "properties": { @@ -623,6 +678,10 @@ { "description": "List and Delete Attachments.", "name": "Attachments" + }, + { + "description": "Update Profile.", + "name": "Profiles" } ] } \ No newline at end of file diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index 05184cf..4ec235d 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -66,6 +66,13 @@ definitions: type: string type: array type: object + api.UpdateProfileRequest: + properties: + base64_avatar: + type: string + name: + type: string + type: object api.VerifyNumberSettings: properties: pin: @@ -123,8 +130,8 @@ paths: produces: - application/json responses: - "200": - description: OK + "204": + description: No Content schema: type: string "400": @@ -237,6 +244,35 @@ paths: summary: Delete a Signal Group. tags: - Groups + /v1/profiles/{number}: + put: + description: Set your name and optional an avatar. + parameters: + - description: Profile Data + in: body + name: data + required: true + schema: + $ref: '#/definitions/api.UpdateProfileRequest' + - description: Registered Phone Number + in: path + name: number + required: true + type: string + produces: + - application/json + responses: + "204": + description: No Content + schema: + type: string + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.Error' + summary: Update Profile. + tags: + - Profiles /v1/qrcodelink: get: description: Link device and generate QR code @@ -408,3 +444,5 @@ tags: name: Messages - description: List and Delete Attachments. name: Attachments +- description: Update Profile. + name: Profiles diff --git a/src/main.go b/src/main.go index 2d7c0ed..75c299b 100644 --- a/src/main.go +++ b/src/main.go @@ -34,17 +34,21 @@ import ( // @tag.name Attachments // @tag.description List and Delete Attachments. +// @tag.name Profiles +// @tag.description Update Profile. + // @host 127.0.0.1:8080 // @BasePath / func main() { signalCliConfig := flag.String("signal-cli-config", "/home/.local/share/signal-cli/", "Config directory where signal-cli config is stored") attachmentTmpDir := flag.String("attachment-tmp-dir", "/tmp/", "Attachment tmp directory") + avatarTmpDir := flag.String("avatar-tmp-dir", "/tmp/", "Avatar tmp directory") flag.Parse() router := gin.Default() log.Info("Started Signal Messenger REST API") - api := api.NewApi(*signalCliConfig, *attachmentTmpDir) + api := api.NewApi(*signalCliConfig, *attachmentTmpDir, *avatarTmpDir) v1 := router.Group("/v1") { about := v1.Group("/about") @@ -86,6 +90,11 @@ func main() { attachments.DELETE(":attachment", api.RemoveAttachment) attachments.GET(":attachment", api.ServeAttachment) } + + profiles := v1.Group("profiles") + { + profiles.PUT(":number", api.UpdateProfile) + } } v2 := router.Group("/v2")