├── gradle.properties ├── History.md ├── orgs_ignored.yaml.enc ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── micronaut-cli.yml ├── src ├── main │ ├── resources │ │ ├── html │ │ │ ├── Slack_Mark_Web.png │ │ │ ├── styles.css │ │ │ └── index.html │ │ ├── META-INF │ │ │ └── native-image │ │ │ │ └── wonky │ │ │ │ └── wonky-application │ │ │ │ └── native-image.properties │ │ ├── logback.xml │ │ └── application.yml │ ├── k8s │ │ ├── service.yaml │ │ └── deployment.yaml │ └── java │ │ └── wonky │ │ ├── security │ │ └── AuthenticationProviderUserPassword.java │ │ ├── model │ │ ├── TelegramInfo.java │ │ └── Organization.java │ │ ├── api │ │ ├── Invite.java │ │ └── ApiController.java │ │ ├── Application.java │ │ ├── service │ │ ├── SlackOrganization.java │ │ ├── EntityNotFoundException.java │ │ └── SlackService.java │ │ ├── slack │ │ ├── Team.java │ │ └── Icon.java │ │ ├── http │ │ ├── SlackResponseException.java │ │ ├── SlackResponseExceptionHandler.java │ │ ├── EntityNotFoundExceptionHandler.java │ │ └── SlackClient.java │ │ ├── config │ │ └── Bootstrap.java │ │ └── json │ │ └── JacksonUtil.java └── test │ ├── resources │ └── foo.yaml │ └── groovy │ └── wonky │ ├── json │ └── JacksonUtilSpec.groovy │ ├── service │ └── SlackServiceSpec.groovy │ └── api │ └── ApiControllerSpec.groovy ├── .gitignore ├── runDockerNative ├── .editorconfig ├── runDocker ├── .github ├── dependabot.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── HEADER_LICENSE ├── .travis.yml ├── gradlew.bat ├── README.md ├── CODE_OF_CONDUCT.md ├── gradlew └── LICENSE /gradle.properties: -------------------------------------------------------------------------------- 1 | version=0.5.5-SNAPSHOT 2 | micronautVersion=3.4.1 3 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.1.0 / 2015-08-05 3 | ================== 4 | 5 | * Initial release. -------------------------------------------------------------------------------- /orgs_ignored.yaml.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domix/wonky/HEAD/orgs_ignored.yaml.enc -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domix/wonky/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /micronaut-cli.yml: -------------------------------------------------------------------------------- 1 | profile: service 2 | defaultPackage: wonky 3 | --- 4 | testFramework: spock 5 | sourceLanguage: java -------------------------------------------------------------------------------- /src/main/resources/html/Slack_Mark_Web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domix/wonky/HEAD/src/main/resources/html/Slack_Mark_Web.png -------------------------------------------------------------------------------- /src/test/resources/foo.yaml: -------------------------------------------------------------------------------- 1 | - !!wonky.service.SlackOrganization 2 | token: "01" 3 | wonkyDomain: "01" 4 | - !!wonky.service.SlackOrganization 5 | token: "02" 6 | wonkyDomain: "02" -------------------------------------------------------------------------------- /src/main/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: wonky 5 | spec: 6 | selector: 7 | app: wonky 8 | ports: 9 | - protocol: TCP 10 | port: 8080 11 | -------------------------------------------------------------------------------- /src/main/resources/html/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | display: flex; 3 | min-height: 100vh; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1 0 auto; 9 | } 10 | 11 | .page-footer { 12 | background-color: #26a59a; 13 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/native-image/wonky/wonky-application/native-image.properties: -------------------------------------------------------------------------------- 1 | Args = -H:IncludeResources=logback.xml|application.yml|html/index.html|html/styles.css|html/Slack_Mark_Web.png \ 2 | -H:Name=wonky \ 3 | -H:Class=wonky.Application -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build 3 | out 4 | .gradletasknamecache 5 | *.iml 6 | *.ipr 7 | *.iws 8 | .idea 9 | README.html 10 | .classpath 11 | .project 12 | .settings 13 | .factorypath 14 | *.hprof 15 | src/test/resources/orgs_ignored.yaml 16 | orgs_ignored.yaml 17 | security.yaml 18 | tenants.yaml 19 | dive.log 20 | -------------------------------------------------------------------------------- /runDockerNative: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | TAG=$(grep "version" gradle.properties|cut -d'=' -f2) 4 | TAG=$(echo "$TAG" | tr '[:upper:]' '[:lower:]') 5 | TAG="$TAG-native" 6 | 7 | if [[ "$(docker images -q domix/wonky:$TAG 2> /dev/null)" == "" ]]; then 8 | ./gradlew buildImage 9 | fi 10 | 11 | echo "running domix/wonky:$TAG" 12 | #run container 13 | docker run --rm -p 8080:8080 -v `pwd`/orgs_ignored.yaml:/etc/wonky/tenants.yaml domix/wonky:$TAG 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | max_line_length = 120 10 | tab_width = 2 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | max_line_length = 100 15 | 16 | [{*.gant,*.gradle,*.groovy,*.gson,*.gy}] 17 | ij_groovy_label_indent_size = 2 18 | ij_groovy_spaces_within_brackets = true 19 | ij_groovy_spaces_within_gstring_injection_braces = true 20 | -------------------------------------------------------------------------------- /runDocker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | TAG=$(grep "version" gradle.properties|cut -d'=' -f2) 4 | 5 | JRE=$(grep "jreVersion" gradle.properties|cut -d'=' -f2) 6 | TAG=$(echo "$TAG" | tr '[:upper:]' '[:lower:]') 7 | TAG="$TAG" 8 | 9 | if [[ "$(docker images -q domix/wonky:$TAG 2> /dev/null)" == "" ]]; then 10 | ./gradlew buildImage 11 | fi 12 | 13 | echo "running domix/wonky:$TAG" 14 | #run container 15 | docker run --rm -p 8080:8080 -v `pwd`/orgs_ignored.yaml:/etc/wonky/tenants.yaml domix/wonky:$TAG 16 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gradle" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /HEADER_LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (C) 2014-2019 the original author or authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | micronaut: 2 | application: 3 | name: wonky 4 | security: 5 | intercept-url-map: 6 | - 7 | pattern: /static/** 8 | http-method: GET 9 | access: 10 | - isAnonymous() 11 | - pattern: /** 12 | http-method: GET 13 | access: 14 | - isAnonymous() 15 | router: 16 | static-resources: 17 | default: 18 | enabled: true 19 | mapping: /** 20 | paths: 21 | - classpath:html 22 | static: 23 | enabled: true 24 | mapping: /static/** 25 | paths: 26 | - classpath:html 27 | server: 28 | port: 8080 29 | tracing: 30 | jaeger: 31 | enabled: true 32 | -------------------------------------------------------------------------------- /src/main/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: wonky 5 | labels: 6 | app: wonky 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: wonky 11 | template: 12 | metadata: 13 | labels: 14 | app: wonky 15 | spec: 16 | containers: 17 | - name: wonky 18 | image: domix/wonky:0.2 19 | ports: 20 | - containerPort: 8080 21 | protocol: TCP 22 | resources: 23 | limits: 24 | memory: "256Mi" 25 | cpu: "500m" 26 | volumeMounts: 27 | - name: wonky-tenants-volume 28 | mountPath: /etc/wonky 29 | volumes: 30 | - name: wonky-tenants-volume 31 | configMap: 32 | name: wonky-tenants -------------------------------------------------------------------------------- /src/main/java/wonky/security/AuthenticationProviderUserPassword.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2019 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package wonky.security; 17 | 18 | public class AuthenticationProviderUserPassword {//*implements AuthenticationProvider { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/wonky/model/TelegramInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2019 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package wonky.model; 17 | 18 | public class TelegramInfo { 19 | private boolean activated; 20 | private boolean showChannels; 21 | private String name; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/wonky/api/Invite.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2019 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package wonky.api; 17 | 18 | import io.micronaut.core.annotation.Introspected; 19 | import lombok.Getter; 20 | import lombok.Setter; 21 | 22 | @Getter 23 | @Setter 24 | @Introspected 25 | public class Invite { 26 | private String email; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/wonky/Application.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2019 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package wonky; 17 | 18 | import io.micronaut.runtime.Micronaut; 19 | 20 | /** 21 | * Created by domix on 01/06/18. 22 | */ 23 | public class Application { 24 | 25 | public static void main(String[] args) { 26 | Micronaut.run(Application.class); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /src/main/java/wonky/model/Organization.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2019 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package wonky.model; 17 | 18 | import io.micronaut.core.annotation.Introspected; 19 | import lombok.Getter; 20 | import lombok.Setter; 21 | import wonky.slack.Team; 22 | 23 | @Setter 24 | @Getter 25 | @Introspected 26 | public class Organization { 27 | private Team team; 28 | //TODO: Users info 29 | //TODO: channels info 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/wonky/service/SlackOrganization.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2019 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package wonky.service; 17 | 18 | import io.micronaut.core.annotation.Introspected; 19 | import lombok.Getter; 20 | import lombok.Setter; 21 | 22 | /** 23 | * Created by domix on 01/06/18. 24 | */ 25 | @Setter 26 | @Getter 27 | @Introspected 28 | public class SlackOrganization { 29 | private String token; 30 | private String wonkyDomain; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/wonky/slack/Team.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2019 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package wonky.slack; 17 | 18 | import com.fasterxml.jackson.annotation.JsonProperty; 19 | import io.micronaut.core.annotation.Introspected; 20 | import lombok.Getter; 21 | import lombok.Setter; 22 | 23 | /** 24 | * Created by domix on 05/06/18. 25 | */ 26 | @Setter 27 | @Getter 28 | @Introspected 29 | public class Team { 30 | private String domain; 31 | @JsonProperty("email_domain") 32 | private String emailDomain; 33 | private Icon icon; 34 | private String id; 35 | private String name; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/wonky/service/EntityNotFoundException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2019 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package wonky.service; 17 | 18 | import io.micronaut.core.annotation.Introspected; 19 | import lombok.Getter; 20 | 21 | @Getter 22 | @Introspected 23 | public class EntityNotFoundException extends RuntimeException { 24 | private final String entity; 25 | private final String id; 26 | 27 | public EntityNotFoundException(String entity, String id) { 28 | this("The entity can not be found.", entity, id); 29 | } 30 | 31 | public EntityNotFoundException(String message, String entity, String id) { 32 | super(message); 33 | 34 | this.entity = entity; 35 | this.id = id; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/wonky/http/SlackResponseException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2019 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package wonky.http; 17 | 18 | import io.micronaut.core.annotation.Introspected; 19 | import lombok.Getter; 20 | 21 | @Getter 22 | @Introspected 23 | public class SlackResponseException extends RuntimeException { 24 | private final String error; 25 | private final String slackResponse; 26 | 27 | public SlackResponseException(String error, String slackResponse) { 28 | this("Slack error response", error, slackResponse); 29 | } 30 | 31 | public SlackResponseException(String message, String error, String slackResponse) { 32 | super(message); 33 | this.error = error; 34 | this.slackResponse = slackResponse; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/wonky/config/Bootstrap.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2019 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package wonky.config; 17 | 18 | import io.micronaut.context.event.StartupEvent; 19 | import io.micronaut.runtime.event.annotation.EventListener; 20 | import io.micronaut.scheduling.annotation.Async; 21 | import jakarta.inject.Singleton; 22 | import lombok.extern.slf4j.Slf4j; 23 | import wonky.service.SlackService; 24 | 25 | @Slf4j 26 | @Singleton 27 | public class Bootstrap { 28 | private final SlackService slackService; 29 | 30 | public Bootstrap(SlackService slackService) { 31 | this.slackService = slackService; 32 | } 33 | 34 | @EventListener 35 | @Async 36 | void onStartup(StartupEvent event) { 37 | slackService.loadAllOrgs(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/wonky/slack/Icon.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2019 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package wonky.slack; 17 | 18 | import com.fasterxml.jackson.annotation.JsonProperty; 19 | import io.micronaut.core.annotation.Introspected; 20 | import lombok.Getter; 21 | import lombok.Setter; 22 | 23 | /** 24 | * Created by domix on 05/06/18. 25 | */ 26 | @Setter 27 | @Getter 28 | @Introspected 29 | public class Icon { 30 | @JsonProperty("image_102") 31 | private String image102; 32 | @JsonProperty("image_132") 33 | private String image132; 34 | @JsonProperty("image_230") 35 | private String image230; 36 | @JsonProperty("image_34") 37 | private String image34; 38 | @JsonProperty("image_44") 39 | private String image44; 40 | @JsonProperty("image_68") 41 | private String image68; 42 | @JsonProperty("image_88") 43 | private String image88; 44 | @JsonProperty("image_original") 45 | private String imageOriginal; 46 | } 47 | -------------------------------------------------------------------------------- /src/test/groovy/wonky/json/JacksonUtilSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright (C) 2014-2019 the original author or authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package wonky.json 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper 20 | import spock.lang.Specification 21 | import wonky.slack.Team 22 | 23 | class JacksonUtilSpec extends Specification { 24 | def foo() { 25 | given: 26 | def util = new JacksonUtil(new ObjectMapper()) 27 | Team team = new Team(domain: 'dd.com') 28 | def json = util.toJson(team) 29 | expect: 30 | util.readValue(json, null, Team) 31 | } 32 | 33 | def bar() { 34 | when: 35 | def util = new JacksonUtil(new ObjectMapper()) 36 | util.readValue("ssffs", null, Team) 37 | then: 38 | thrown RuntimeException 39 | when: 40 | util.readValue("ssffs", "ss", Team) 41 | then: 42 | thrown RuntimeException 43 | when: 44 | util.toJson(new Object()) 45 | then: 46 | thrown RuntimeException 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/wonky/http/SlackResponseExceptionHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2019 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package wonky.http; 17 | 18 | import io.micronaut.context.annotation.Requires; 19 | import io.micronaut.http.HttpRequest; 20 | import io.micronaut.http.HttpResponse; 21 | import io.micronaut.http.annotation.Produces; 22 | import io.micronaut.http.server.exceptions.ExceptionHandler; 23 | import jakarta.inject.Singleton; 24 | 25 | import static io.micronaut.core.util.CollectionUtils.mapOf; 26 | 27 | @Produces 28 | @Singleton 29 | @Requires(classes = {SlackResponseException.class, ExceptionHandler.class}) 30 | public class SlackResponseExceptionHandler implements ExceptionHandler> { 31 | 32 | @Override 33 | public HttpResponse handle(HttpRequest request, SlackResponseException exception) { 34 | final var data = mapOf( 35 | "message", exception.getMessage(), 36 | "error", exception.getError(), 37 | "slackResponse", exception.getSlackResponse()); 38 | 39 | return HttpResponse.serverError(data); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/wonky/http/EntityNotFoundExceptionHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2019 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package wonky.http; 17 | 18 | import io.micronaut.context.annotation.Requires; 19 | import io.micronaut.http.HttpRequest; 20 | import io.micronaut.http.HttpResponse; 21 | import io.micronaut.http.annotation.Produces; 22 | import io.micronaut.http.server.exceptions.ExceptionHandler; 23 | import jakarta.inject.Singleton; 24 | import wonky.service.EntityNotFoundException; 25 | 26 | import static io.micronaut.core.util.CollectionUtils.mapOf; 27 | 28 | @Produces 29 | @Singleton 30 | @Requires(classes = {EntityNotFoundException.class, ExceptionHandler.class}) 31 | public class EntityNotFoundExceptionHandler implements ExceptionHandler> { 32 | 33 | @Override 34 | public HttpResponse handle(HttpRequest request, EntityNotFoundException exception) { 35 | final var data = mapOf( 36 | "message", exception.getMessage(), 37 | "entity", exception.getEntity(), 38 | "id", exception.getId()); 39 | return HttpResponse.notFound(data); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/groovy/wonky/service/SlackServiceSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright (C) 2014-2019 the original author or authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package wonky.service 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper 20 | import spock.lang.Ignore 21 | import spock.lang.Specification 22 | import wonky.http.SlackClient 23 | import wonky.json.JacksonUtil 24 | import wonky.model.TelegramInfo 25 | import wonky.security.AuthenticationProviderUserPassword 26 | 27 | /** 28 | * Created by domix on 01/06/18. 29 | */ 30 | class SlackServiceSpec extends Specification { 31 | 32 | def smoke() { 33 | given: 34 | new TelegramInfo() 35 | new AuthenticationProviderUserPassword() 36 | expect: 37 | //WTF! 38 | true 39 | } 40 | 41 | def foo() { 42 | given: 43 | def service = new SlackService(Mock(SlackClient), './src/test/resources/foo.yaml', 100) 44 | service.load() 45 | expect: 46 | service.orgs.size() == 2 47 | } 48 | 49 | @Ignore 50 | def foo2() { 51 | given: 52 | def token = System.getenv("TOKEN") 53 | assert token 54 | def jacksonUtil = new JacksonUtil(objectMapper: new ObjectMapper()) 55 | def service = new SlackService(tenantsFile: './src/test/resources/foo.yaml', jacksonUtil: jacksonUtil) 56 | def information = service.tenantSlackInformation(token) 57 | expect: 58 | information 59 | information.icon.imageOriginal 60 | println information.getIcon().getImageOriginal() 61 | } 62 | 63 | def bar() { 64 | when: 65 | def service = new SlackService(Mock(SlackClient), './src/test/resources/notfile.yaml', 100) 66 | service.load() 67 | then: 68 | thrown IllegalStateException 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/wonky/json/JacksonUtil.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2019 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package wonky.json; 17 | 18 | import com.fasterxml.jackson.core.JsonProcessingException; 19 | import com.fasterxml.jackson.databind.JsonNode; 20 | import com.fasterxml.jackson.databind.ObjectMapper; 21 | import jakarta.inject.Singleton; 22 | 23 | import java.io.IOException; 24 | 25 | import static io.micronaut.core.util.StringUtils.isNotEmpty; 26 | 27 | /** 28 | * Created by domix on 05/06/18. 29 | */ 30 | @Singleton 31 | public class JacksonUtil { 32 | private final ObjectMapper objectMapper; 33 | 34 | public JacksonUtil(ObjectMapper objectMapper) { 35 | this.objectMapper = objectMapper; 36 | } 37 | 38 | public String toJson(Object d) { 39 | try { 40 | return objectMapper.writeValueAsString(d); 41 | } catch (JsonProcessingException e) { 42 | //TODO: improve exception handling 43 | throw new RuntimeException(e.getMessage(), e); 44 | } 45 | } 46 | 47 | public T readValue(String content, String node, Class valueType) { 48 | if (isNotEmpty(node)) { 49 | try { 50 | JsonNode jsonNode = objectMapper.readTree(content).get(node); 51 | return objectMapper.treeToValue(jsonNode, valueType); 52 | } catch (IOException e) { 53 | //TODO: improve exception handling 54 | throw new RuntimeException(e.getMessage(), e); 55 | } 56 | } else { 57 | return readValue(content, valueType); 58 | } 59 | } 60 | 61 | public T readValue(String content, Class valueType) { 62 | try { 63 | return objectMapper.readValue(content, valueType); 64 | } catch (IOException e) { 65 | //TODO: improve exception handling 66 | throw new RuntimeException(e.getMessage(), e); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: openjdk17 4 | 5 | env: 6 | global: 7 | - WONKY_TENANTS_FILE=./orgs_ignored.yaml 8 | - secure: YynxQ8ZEPhnPo3bgAJqZ4l0gvUqHLV+D7eMS0/bmxR3PkCjVjZmW80UKJnj2a4fXOX6AI1snWp8WfbGjgV7gmoNYeFdk47Ym4UQR8yXPaAq1sf4IZXJmQ4j1cFLGDQgsdTsUbZwcIJYwPt7BN0ecOWtscr9P1ohxlGsVnKw0k51qqRi0YmpRf6SWEqwkAw/lz32ukHQH4phguUBtw9+YV2C5y+fqSMByidLFeengInwncCos5cFUPOSqDzjxbG1Q+fUN9UQrGbCs9DZV5cTQoDxo9NA6cNJo6cTmVKx1kh2vC8aQSw6TuYiZJIltH84LchL5qQ81A1/fVR/CXy+UWH2uxbWVwtQ8ttH+eu9aCCOnPjewRAu0blimgixTZHl2mfHGoN3vybFT8krwZEPeqgYile095BVai9P/0R0P2aAvsHz1tc5e3IC0axPjav8eETZv89ohELfLdbWO289FsVvTxTCkpagBHE35651RAXyP2dhctKLnqfhsfvaerwTChgf2q7Xddlp9pfmZwNeAQ/19xp+cEwdlZEq4H+LU3oecsWY5J2ie0qdpbXnoL9z2xaNP/ZScdSHadlauc1Qo8xZnPy/kFQ6vdrqW0DlxmK5SZoGpBJCuMdMUMog3VX6/W0tgNkypS+FhD8sp1MfUDnU3mInSbfp3x4rJA5ftq44= 9 | - secure: HRwJQXRVUkGz90S7TF7TUsk1vVDMpecgmnF8V/DlBRQIgxuMrFMXk5HvlfPhy9BrJb83oviIVXtttC/Kkf2FWxPOa8ifHQDiLZC1Le74Cfl4lGiWCSj2E0NVxh64UiZjWNFy8JI+ZVH1NO34UNc67xbbcryKdIKffLlFzJKdfAbFnfOowsykJoRMPXGccyO6NipFXDy+pOsjjq61XvrsiQqW1Kfv5WcCPwIIKP0A0R0eFb1/7T6K2Fe+eeBotq3nxTMtNqzUeQzxyeeCusiO0OTzy+sS2eimFRUOdFXc39PQjqETM0ly5kj9IMzJuNuKJZq6IDOcUCInBduQXQhmkBeiThED5OSzdvbtWnPKxfTZ7069oG8M8LOUmmIODRdQWl9rvtJrKP0JodiVNzrJXeb2SG3bP5kANy7s9nEqmK3IzZtHDisFUcIqkDzL1e9lDYAbEyE/sVczDidD+gajyGPDHMC/K7aXABA15l0iCYStE0J6rZA14ztM9dNKIHjAhcFrS/sr/j6ol/pRWZahlq4uYKElDu3LhFRVMby4jaPjide1JUzZWsC8JjS6r8bcSXdiEL9ykcTbPYCCzz0g580YjpDy7R9H6/OLRYbAFlbwwiQvalZQ/nD5LLx7FTkzeCC209VVA839vXYm/xVyK/3vpxGxwQ/fy75SlYUH1CU= 10 | - secure: VfRcsDTQyazo/zd1dzPiTjwU+NwfAMrFsemZXi8igi68ZdDOVlWYwhReJ6bX6rH1S5uFNe9ozgpU7j5+OzIRfFd94CvvFm1vGuXaIwam3BIR4coDx9f0H2+bct7zVR0bdWgtbPpSkAa3WEv1ASh1K2kYysgytEthExrXCjwwU4o/Dz5UFNEoIbPkCNf9yzyoGjr6uJHkOuf2HrIj3KUPUog/M9Cxd8h2xOBrIX4XicW7fJ4BMlfJRh9Y7pzttbJMQ4ER7hX4lCNaENduMLUmupgnNKAkkLW8HNR1ee0HbXK5olzG+YZza/NS+1XkDB/cpSaIHcdUu/3Gyn7v2Ve2X3q8rcLOoiBMQaCYWf/kA1EIEzyyh3ACCI3VwJCpFPZ2E+dmckYncG3g13Xs/Ff8rE1yaMMwes+muUdS58IrEj000nhMARc4/jnG93+4V7a/MHBNi0NhJhZJP5cF04ZkenF18yZAQFMZbQwLQG1ScVX5bw3mgmQzwDkLjWA4W5BBck704l1eyTAfkrYWbkqo7pgu1a+UumZnUMkka1nBgdPUxNT+ZZErlzbWwXOg9QUdEJGJAA3pLGOPCRZfE2pI6TBhC2Ls6we2Z9sKwrnW9uUN51r8Ahcc9I1696LLTrKZiUBWxaPthWfSUh06Il+EGvRl+W2JweiXRjETwbCAlm4= 11 | 12 | before_cache: 13 | - rm -fr $HOME/.gradle/caches/modules-2/modules-2.lock 14 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 15 | 16 | cache: 17 | directories: 18 | - $HOME/.gradle/caches/ 19 | - $HOME/.gradle/wrapper/ 20 | 21 | before_install: 22 | - openssl aes-256-cbc -K $encrypted_55795232bcfe_key -iv $encrypted_55795232bcfe_iv 23 | -in orgs_ignored.yaml.enc -out orgs_ignored.yaml -d 24 | 25 | install: skip 26 | 27 | script: 28 | - "./gradlew build --warning-mode=all" 29 | -------------------------------------------------------------------------------- /src/main/java/wonky/api/ApiController.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2019 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package wonky.api; 17 | 18 | import io.micronaut.http.HttpResponse; 19 | import io.micronaut.http.HttpResponseFactory; 20 | import io.micronaut.http.annotation.Body; 21 | import io.micronaut.http.annotation.Controller; 22 | import io.micronaut.http.annotation.Get; 23 | import io.micronaut.http.annotation.Header; 24 | import io.micronaut.http.annotation.Post; 25 | import io.micronaut.http.annotation.QueryValue; 26 | import io.micronaut.security.annotation.Secured; 27 | import io.micronaut.security.rules.SecurityRule; 28 | import lombok.extern.slf4j.Slf4j; 29 | import wonky.model.Organization; 30 | import wonky.service.SlackService; 31 | 32 | import java.util.Optional; 33 | 34 | import static io.micronaut.http.HttpHeaders.ACCEPT_LANGUAGE; 35 | import static io.micronaut.http.HttpHeaders.HOST; 36 | 37 | /** 38 | * Created by domix on 01/06/18. 39 | */ 40 | @Slf4j 41 | @Controller("/v1") 42 | @Secured(SecurityRule.IS_ANONYMOUS) 43 | public class ApiController { 44 | private SlackService slackService; 45 | 46 | public ApiController(SlackService slackService) { 47 | this.slackService = slackService; 48 | } 49 | 50 | @Get("/organizations/_self") 51 | public HttpResponse index(@Header(HOST) String hostname, @Header(value = ACCEPT_LANGUAGE, defaultValue = "en") String language) { 52 | String locale = locale(language); 53 | 54 | return HttpResponseFactory.INSTANCE.ok(getOrganizationByDomain(hostname)); 55 | } 56 | 57 | private String locale(@Header(value = ACCEPT_LANGUAGE, defaultValue = "en") String language) { 58 | return Optional.ofNullable(language).orElse("en"); 59 | } 60 | 61 | @Get("/organizations/{hostname}") 62 | public Organization forDomain(@QueryValue("hostname") String hostname, @Header(value = ACCEPT_LANGUAGE, defaultValue = "en") String language) { 63 | return getOrganizationByDomain(hostname); 64 | } 65 | 66 | @Post("/invites") 67 | public String invite(@Header(HOST) String hostname, @Body Invite invite, @Header(value = ACCEPT_LANGUAGE, defaultValue = "en") String language) { 68 | return slackService.invite(hostname, invite); 69 | } 70 | 71 | private Organization getOrganizationByDomain(String hostname) { 72 | log.info("Looking for [{}]", hostname); 73 | 74 | return slackService.get(hostname); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /src/main/java/wonky/http/SlackClient.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2019 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package wonky.http; 17 | 18 | import io.micronaut.http.HttpRequest; 19 | import io.micronaut.http.client.HttpClient; 20 | import io.micronaut.http.client.annotation.Client; 21 | import jakarta.inject.Singleton; 22 | import wonky.json.JacksonUtil; 23 | import wonky.service.SlackOrganization; 24 | import wonky.slack.Team; 25 | 26 | import java.io.UnsupportedEncodingException; 27 | import java.net.URLEncoder; 28 | import java.util.Map; 29 | 30 | import static io.micronaut.http.HttpHeaders.CONTENT_TYPE; 31 | import static io.micronaut.http.HttpRequest.GET; 32 | import static io.micronaut.http.HttpRequest.POST; 33 | import static io.micronaut.http.MediaType.APPLICATION_FORM_URLENCODED; 34 | import static java.lang.String.format; 35 | 36 | @Singleton 37 | public class SlackClient { 38 | 39 | private final HttpClient httpClient; 40 | private final JacksonUtil jacksonUtil; 41 | 42 | public SlackClient(@Client("https://slack.com") HttpClient httpClient, JacksonUtil jacksonUtil) { 43 | this.httpClient = httpClient; 44 | this.jacksonUtil = jacksonUtil; 45 | } 46 | 47 | public Team fetchTeamInfo(String token) { 48 | String uri = format("/api/team.info?token=%s", token); 49 | HttpRequest req = GET(uri); 50 | 51 | String retrieve = httpClient.toBlocking().retrieve(req); 52 | 53 | return getTeamFromSlackResponse(retrieve); 54 | } 55 | 56 | public String invite(SlackOrganization tenant, String email) { 57 | 58 | String uri = format("/api/users.admin.invite?token=%s", tenant.getToken()); 59 | String encodedEmail; 60 | 61 | try { 62 | encodedEmail = URLEncoder.encode(email, "UTF-8"); 63 | } catch (UnsupportedEncodingException e) { 64 | throw new RuntimeException("chaz"); 65 | } 66 | 67 | String payload = String.format("email=%s", encodedEmail); 68 | 69 | HttpRequest req = POST(uri, payload) 70 | .header(CONTENT_TYPE, APPLICATION_FORM_URLENCODED); 71 | 72 | return httpClient.toBlocking().exchange(req, String.class).body(); 73 | } 74 | 75 | private Team getTeamFromSlackResponse(String response) { 76 | validateSlackError(response); 77 | return jacksonUtil.readValue(response, "team", Team.class); 78 | } 79 | 80 | private void validateSlackError(String body) { 81 | final var map = jacksonUtil.readValue(body, Map.class); 82 | Object ok = map.get("ok"); 83 | 84 | boolean result = Boolean.parseBoolean(ok.toString()); 85 | if (!result) { 86 | String error = map.getOrDefault("error", "Unknown").toString(); 87 | throw new SlackResponseException(error, body); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wonky 2 | [![Build Status](https://travis-ci.org/domix/wonky.svg)](https://travis-ci.org/domix/wonky) 3 | [![codecov.io](http://codecov.io/github/domix/wonky/coverage.svg?branch=master)](http://codecov.io/github/domix/wonky?branch=master) 4 | 5 | 6 | Wonky is a port of [slacking](https://github.com/rauchg/slackin/), to the JVM written in `Java` and [Micronaut](http://micronaut.io/). 7 | 8 | ## Features 9 | 10 | - A landing page you can point users to fill in their emails and receive an invite (`http://slack.yourdomain.com`) 11 | 12 | ## Build 13 | 14 | Docker Engine 15 | 16 | ### Requirements 17 | 18 | [!["JDK"](https://img.shields.io/badge/JDK-8.0+-F30000.svg?style=flat)](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) 19 | 20 | ### Slack token 21 | 22 | To build & run wonky you need a `Slack API token`. Note that the user you use to generate the token must be an admin. You may want to create a dedicated @wonky-inviter user (or similar) for this. 23 | 24 | You can find your API token [here](http://api.slack.com/web) 25 | 26 | Once you have the token, you need to write the `configuration file`. 27 | 28 | #### Configuration file 29 | 30 | The configuration file is very simple to write, it's a `YAML`. 31 | 32 | ##### Single Slack organization 33 | 34 | ```YAML 35 | - !!wonky.service.SlackOrganization 36 | token: "xoxp-..." 37 | wonkyDomain: "localhost:8080" 38 | ``` 39 | 40 | *NOTE:* Wonky supports multiple organizations (aka multitenancy), Wonky will use the domain (HOST http header) to select the right token. Consider this. 41 | 42 | ##### Multiple Slack organizations 43 | 44 | ```YAML 45 | - !!wonky.service.SlackOrganization 46 | token: "xoxp-..." 47 | wonkyDomain: "localhost:8080" 48 | - !!wonky.service.SlackOrganization 49 | token: "xoxp-..." 50 | wonkyDomain: "slack.myorganization.com" 51 | ``` 52 | 53 | ##### Recomendation 54 | 55 | We strongly recommend you write and name the config file as `orgs_ignored.yaml` and save it to the root source of wonky, in git is marked as ignored. 56 | 57 | #### Environment variables 58 | 59 | In order to run properly the test, you have to provide the following `Environment Variables`; 60 | 61 | * WONKY_TENANTS_FILE 62 | * WONKY_TEST_EMAIL_PREFIX 63 | 64 | You can configued as follows in the shell: 65 | 66 | ````bash 67 | $ export WONKY_TENANTS_FILE=./orgs_ignored.yaml 68 | $ export WONKY_TEST_EMAIL_PREFIX=something 69 | 70 | ```` 71 | 72 | Now you can build wonky from source :) 73 | 74 | ## Building from source 75 | 76 | ```bash 77 | $ ./gradlew clean build 78 | ``` 79 | 80 | ## Run 81 | 82 | By default wonky runs on port `8080`, as any `Micronaut` application you can chance the port as you wish. 83 | 84 | 85 | ```bash 86 | $ ./gradlew run 87 | ``` 88 | 89 | Alternatively, you can run Wonky with Docker as a container: 90 | 91 | 92 | ```bash 93 | $ docker run --rm -p 8080:8080 -v `pwd`/orgs_ignored.yaml:/etc/wonky/tenants.yaml domix/wonky:0.3.7 94 | ``` 95 | 96 | ### Communities using Wonky 97 | 98 | - [The Data Pub](http://slack.thedata.pub) 99 | - [JavaMexico.org](http://slack.javamexico.org) 100 | - [SpringHispano.org](http://slack.springhispano.org) 101 | - [Groovyando.org](http://slack.groovyando.org) 102 | - [JavaHispano.org](http://slack.javahispano.org) 103 | 104 | ### Development badges 105 | 106 | ![codecov.io](http://codecov.io/github/domix/wonky/branch.svg?branch=master) 107 | -------------------------------------------------------------------------------- /src/test/groovy/wonky/api/ApiControllerSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright (C) 2014-2019 the original author or authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package wonky.api 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper 20 | import io.micronaut.context.ApplicationContext 21 | import io.micronaut.core.type.Argument 22 | import io.micronaut.http.HttpRequest 23 | import io.micronaut.http.HttpResponse 24 | import io.micronaut.http.client.HttpClient 25 | import io.micronaut.http.client.exceptions.HttpClientResponseException 26 | import io.micronaut.runtime.server.EmbeddedServer 27 | import org.apache.commons.lang3.RandomStringUtils 28 | import spock.lang.AutoCleanup 29 | import spock.lang.Shared 30 | import spock.lang.Specification 31 | import wonky.json.JacksonUtil 32 | import wonky.model.Organization 33 | 34 | import static io.micronaut.http.HttpRequest.GET 35 | import static io.micronaut.http.HttpRequest.POST 36 | import static io.micronaut.http.HttpStatus.* 37 | 38 | class ApiControllerSpec extends Specification { 39 | @Shared 40 | @AutoCleanup 41 | EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer) 42 | 43 | @Shared 44 | @AutoCleanup 45 | HttpClient client = embeddedServer.applicationContext.createBean(HttpClient, embeddedServer.getURL()) 46 | 47 | def "Getting the organization info"() { 48 | when: 49 | HttpRequest request = GET('/v1/organizations/_self').header("Host", "localhost") 50 | 51 | HttpResponse rsp = client.toBlocking().exchange(request, Argument.of(Organization)) 52 | client.toBlocking().exchange(request, Argument.of(Organization)) 53 | 54 | then: 'the endpoint can be accessed' 55 | rsp.status == OK 56 | rsp.body() 57 | 58 | when: 59 | def body = rsp.body() 60 | 61 | then: 62 | body.team.name 63 | } 64 | 65 | def "Fail when Getting the organization info"() { 66 | when: 67 | HttpRequest notFound = GET('/v1/organizations/notfound') 68 | 69 | client.toBlocking().exchange(notFound, Argument.of(Map)) 70 | 71 | then: 'the endpoint can be accessed' 72 | HttpClientResponseException notFoundException = thrown(HttpClientResponseException) 73 | NOT_FOUND == notFoundException.response.status 74 | def body = notFoundException.response.body() 75 | body.id 76 | body.entity 77 | body.message 78 | 79 | when: 80 | HttpRequest badToken = GET('/v1/organizations/badtoken') 81 | 82 | client.toBlocking().exchange(badToken, Argument.of(Map)) 83 | 84 | then: 'the endpoint can be accessed' 85 | HttpClientResponseException badTokenException = thrown(HttpClientResponseException) 86 | INTERNAL_SERVER_ERROR == badTokenException.response.status 87 | def body1 = badTokenException.response.body() 88 | body1.error 89 | body1.message 90 | body1.slackResponse 91 | } 92 | 93 | def "invite"() { 94 | when: 95 | def prefix = System.getenv("WONKY_TEST_EMAIL_PREFIX") 96 | assert prefix 97 | def alphanumeric = RandomStringUtils.randomAlphanumeric(10) 98 | Invite invite = new Invite(email: "${ prefix }+${ alphanumeric }@gmail.com") 99 | 100 | JacksonUtil jacksonUtil = new JacksonUtil(new ObjectMapper()) 101 | def jsonInvite = jacksonUtil.toJson(invite) 102 | 103 | HttpRequest notFound = POST('/v1/invites', jsonInvite).header("Host", "localhost") 104 | 105 | def exchange = client.toBlocking().exchange(notFound, Argument.of(Map)) 106 | 107 | then: 'the endpoint can be accessed' 108 | 109 | OK == exchange.status() 110 | 111 | } 112 | 113 | 114 | } 115 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## 1. Purpose 4 | 5 | A primary goal of Wonky is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof). 6 | 7 | This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior. 8 | 9 | We invite all those who participate in Wonky to help us create safe and positive experiences for everyone. 10 | 11 | ## 2. Open Source Citizenship 12 | 13 | A supplemental goal of this Code of Conduct is to increase open source citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community. 14 | 15 | Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society. 16 | 17 | If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know. 18 | 19 | ## 3. Expected Behavior 20 | 21 | The following behaviors are expected and requested of all community members: 22 | 23 | * Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community. 24 | * Exercise consideration and respect in your speech and actions. 25 | * Attempt collaboration before conflict. 26 | * Refrain from demeaning, discriminatory, or harassing behavior and speech. 27 | * Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential. 28 | * Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations. 29 | 30 | ## 4. Unacceptable Behavior 31 | 32 | The following behaviors are considered harassment and are unacceptable within our community: 33 | 34 | * Violence, threats of violence or violent language directed against another person. 35 | * Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language. 36 | * Posting or displaying sexually explicit or violent material. 37 | * Posting or threatening to post other people’s personally identifying information ("doxing"). 38 | * Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability. 39 | * Inappropriate photography or recording. 40 | * Inappropriate physical contact. You should have someone’s consent before touching them. 41 | * Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances. 42 | * Deliberate intimidation, stalking or following (online or in person). 43 | * Advocating for, or encouraging, any of the above behavior. 44 | * Sustained disruption of community events, including talks and presentations. 45 | 46 | ## 5. Consequences of Unacceptable Behavior 47 | 48 | Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated. 49 | 50 | Anyone asked to stop unacceptable behavior is expected to comply immediately. 51 | 52 | If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event). 53 | 54 | ## 6. Reporting Guidelines 55 | 56 | If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. domingo.suarez@gmail.com. 57 | 58 | 59 | 60 | Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress. 61 | 62 | ## 7. Addressing Grievances 63 | 64 | If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify Domix with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies. 65 | 66 | 67 | 68 | ## 8. Scope 69 | 70 | We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues–online and in-person–as well as in all one-on-one communications pertaining to community business. 71 | 72 | This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members. 73 | 74 | ## 9. Contact info 75 | 76 | domingo.suarez@gmail.com 77 | 78 | ## 10. License and attribution 79 | 80 | This Code of Conduct is distributed under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/). 81 | 82 | Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy). 83 | 84 | Retrieved on November 22, 2016 from [http://citizencodeofconduct.org/](http://citizencodeofconduct.org/) 85 | -------------------------------------------------------------------------------- /src/main/java/wonky/service/SlackService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2019 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package wonky.service; 17 | 18 | import io.micronaut.caffeine.cache.Cache; 19 | import io.micronaut.caffeine.cache.Caffeine; 20 | import io.micronaut.context.annotation.Context; 21 | import io.micronaut.context.annotation.Value; 22 | import lombok.extern.slf4j.Slf4j; 23 | import org.apache.commons.io.monitor.FileAlterationListener; 24 | import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; 25 | import org.apache.commons.io.monitor.FileAlterationMonitor; 26 | import org.apache.commons.io.monitor.FileAlterationObserver; 27 | import org.yaml.snakeyaml.Yaml; 28 | import wonky.api.Invite; 29 | import wonky.http.SlackClient; 30 | import wonky.http.SlackResponseException; 31 | import wonky.model.Organization; 32 | import wonky.slack.Team; 33 | 34 | import javax.annotation.PostConstruct; 35 | import java.io.File; 36 | import java.io.FileInputStream; 37 | import java.io.FileNotFoundException; 38 | import java.io.IOException; 39 | import java.io.InputStream; 40 | import java.util.List; 41 | import java.util.Optional; 42 | import java.util.concurrent.TimeUnit; 43 | 44 | import static java.lang.String.format; 45 | 46 | /** 47 | * Created by domix on 01/06/18. 48 | */ 49 | @Context 50 | @Slf4j 51 | public class SlackService { 52 | private final SlackClient slackClient; 53 | private final String tenantsFile; 54 | private final int pollInterval; 55 | private List orgs; 56 | private Cache cache; 57 | 58 | public SlackService( 59 | SlackClient slackClient, 60 | @Value("${wonky.tenants.file:/etc/wonky/tenants.yaml}") final String tenantsFile, 61 | @Value("${wonky.tenants.file.pollinterval:100}") final int pollInterval 62 | ) { 63 | this.slackClient = slackClient; 64 | this.tenantsFile = tenantsFile; 65 | this.pollInterval = pollInterval; 66 | } 67 | 68 | @PostConstruct 69 | public void init() { 70 | log.info("Tenant file {}", tenantsFile); 71 | File file = new File(tenantsFile); 72 | String tenantsFileDirectory = file.getParentFile().getAbsolutePath(); 73 | log.info("Watching changes in [{}]", tenantsFileDirectory); 74 | log.info("Poll interval [{}]", pollInterval); 75 | FileAlterationObserver observer = new FileAlterationObserver(tenantsFileDirectory); 76 | FileAlterationMonitor monitor = new FileAlterationMonitor(pollInterval); 77 | 78 | load(); 79 | 80 | FileAlterationListener listener = new FileAlterationListenerAdaptor() { 81 | @Override 82 | public void onFileChange(File file) { 83 | log.debug("onFileChange"); 84 | log.info("Reloading file [{}]", file.getAbsoluteFile().getName()); 85 | //TODO: verify that the file changed is the configured file 86 | load(); 87 | } 88 | }; 89 | observer.addListener(listener); 90 | monitor.addObserver(observer); 91 | try { 92 | monitor.start(); 93 | } catch (Exception e) { 94 | throw new IllegalStateException(e.getMessage(), e); 95 | } 96 | 97 | cache = Caffeine.newBuilder() 98 | .expireAfterWrite(24, TimeUnit.HOURS) 99 | .maximumSize(100) 100 | .build(); 101 | } 102 | 103 | public void loadAllOrgs() { 104 | orgs.parallelStream() 105 | .forEach(this::handleErrorFromSlack); 106 | } 107 | 108 | private void handleErrorFromSlack(SlackOrganization slackOrganization) { 109 | try { 110 | this.get(slackOrganization.getWonkyDomain()); 111 | } catch (SlackResponseException ex) { 112 | log.warn(ex.getMessage(), ex); 113 | } 114 | } 115 | 116 | public void load() { 117 | log.info("Loading [{}]", tenantsFile); 118 | Yaml yaml = new Yaml(); 119 | 120 | try { 121 | File file = new File(tenantsFile); 122 | InputStream ios = new FileInputStream(file); 123 | orgs = yaml.load(ios); 124 | 125 | ios.close(); 126 | } catch (FileNotFoundException e) { 127 | log.error(format("Can't load tenants file. '%s'", tenantsFile), e); 128 | throw new IllegalStateException(format("Can't load tenants file. '%s'", tenantsFile), e); 129 | } catch (IOException e) { 130 | log.warn(e.getMessage(), e); 131 | } 132 | } 133 | 134 | public List getOrgs() { 135 | return orgs; 136 | } 137 | 138 | public Organization get(String hostname) { 139 | log.warn("Getting {}", hostname); 140 | 141 | return Optional.ofNullable(cache.getIfPresent(hostname)) 142 | .map(organization -> { 143 | log.info("Cached Data"); 144 | return organization; 145 | }) 146 | .orElseGet(() -> findTenant(hostname) 147 | .map(slackOrganization -> { 148 | Team team = tenantSlackInformation(slackOrganization.getToken()); 149 | 150 | Organization organization = new Organization(); 151 | organization.setTeam(team); 152 | log.info("Saving in cache: {}...", organization.getTeam().getName()); 153 | cache.put(hostname, organization); 154 | return organization; 155 | 156 | } 157 | ) 158 | .orElseThrow(() -> throwSlackOrganizationNotFoundException(hostname))); 159 | } 160 | 161 | public Optional findTenant(String hostname) { 162 | return 163 | orgs.stream() 164 | .filter(slackOrganization -> slackOrganization.getWonkyDomain().equals(hostname)) 165 | .findFirst(); 166 | } 167 | 168 | public Team tenantSlackInformation(String token) { 169 | return this.slackClient.fetchTeamInfo(token); 170 | } 171 | 172 | public String invite(String hostname, Invite invite) { 173 | SlackOrganization tenant = findTenant(hostname) 174 | .orElseThrow(() -> throwSlackOrganizationNotFoundException(hostname)); 175 | 176 | return slackClient.invite(tenant, invite.getEmail()); 177 | } 178 | 179 | private EntityNotFoundException throwSlackOrganizationNotFoundException(String hostname) { 180 | log.warn("No config found for {}", hostname); 181 | return new EntityNotFoundException("Slack Organization", hostname); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/main/resources/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Wonky - Slack made easy for organizations! 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 79 | 80 |

81 |
82 | 86 |
87 | Fork me on GitHub 88 | 89 |
90 |
91 |

{{organizationName}}

92 |
93 |
94 |
95 | 96 |
97 |
98 | 99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | email 108 | 109 | 110 | 111 |
112 | 113 |
114 |
115 | 116 | 125 | 144 |
145 |
146 |
147 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command; 206 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 207 | # shell script including quotes and variable substitutions, so put them in 208 | # double quotes to make sure that they get re-expanded; and 209 | # * put everything else in single quotes, so that it's not re-expanded. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | --------------------------------------------------------------------------------