Skip to content

Commit

Permalink
feat: api publishing
Browse files Browse the repository at this point in the history
  • Loading branch information
kashike committed Jan 19, 2024
1 parent 5362e49 commit 1e71050
Show file tree
Hide file tree
Showing 9 changed files with 325 additions and 4 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ dependencies {
implementation(libs.jetbrains.annotations)
implementation(libs.springdoc.openapi.starter.webmvc.ui)
implementation("org.springframework.boot", "spring-boot-starter-data-mongodb")
implementation("org.springframework.boot", "spring-boot-starter-security")
implementation("org.springframework.boot", "spring-boot-starter-validation")
implementation("org.springframework.boot", "spring-boot-starter-web")
testImplementation("org.springframework.boot", "spring-boot-starter-test") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* This file is part of bibliothek, licensed under the MIT License.
*
* Copyright (c) 2019-2024 PaperMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.papermc.bibliothek.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
SecurityFilterChain securityFilterChain(final HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(requests -> {
requests
.requestMatchers(HttpMethod.POST, "/v2/projects/*/versions/*/builds")
.hasAnyRole("CREATE_BUILD")
.anyRequest()
.permitAll();
})
.csrf(AbstractHttpConfigurer::disable)
.httpBasic(configurer -> configurer.realmName("bibliothek"));
return http.build();
}

@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* This file is part of bibliothek, licensed under the MIT License.
*
* Copyright (c) 2019-2024 PaperMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.papermc.bibliothek.controller.v2;

import io.papermc.bibliothek.database.model.Build;
import io.papermc.bibliothek.database.model.Project;
import io.papermc.bibliothek.database.model.Version;
import io.papermc.bibliothek.database.model.VersionFamily;
import io.papermc.bibliothek.database.repository.BuildCollection;
import io.papermc.bibliothek.database.repository.ProjectCollection;
import io.papermc.bibliothek.database.repository.VersionCollection;
import io.papermc.bibliothek.database.repository.VersionFamilyCollection;
import io.papermc.bibliothek.exception.ProjectNotFound;
import io.swagger.v3.oas.annotations.Hidden;
import java.net.URI;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;

@RestController
@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
@SuppressWarnings("checkstyle:FinalClass")
public class PublishController {
private final ProjectCollection projects;
private final VersionFamilyCollection families;
private final VersionCollection versions;
private final BuildCollection builds;

@Autowired
private PublishController(
final ProjectCollection projects,
final VersionFamilyCollection families,
final VersionCollection versions,
final BuildCollection builds
) {
this.projects = projects;
this.families = families;
this.versions = versions;
this.builds = builds;
}

@Hidden
@PostMapping(
path = "/v2/projects/{project:[a-z]+}/versions/{version:" + Version.PATTERN + "}/builds",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity<?> createBuild(@RequestBody final PublishRequest request) {
final Project project = this.projects.findByName(request.project()).orElseThrow(ProjectNotFound::new);
final VersionFamily family = this.families.findByProjectAndName(project._id(), request.family())
.orElseGet(() -> this.families.save(new VersionFamily(null, project._id(), request.family(), request.familyTime())));
final Version version = this.versions.findByProjectAndName(project._id(), request.version())
.orElseGet(() -> this.versions.save(new Version(null, project._id(), family._id(), request.version(), request.versionTime())));
final Build build = this.builds.insert(new Build(
null,
project._id(),
version._id(),
request.build(),
request.time(),
request.changes(),
request.downloads(),
request.channel(),
false
));
return ResponseEntity.created(URI.create(
MvcUriComponentsBuilder
.fromMappingName("project.version.build")
.arg(0, project.name())
.arg(1, version.name())
.arg(2, build.number())
.build()
)).body(new PublishResponse(true, build));
}

private record PublishRequest(
String project,
String family,
@Nullable Instant familyTime,
String version,
@Nullable Instant versionTime,
int build,
Instant time,
Build.Channel channel,
List<Build.Change> changes,
Map<String, Build.Download> downloads
) {
}

private record PublishResponse(
boolean success,
Build build
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ private VersionBuildController(
),
responseCode = "200"
)
@GetMapping("/v2/projects/{project:[a-z]+}/versions/{version:" + Version.PATTERN + "}/builds/{build:\\d+}")
@GetMapping(
name = "project.version.build",
path = "/v2/projects/{project:[a-z]+}/versions/{version:" + Version.PATTERN + "}/builds/{build:\\d+}"
)
@Operation(summary = "Gets information related to a specific build.")
public ResponseEntity<?> build(
@Parameter(name = "project", description = "The project identifier.", example = "paper")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/
package io.papermc.bibliothek.database.model;

import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
Expand Down Expand Up @@ -54,16 +55,19 @@ public record Build(
@Nullable Boolean promoted
) {
public Channel channelOrDefault() {
return Objects.requireNonNullElse(this.channel(), Build.Channel.DEFAULT);
return Objects.requireNonNullElse(this.channel(), Channel.DEFAULT);
}

public boolean promotedOrDefault() {
return Objects.requireNonNullElse(this.promoted(), false);
}

// These annotations are in reverse, but we can't fix this in v2.
public enum Channel {
@JsonAlias("DEFAULT")
@JsonProperty("default")
DEFAULT,
@JsonAlias("EXPERIMENTAL")
@JsonProperty("experimental")
EXPERIMENTAL;
}
Expand Down
38 changes: 38 additions & 0 deletions src/main/java/io/papermc/bibliothek/database/model/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* This file is part of bibliothek, licensed under the MIT License.
*
* Copyright (c) 2019-2024 PaperMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.papermc.bibliothek.database.model;

import java.util.Set;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "users")
public record User(
@Id ObjectId _id,
String username,
String password,
Set<String> authorities
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* This file is part of bibliothek, licensed under the MIT License.
*
* Copyright (c) 2019-2024 PaperMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.papermc.bibliothek.database.repository;

import io.papermc.bibliothek.database.model.User;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends MongoRepository<User, ObjectId> {
User findByUsername(final String username);
}
3 changes: 1 addition & 2 deletions src/main/java/io/papermc/bibliothek/filter/CorsFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,11 @@

@WebFilter("/*")
public class CorsFilter implements Filter {

@Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET");
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
chain.doFilter(request, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* This file is part of bibliothek, licensed under the MIT License.
*
* Copyright (c) 2019-2024 PaperMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.papermc.bibliothek.service;

import io.papermc.bibliothek.database.model.User;
import io.papermc.bibliothek.database.repository.UserRepository;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository users;

@Autowired
public UserDetailsServiceImpl(final UserRepository users) {
this.users = users;
}

@Override
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
final User user = this.users.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(username);
}
return new org.springframework.security.core.userdetails.User(
user.username(),
user.password(),
user.authorities()
.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet())
);
}
}

0 comments on commit 1e71050

Please sign in to comment.