diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000000..38df13ff11 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,5 @@ +# Examples + +This repo contains examples of Epiphany configurations and how-to examples for setting up applications to run on Epiphany. + +For the full story, go to [Epiphany documentation](https://github.com/epiphany-platform/docs/README.md). diff --git a/examples/dotnet/Epiphany.SampleApps/.gitignore b/examples/dotnet/Epiphany.SampleApps/.gitignore new file mode 100644 index 0000000000..55b14022aa --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/.gitignore @@ -0,0 +1,330 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ \ No newline at end of file diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/.dockerignore b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/.dockerignore new file mode 100644 index 0000000000..2c5959ec5f --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/.dockerignore @@ -0,0 +1,4 @@ +# Sample contents of .dockerignore file +bin/ +obj/ +node_modules/ diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Controllers/AuthController.cs b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Controllers/AuthController.cs new file mode 100644 index 0000000000..7febb94a6d --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Controllers/AuthController.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Epiphany.SampleApps.AuthService.Extensions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.IdentityModel.Clients.ActiveDirectory; + +namespace Epiphany.SampleApps.AuthService.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class AuthController : ControllerBase + { + private readonly CachedGraphClientTokenProvider _tokenProvider; + public AuthController(CachedGraphClientTokenProvider tokenProvider) + { + _tokenProvider = tokenProvider; + } + // POST: api/Auth + [HttpPost] + public async Task AcquireToken(AzureAdConfig config) + { + + var result = await _tokenProvider.GetToken(config); + + return Ok(result.AccessToken); + } + + } +} diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Dockerfile b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Dockerfile new file mode 100644 index 0000000000..b7226e0b71 --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Dockerfile @@ -0,0 +1,17 @@ +# Stage 1 +FROM microsoft/dotnet:2.1-sdk AS builder +WORKDIR /source + +# caches restore result by copying csproj file separately +COPY *.csproj . +RUN dotnet restore + +# copies the rest of your code +COPY . . +RUN dotnet publish --output /app/ --configuration Release + +# Stage 2 +FROM microsoft/dotnet:2.1-aspnetcore-runtime +WORKDIR /app +COPY --from=builder /app . +ENTRYPOINT ["dotnet", "Epiphany.SampleApps.AuthService.dll"] diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Epiphany.SampleApps.AuthService.csproj b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Epiphany.SampleApps.AuthService.csproj new file mode 100644 index 0000000000..19ca7f1a0a --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Epiphany.SampleApps.AuthService.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.1 + aspnet-Epiphany.SampleApps.AuthService-61246923-48AF-484E-A537-683350BBA27D + 0 + + + + + + + + + + + + + + diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Extensions/AzureAdConfig.cs b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Extensions/AzureAdConfig.cs new file mode 100644 index 0000000000..a973cb266c --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Extensions/AzureAdConfig.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Epiphany.SampleApps.AuthService.Extensions +{ + public class AzureAdConfig + { + public string TenantId { get; set; } + public string ClientId { get; set; } + public string Resource { get; set; } + public string ClientSecret { get; set; } + } +} diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Extensions/CachedGraphClientTokenProvider.cs b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Extensions/CachedGraphClientTokenProvider.cs new file mode 100644 index 0000000000..a21e2285aa --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Extensions/CachedGraphClientTokenProvider.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.IdentityModel.Clients.ActiveDirectory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Epiphany.SampleApps.AuthService.Extensions +{ + public class CachedGraphClientTokenProvider + { + private readonly IConfiguration _configuration; + public CachedGraphClientTokenProvider(IConfiguration configuration) + { + _configuration = configuration; + } + internal async Task GetToken(AzureAdConfig config) + { + ClientCredential clientCredential = new ClientCredential(config.ClientId, config.ClientSecret); + AuthenticationContext ctx = new AuthenticationContext(_configuration["AzureAd:Instance"] + config.TenantId); + + AuthenticationResult result = null; + try + { + result = await ctx.AcquireTokenAsync(config.Resource, clientCredential); + } + catch (AdalSilentTokenAcquisitionException exc) + { + throw exc; + } + catch (AdalException exc) + { + throw exc; + } + return result; + + } + } +} diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Program.cs b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Program.cs new file mode 100644 index 0000000000..54755cbace --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Program.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Epiphany.SampleApps.AuthService +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Startup.cs b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Startup.cs new file mode 100644 index 0000000000..543e3ecdc8 --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/Startup.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.AzureAD.UI; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Epiphany.SampleApps.AuthService.Extensions; + +namespace Epiphany.SampleApps.AuthService +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services + .AddAuthentication(AzureADDefaults.BearerAuthenticationScheme) + .AddAzureADBearer(options => Configuration.Bind("AzureAd", options)); + + services.AddTransient(); + + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseAuthentication(); + app.UseStatusCodePages(); + + app.UseMvc(); + } + } +} diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/appsettings.Development.json b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/appsettings.Development.json new file mode 100644 index 0000000000..e203e9407e --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/appsettings.json b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/appsettings.json new file mode 100644 index 0000000000..f9b07cdd64 --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.AuthService/appsettings.json @@ -0,0 +1,12 @@ +{ + "AzureAd": { + "Instance": "https://login.microsoftonline.com/", + "Domain": "domain.onmicrosoft.com" + }, + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.CronApp/Dockerfile b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.CronApp/Dockerfile new file mode 100644 index 0000000000..0eb7a84eda --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.CronApp/Dockerfile @@ -0,0 +1,17 @@ +# Stage 1 +FROM microsoft/aspnetcore-build AS builder +WORKDIR /source + +# caches restore result by copying csproj file separately +COPY *.csproj . +RUN dotnet restore + +# copies the rest of your code +COPY . . +RUN dotnet publish --output /app/ --configuration Release + +# Stage 2 +FROM microsoft/aspnetcore +WORKDIR /app +COPY --from=builder /app . +ENTRYPOINT ["dotnet", "Epiphany.SampleApps.CronApp.dll"] diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.CronApp/Epiphany.SampleApps.CronApp.csproj b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.CronApp/Epiphany.SampleApps.CronApp.csproj new file mode 100644 index 0000000000..ce1697ae88 --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.CronApp/Epiphany.SampleApps.CronApp.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp2.0 + + + diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.CronApp/Program.cs b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.CronApp/Program.cs new file mode 100644 index 0000000000..777ee54726 --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.CronApp/Program.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading; + +namespace Epiphany.SampleApps.CronApp +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("CRON JOB EXECUTED!"); + Thread.Sleep(10000); + Console.WriteLine("CRON JOB FINISHED!"); + } + } +} diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.CronApp/cronjob.yaml b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.CronApp/cronjob.yaml new file mode 100644 index 0000000000..b5168d9a51 --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.CronApp/cronjob.yaml @@ -0,0 +1,16 @@ +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + name: sample-cron-job +spec: + schedule: "*/1 * * * *" + jobTemplate: + spec: + template: + spec: + containers: + - name: sample-cron-job + image: myregistry.azurecr.io/samples/sample-cron-app:v1 + restartPolicy: OnFailure + imagePullSecrets: + - name: mysecret diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.Consumer/Dockerfile b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.Consumer/Dockerfile new file mode 100644 index 0000000000..387974dd41 --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.Consumer/Dockerfile @@ -0,0 +1,20 @@ +FROM microsoft/aspnetcore AS base +WORKDIR /app +EXPOSE 80 + +FROM microsoft/aspnetcore-build:2.0 AS build +WORKDIR /src +COPY epiphany-web-app.sln ./ +COPY epiphany-web-app/epiphany-web-app.csproj epiphany-web-app/ +RUN dotnet restore -nowarn:msb3202,nu1503 +COPY . . +WORKDIR /src/epiphany-web-app +RUN dotnet build -c Release -o /app + +FROM build AS publish +RUN dotnet publish -c Release -o /app + +FROM base AS final +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "epiphany-web-app.dll"] diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.Consumer/Epiphany.SampleApps.Kafka.Consumer.csproj b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.Consumer/Epiphany.SampleApps.Kafka.Consumer.csproj new file mode 100644 index 0000000000..913da82d28 --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.Consumer/Epiphany.SampleApps.Kafka.Consumer.csproj @@ -0,0 +1,13 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.Consumer/Program.cs b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.Consumer/Program.cs new file mode 100644 index 0000000000..5c9f4fee3f --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.Consumer/Program.cs @@ -0,0 +1,48 @@ +using Confluent.Kafka; +using Confluent.Kafka.Serialization; +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Epiphany.KafkaConsumer +{ + class Program + { + static void Main(string[] args) + { + IConfiguration config = new ConfigurationBuilder() + .AddEnvironmentVariables() + .Build(); + + var conf = new Dictionary + { + { "group.id", config["KAFKA_GROUP_ID"] }, + { "bootstrap.servers", config["KAFKA_BOOTSTRAP_SERVERS"]}, + { "auto.commit.interval.ms", config["KAFKA_AUTO_COMMIT_INVETRVAL_MS"] }, + { "auto.offset.reset", config["KAFKA_AUTO_OFFSET_RESET"]}, + { "session.timeout.ms", config["KAFKA_SESSION_TIMEOUT_MS"]}, + }; + + + using (var consumer = new Consumer(conf, new StringDeserializer(Encoding.UTF8), new StringDeserializer(Encoding.UTF8))) + { + consumer.OnMessage += (_, msg) + => Console.WriteLine($"Read '{msg.Value}' from: {msg.TopicPartitionOffset}"); + + consumer.OnError += (_, error) + => Console.WriteLine($"Error: {error}"); + + consumer.OnConsumeError += (_, msg) + => Console.WriteLine($"Consume error ({msg.TopicPartitionOffset}): {msg.Error}"); + + consumer.Subscribe("foo"); + + while (true) + { + consumer.Poll(TimeSpan.FromMilliseconds(100)); + } + } + } + } +} diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.Producer/Dockerfile b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.Producer/Dockerfile new file mode 100644 index 0000000000..387974dd41 --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.Producer/Dockerfile @@ -0,0 +1,20 @@ +FROM microsoft/aspnetcore AS base +WORKDIR /app +EXPOSE 80 + +FROM microsoft/aspnetcore-build:2.0 AS build +WORKDIR /src +COPY epiphany-web-app.sln ./ +COPY epiphany-web-app/epiphany-web-app.csproj epiphany-web-app/ +RUN dotnet restore -nowarn:msb3202,nu1503 +COPY . . +WORKDIR /src/epiphany-web-app +RUN dotnet build -c Release -o /app + +FROM build AS publish +RUN dotnet publish -c Release -o /app + +FROM base AS final +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "epiphany-web-app.dll"] diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.Producer/Epiphany.SampleApps.Kafka.Producer.csproj b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.Producer/Epiphany.SampleApps.Kafka.Producer.csproj new file mode 100644 index 0000000000..bf3ba773ec --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.Producer/Epiphany.SampleApps.Kafka.Producer.csproj @@ -0,0 +1,13 @@ + + + + Exe + netcoreapp2.1 + + + + + + + + diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.Producer/Program.cs b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.Producer/Program.cs new file mode 100644 index 0000000000..61eb57b179 --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.Producer/Program.cs @@ -0,0 +1,49 @@ +using Confluent.Kafka; +using Confluent.Kafka.Serialization; +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; + +namespace Epiphany.KafkaProducer +{ + class Program + { + static void Main(string[] args) + { + IConfiguration config = new ConfigurationBuilder() + .AddEnvironmentVariables() + .Build(); + + var conf = new Dictionary + { + { "group.id", config["KAFKA_GROUP_ID"] }, + { "bootstrap.servers", config["KAFKA_BOOTSTRAP_SERVERS"]}, + { "auto.commit.interval.ms", config["KAFKA_AUTO_COMMIT_INVETRVAL_MS"] }, + { "auto.offset.reset", config["KAFKA_AUTO_OFFSET_RESET"]}, + {"session.timeout.ms", config["KAFKA_SESSION_TIMEOUT_MS"]}, + }; + + using (var producer = new Producer(conf, null, new StringSerializer(Encoding.UTF8))) + { + for (int i = 0; i < 100; i++) + { + var dr = producer.ProduceAsync("foo", null, RandomString(10)).Result; + Console.WriteLine($"Delivered '{dr.Value}' to: {dr.TopicPartitionOffset}"); + } + } + + } + + private static Random random = new Random(); + public static string RandomString(int length) + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + return new string(Enumerable.Repeat(chars, length) + .Select(s => s[random.Next(s.Length)]).ToArray()); + } + } +} diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/.dockerignore b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/.dockerignore new file mode 100644 index 0000000000..2c5959ec5f --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/.dockerignore @@ -0,0 +1,4 @@ +# Sample contents of .dockerignore file +bin/ +obj/ +node_modules/ diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/Controllers/KafkaController.cs b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/Controllers/KafkaController.cs new file mode 100644 index 0000000000..2ac15eaad8 --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/Controllers/KafkaController.cs @@ -0,0 +1,51 @@ +using Confluent.Kafka; +using Confluent.Kafka.Serialization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Epiphany.Kafka.WebProducer.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class KafkaController : ControllerBase + { + private readonly Producer _producer; + private readonly IConfiguration _configuration; + + public KafkaController(IConfiguration configuration) + { + _configuration = configuration; + var conf = new Dictionary + { + { "group.id", _configuration["KAFKA_GROUP_ID"] }, + { "bootstrap.servers", _configuration["KAFKA_BOOTSTRAP_SERVERS"]}, + { "auto.commit.interval.ms", _configuration["KAFKA_AUTO_COMMIT_INVETRVAL_MS"] }, + { "auto.offset.reset", _configuration["KAFKA_AUTO_OFFSET_RESET"]}, + {"session.timeout.ms", _configuration["KAFKA_SESSION_TIMEOUT_MS"]}, + }; + + _producer = new Producer(conf, null, new StringSerializer(Encoding.UTF8)); + //{ + // for (int i = 0; i < 100; i++) + // { + // var dr = producer.ProduceAsync("foo", null, RandomString(10)).Result; + // Console.WriteLine($"Delivered '{dr.Value}' to: {dr.TopicPartitionOffset}"); + // } + //} + } + + [HttpPost] + public async void Post([FromBody]KafkaMessageModel model) + { + await _producer.ProduceAsync(model.Topic, null, model.Message); + //var dr = producer.ProduceAsync("foo", null, RandomString(10)).Result; + //Console.WriteLine($"Delivered '{dr.Value}' to: {dr.TopicPartitionOffset}"); + } + } +} diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/Dockerfile b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/Dockerfile new file mode 100644 index 0000000000..3e80c175bf --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/Dockerfile @@ -0,0 +1,17 @@ +# Stage 1 +FROM microsoft/dotnet:2.1-sdk AS builder +WORKDIR /source + +# caches restore result by copying csproj file separately +COPY *.csproj . +RUN dotnet restore + +# copies the rest of your code +COPY . . +RUN dotnet publish --output /app/ --configuration Release + +# Stage 2 +FROM microsoft/dotnet:2.1-aspnetcore-runtime +WORKDIR /app +COPY --from=builder /app . +ENTRYPOINT ["dotnet", "Epiphany.SampleApps.Kafka.WebProducer.dll"] diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/Epiphany.SampleApps.Kafka.WebProducer.csproj b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/Epiphany.SampleApps.Kafka.WebProducer.csproj new file mode 100644 index 0000000000..99c42db7d9 --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/Epiphany.SampleApps.Kafka.WebProducer.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp2.1 + 5db522b1-f77b-4cd2-bfd0-947e262849d7 + + + + + + + + + + + + diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/KafkaMessageModel.cs b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/KafkaMessageModel.cs new file mode 100644 index 0000000000..8ec03fd59d --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/KafkaMessageModel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Epiphany.Kafka.WebProducer +{ + public class KafkaMessageModel + { + [Required] + public string Topic { get; set; } + [Required] + public string Message { get; set; } + } +} diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/Program.cs b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/Program.cs new file mode 100644 index 0000000000..6b83bdec1c --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/Program.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Epiphany.Kafka.WebProducer +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/Startup.cs b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/Startup.cs new file mode 100644 index 0000000000..a7a1547498 --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/Startup.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Epiphany.Kafka.WebProducer +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseMvc(); + } + } +} diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/k8s/configmap.yaml b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/k8s/configmap.yaml new file mode 100644 index 0000000000..a346969985 --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/k8s/configmap.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +data: + KAFKA_AUTO_COMMIT_INVETRVAL_MS: "100" + KAFKA_AUTO_OFFSET_RESET: earliest + KAFKA_BOOTSTRAP_SERVERS: 40.67.255.155:9092,40.67.253.172:9092,40.67.253.223:9092 + KAFKA_GROUP_ID: groupidrandom + KAFKA_SESSION_TIMEOUT_MS: "30000" +kind: ConfigMap +metadata: + name: kafka + namespace: default diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/k8s/deployment.yaml b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/k8s/deployment.yaml new file mode 100644 index 0000000000..68c34898c8 --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/k8s/deployment.yaml @@ -0,0 +1,52 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: kafka-web-producer +spec: + replicas: 2 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + minReadySeconds: 5 + template: + metadata: + labels: + app: kafka-web-producer + spec: + containers: + - name: kafka-web-producer + image: your-docker-repository.azurecr.io/samples/kafka-webproducer:v6 + imagePullPolicy: Always + env: + - name: KAFKA_SESSION_TIMEOUT_MS + valueFrom: + configMapKeyRef: + name: kafka + key: KAFKA_SESSION_TIMEOUT_MS + - name: KAFKA_BOOTSTRAP_SERVERS + valueFrom: + configMapKeyRef: + name: kafka + key: KAFKA_BOOTSTRAP_SERVERS + - name: KAFKA_AUTO_COMMIT_INVETRVAL_MS + valueFrom: + configMapKeyRef: + name: kafka + key: KAFKA_AUTO_COMMIT_INVETRVAL_MS + - name: KAFKA_GROUP_ID + valueFrom: + configMapKeyRef: + name: kafka + key: KAFKA_GROUP_ID + - name: KAFKA_AUTO_OFFSET_RESET + valueFrom: + configMapKeyRef: + name: kafka + key: KAFKA_AUTO_OFFSET_RESET + + ports: + - containerPort: 80 + imagePullSecrets: + - name: registrysecretname diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/k8s/pod.yaml b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/k8s/pod.yaml new file mode 100644 index 0000000000..f261b9d16f --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/k8s/pod.yaml @@ -0,0 +1,38 @@ +apiVersion: v1 +kind: Pod +metadata: + name: kafka-web-producer-pod +spec: + containers: + - name: kafka-web-producer + image: your-docker-repository.azurecr.io/examples/kafka-webpducer:v2 + imagePullPolicy: Always + env: + - name: KAFKA_SESSION_TIMEOUT_MS + valueFrom: + configMapKeyRef: + name: kafka + key: KAFKA_SESSION_TIMEOUT_MS + - name: KAFKA_BOOTSTRAP_SERVERS + valueFrom: + configMapKeyRef: + name: kafka + key: KAFKA_BOOTSTRAP_SERVERS + - name: KAFKA_AUTO_COMMIT_INVETRVAL_MS + valueFrom: + configMapKeyRef: + name: kafka + key: KAFKA_AUTO_COMMIT_INVETRVAL_MS + - name: KAFKA_GROUP_ID + valueFrom: + configMapKeyRef: + name: kafka + key: KAFKA_GROUP_ID + - name: KAFKA_AUTO_OFFSET_RESET + valueFrom: + configMapKeyRef: + name: kafka + key: KAFKA_AUTO_OFFSET_RESET + restartPolicy: Always + imagePullSecrets: + - name: registrysecretname diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/k8s/service.yaml b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/k8s/service.yaml new file mode 100644 index 0000000000..e9ddc6cc72 --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.Kafka.WebProducer/k8s/service.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: kafka-web-producer + labels: + version: 1.0.0 +spec: + selector: + app: kafka-web-producer + ports: + - port: 80 + nodePort: 30001 + type: NodePort diff --git a/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.sln b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.sln new file mode 100644 index 0000000000..1dbcaa15be --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/Epiphany.SampleApps.sln @@ -0,0 +1,62 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2042 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Epiphany.SampleApps.Kafka.Consumer", "Epiphany.SampleApps.Kafka.Consumer\Epiphany.SampleApps.Kafka.Consumer.csproj", "{26CD24AA-8277-43EF-9DE4-7499A0C221C6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Epiphany.SampleApps.Kafka.Producer", "Epiphany.SampleApps.Kafka.Producer\Epiphany.SampleApps.Kafka.Producer.csproj", "{F5C363FE-A203-4965-AD95-8A3720D0DC50}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Epiphany.SampleApps.Kafka.WebProducer", "Epiphany.SampleApps.Kafka.WebProducer\Epiphany.SampleApps.Kafka.WebProducer.csproj", "{6EFCB5B2-80EA-4204-8E7E-144015A43DA6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Kafka", "Kafka", "{D91EC406-4433-405C-9686-A55EBD5402F1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CronJob", "CronJob", "{319C0193-6840-476F-B0F3-ADA1BE1A0D8C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Epiphany.SampleApps.CronApp", "Epiphany.SampleApps.CronApp\Epiphany.SampleApps.CronApp.csproj", "{9087F972-A43A-457A-BFF6-449675452537}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authentication", "Authentication", "{888EDF56-05F1-4B1C-BA8E-D9EEFB0848E2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Epiphany.SampleApps.AuthService", "Epiphany.SampleApps.AuthService\Epiphany.SampleApps.AuthService.csproj", "{8338EB38-63DB-4757-BF94-461BE3034B83}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {26CD24AA-8277-43EF-9DE4-7499A0C221C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {26CD24AA-8277-43EF-9DE4-7499A0C221C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {26CD24AA-8277-43EF-9DE4-7499A0C221C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {26CD24AA-8277-43EF-9DE4-7499A0C221C6}.Release|Any CPU.Build.0 = Release|Any CPU + {F5C363FE-A203-4965-AD95-8A3720D0DC50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5C363FE-A203-4965-AD95-8A3720D0DC50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5C363FE-A203-4965-AD95-8A3720D0DC50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5C363FE-A203-4965-AD95-8A3720D0DC50}.Release|Any CPU.Build.0 = Release|Any CPU + {6EFCB5B2-80EA-4204-8E7E-144015A43DA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6EFCB5B2-80EA-4204-8E7E-144015A43DA6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6EFCB5B2-80EA-4204-8E7E-144015A43DA6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6EFCB5B2-80EA-4204-8E7E-144015A43DA6}.Release|Any CPU.Build.0 = Release|Any CPU + {9087F972-A43A-457A-BFF6-449675452537}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9087F972-A43A-457A-BFF6-449675452537}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9087F972-A43A-457A-BFF6-449675452537}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9087F972-A43A-457A-BFF6-449675452537}.Release|Any CPU.Build.0 = Release|Any CPU + {8338EB38-63DB-4757-BF94-461BE3034B83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8338EB38-63DB-4757-BF94-461BE3034B83}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8338EB38-63DB-4757-BF94-461BE3034B83}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8338EB38-63DB-4757-BF94-461BE3034B83}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {26CD24AA-8277-43EF-9DE4-7499A0C221C6} = {D91EC406-4433-405C-9686-A55EBD5402F1} + {F5C363FE-A203-4965-AD95-8A3720D0DC50} = {D91EC406-4433-405C-9686-A55EBD5402F1} + {6EFCB5B2-80EA-4204-8E7E-144015A43DA6} = {D91EC406-4433-405C-9686-A55EBD5402F1} + {9087F972-A43A-457A-BFF6-449675452537} = {319C0193-6840-476F-B0F3-ADA1BE1A0D8C} + {8338EB38-63DB-4757-BF94-461BE3034B83} = {888EDF56-05F1-4B1C-BA8E-D9EEFB0848E2} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {94EB69D6-E84E-4E10-8D27-FA9FBB02ABF5} + EndGlobalSection +EndGlobal diff --git a/examples/dotnet/Epiphany.SampleApps/README.md b/examples/dotnet/Epiphany.SampleApps/README.md new file mode 100644 index 0000000000..efd77876fd --- /dev/null +++ b/examples/dotnet/Epiphany.SampleApps/README.md @@ -0,0 +1,49 @@ +# Epiphany examples .NET Core 2.1 + +## Kafka +This readme is targeted towards developers, who want to see an example of how to use kafka. + +For .NET Core app we advise using the following + +* https://github.com/confluentinc/confluent-kafka-dotnet (https://www.nuget.org/packages/Confluent.Kafka/) + +### Example.Kafka.WebProducer + +This is a sample application that takes a POST request to /api/kafka with the follwing json + +```json +{ + "topic": "foo", + "message": "sdfgsdfgsdfgsdfgsdfgsdfgsdfg" +} +``` + +with headers: + +```text +Content-Type: application/json +``` + +And the message should be put into the queue. (1 message per queue) + + +### Example.KafkaProducer + +Simple implementation that puts 100 random messages into queue on topic ```foo``` + +### Example.KafkaConsumer + +Simple implementation that puts messages from the queue onto stdout. + +### Caveats + +1. Copy over part of /etc/hosts with public IPs from kafka Server to your PC (example. In Windows /etc/hosts are located in ```C:\Windows\System32\drivers\etc\hosts```. You need to edit them as Administrator + +```text +# Public IPs +40.67.255.155 epidevk8s-001 epidevk8s-001-public +40.67.253.172 epidevk8s-002 epidevk8s-002-public +40.67.253.223 epidevk8s-003 epidevk8s-003-public +``` + + NOTE: this is just an example file, yours will differ \ No newline at end of file diff --git a/examples/dotnet/README.md b/examples/dotnet/README.md new file mode 100644 index 0000000000..be9eacccc6 --- /dev/null +++ b/examples/dotnet/README.md @@ -0,0 +1,12 @@ +# Dotnet examples using Epiphany + +This directory contains example applications built on top of Epiphany platform. + +## Applications: + + + +- [Dotnet examples using Epiphany](#dotnet-examples-using-epiphany) + - [Applications:](#applications) + + diff --git a/examples/dotnet/epiphany-web-app/.dockerignore b/examples/dotnet/epiphany-web-app/.dockerignore new file mode 100644 index 0000000000..43e8ab1e32 --- /dev/null +++ b/examples/dotnet/epiphany-web-app/.dockerignore @@ -0,0 +1,10 @@ +.dockerignore +.env +.git +.gitignore +.vs +.vscode +docker-compose.yml +docker-compose.*.yml +*/bin +*/obj diff --git a/examples/dotnet/epiphany-web-app/.gitignore b/examples/dotnet/epiphany-web-app/.gitignore new file mode 100644 index 0000000000..55b14022aa --- /dev/null +++ b/examples/dotnet/epiphany-web-app/.gitignore @@ -0,0 +1,330 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ \ No newline at end of file diff --git a/examples/dotnet/epiphany-web-app/Dockerfile b/examples/dotnet/epiphany-web-app/Dockerfile new file mode 100644 index 0000000000..9c3ad2df9e --- /dev/null +++ b/examples/dotnet/epiphany-web-app/Dockerfile @@ -0,0 +1,17 @@ +# Stage 1 +FROM microsoft/aspnetcore-build AS builder +WORKDIR /source + +# caches restore result by copying csproj file separately +COPY *.csproj . +RUN dotnet restore + +# copies the rest of your code +COPY . . +RUN dotnet publish --output /app/ --configuration Release + +# Stage 2 +FROM microsoft/aspnetcore +WORKDIR /app +COPY --from=builder /app . +ENTRYPOINT ["dotnet", "Epiphany.Kafka.WebProducer.dll"] \ No newline at end of file diff --git a/examples/dotnet/epiphany-web-app/README.md b/examples/dotnet/epiphany-web-app/README.md new file mode 100644 index 0000000000..9f46e2c650 --- /dev/null +++ b/examples/dotnet/epiphany-web-app/README.md @@ -0,0 +1,45 @@ +# Epiphany Web App + +Dotnet core web application for Epiphany demo purposes. Solution is built from 3 important parts: + + +- [Epiphany Web App](#epiphany-web-app) + - [Docker compose](#docker-compose) + - [Kubernetes configurations](#kubernetes-configurations) + - [ASP.NET Core application](#aspnet-core-application) + + + +## Docker compose + +In epiphany-web-app solution there is docker-compose project which responsibility is to manage multi image solutions. Image is build using Dockerfile located inside epiphany-web-app project. You can use Visual Studio to build and run web project or using following command to let Docker do that. + +`docker build -f ./epiphany-web-app/Dockerfile -t epiphany-web .` + +Important thing is to set working directory to `.sln` file directory, it will be used as building context. + +When build and image you can inspect it exists using: +`docker images` + +Having image built successfully you need to tag and push image to your Docker repository. +`docker tag YOUR_IMAGE_ID your-docker-repository.io/epiphany-web` + +And then: +`docker push your-docker-repository.io/epiphany-web` + +## Kubernetes configurations + +Definition for Kubernetes deployment is located in `.yml` file. It contains information about docker image repository from which Kubernetes will pull image. Before creating a deployment, you need to update this file with Docker repository address and secret name (fields `image: your-docker-repository.azurecr.io/epiphany-web` and `- name: regcred`) + +When ready, you can apply/create deployment on kubernetes using kubectl command: + +`kubectl create -f ./epiphany-web-app/kubernetes-configs/deploy.yml` + +Successful deployment will result in availability of Epiphany Web App. Default address of app will look like following: +`http://your-master-node-address:30001` + +Port 30001 is default value, you can change it in yml file. + +## ASP.NET Core application + +Demo application build using ASP.NET and Linux image (yes, it can run both: Linux and Windows). Web application is a simple MVC app that contains useful links and graphics about Epiphany. diff --git a/examples/dotnet/epiphany-web-app/docker-compose.dcproj b/examples/dotnet/epiphany-web-app/docker-compose.dcproj new file mode 100644 index 0000000000..2915981768 --- /dev/null +++ b/examples/dotnet/epiphany-web-app/docker-compose.dcproj @@ -0,0 +1,18 @@ + + + + 2.0 + Linux + c701246f-b600-49d9-8152-bd6573e2c0db + LaunchBrowser + http://localhost:{ServicePort} + epiphany-hello-app + + + + docker-compose.yml + + + + + \ No newline at end of file diff --git a/examples/dotnet/epiphany-web-app/docker-compose.override.yml b/examples/dotnet/epiphany-web-app/docker-compose.override.yml new file mode 100644 index 0000000000..63f0a9e848 --- /dev/null +++ b/examples/dotnet/epiphany-web-app/docker-compose.override.yml @@ -0,0 +1,8 @@ +version: '3' + +services: + epiphany-web-app: + environment: + - ASPNETCORE_ENVIRONMENT=Development + ports: + - "80" diff --git a/examples/dotnet/epiphany-web-app/docker-compose.yml b/examples/dotnet/epiphany-web-app/docker-compose.yml new file mode 100644 index 0000000000..ed0a940362 --- /dev/null +++ b/examples/dotnet/epiphany-web-app/docker-compose.yml @@ -0,0 +1,8 @@ +version: '3' + +services: + epiphany-web-app: + image: epiphanywebapp + build: + context: . + dockerfile: epiphany-web-app/Dockerfile diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app.sln b/examples/dotnet/epiphany-web-app/epiphany-web-app.sln new file mode 100644 index 0000000000..d8345d2bcd --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app.sln @@ -0,0 +1,36 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27428.2011 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{C701246F-B600-49D9-8152-BD6573E2C0DB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "kubernetes-configs", "kubernetes-configs", "{484BCC1A-D133-4813-9B87-EC7365371F1D}" + ProjectSection(SolutionItems) = preProject + kubernetes-configs\deploy.yml = kubernetes-configs\deploy.yml + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "epiphany-web-app", "epiphany-web-app\epiphany-web-app.csproj", "{D25051FA-F82A-4C1F-8D15-847D157B0120}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C701246F-B600-49D9-8152-BD6573E2C0DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C701246F-B600-49D9-8152-BD6573E2C0DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C701246F-B600-49D9-8152-BD6573E2C0DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C701246F-B600-49D9-8152-BD6573E2C0DB}.Release|Any CPU.Build.0 = Release|Any CPU + {D25051FA-F82A-4C1F-8D15-847D157B0120}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D25051FA-F82A-4C1F-8D15-847D157B0120}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D25051FA-F82A-4C1F-8D15-847D157B0120}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D25051FA-F82A-4C1F-8D15-847D157B0120}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4C83216B-8CF3-4BAA-8834-A14C7AD22D9F} + EndGlobalSection +EndGlobal diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/About.cshtml b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/About.cshtml new file mode 100644 index 0000000000..3c090d15f0 --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/About.cshtml @@ -0,0 +1,9 @@ +@page +@model AboutModel +@{ + ViewData["Title"] = "About"; +} +

@ViewData["Title"]

+

@Model.Message

+ +

Use this area to provide additional information.

diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/About.cshtml.cs b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/About.cshtml.cs new file mode 100644 index 0000000000..705eb805eb --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/About.cshtml.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace EpiphanyHelloApp.Pages +{ + public class AboutModel : PageModel + { + public string Message { get; set; } + + public void OnGet() + { + Message = "Your application description page."; + } + } +} diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/Contact.cshtml b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/Contact.cshtml new file mode 100644 index 0000000000..b683c8216c --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/Contact.cshtml @@ -0,0 +1,19 @@ +@page +@model ContactModel +@{ + ViewData["Title"] = "Contact"; +} +

@ViewData["Title"]

+

@Model.Message

+ +
+ One Microsoft Way
+ Redmond, WA 98052-6399
+ P: + 425.555.0100 +
+ +
+ Support: Support@example.com
+ Marketing: Marketing@example.com +
diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/Contact.cshtml.cs b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/Contact.cshtml.cs new file mode 100644 index 0000000000..b321c17f49 --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/Contact.cshtml.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace EpiphanyHelloApp.Pages +{ + public class ContactModel : PageModel + { + public string Message { get; set; } + + public void OnGet() + { + Message = "Your contact page."; + } + } +} diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/Error.cshtml b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/Error.cshtml new file mode 100644 index 0000000000..b1f3143a42 --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/Error.cshtml @@ -0,0 +1,23 @@ +@page +@model ErrorModel +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

+ +@if (Model.ShowRequestId) +{ +

+ Request ID: @Model.RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. +

diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/Error.cshtml.cs b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/Error.cshtml.cs new file mode 100644 index 0000000000..90fdf2b960 --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/Error.cshtml.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace EpiphanyHelloApp.Pages +{ + public class ErrorModel : PageModel + { + public string RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + public void OnGet() + { + RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; + } + } +} diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/Index.cshtml b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/Index.cshtml new file mode 100644 index 0000000000..da6e52d196 --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/Index.cshtml @@ -0,0 +1,61 @@ +@page +@model IndexModel +@{ + ViewData["Title"] = "Home page"; +} + + + + diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/Index.cshtml.cs b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/Index.cshtml.cs new file mode 100644 index 0000000000..2111ed7a10 --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/Index.cshtml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace EpiphanyHelloApp.Pages +{ + public class IndexModel : PageModel + { + public void OnGet() + { + + } + } +} diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/_Layout.cshtml b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/_Layout.cshtml new file mode 100644 index 0000000000..d6b51721fb --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/_Layout.cshtml @@ -0,0 +1,65 @@ + + + + + + @ViewData["Title"] - Epiphany Hello app + + + + + + + + + + + + +
+ @RenderBody() +
+
+

© 2018 - Epiphany Team

+
+
+ + + + + + + + + + + + + @RenderSection("Scripts", required: false) + + diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/_ValidationScriptsPartial.cshtml b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/_ValidationScriptsPartial.cshtml new file mode 100644 index 0000000000..a2b13b317f --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/_ValidationScriptsPartial.cshtml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/_ViewImports.cshtml b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/_ViewImports.cshtml new file mode 100644 index 0000000000..8b46976bc0 --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using EpiphanyHelloApp +@namespace EpiphanyHelloApp.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/_ViewStart.cshtml b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/_ViewStart.cshtml new file mode 100644 index 0000000000..a5f10045db --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app/Pages/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app/Program.cs b/examples/dotnet/epiphany-web-app/epiphany-web-app/Program.cs new file mode 100644 index 0000000000..89893d91ee --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app/Program.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace EpiphanyHelloApp +{ + public class Program + { + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + public static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup() + .Build(); + } +} diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app/Startup.cs b/examples/dotnet/epiphany-web-app/epiphany-web-app/Startup.cs new file mode 100644 index 0000000000..096d23c936 --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app/Startup.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace EpiphanyHelloApp +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseBrowserLink(); + app.UseDeveloperExceptionPage(); + } + else + { + app.UseExceptionHandler("/Error"); + } + + app.UseStaticFiles(); + + app.UseMvc(); + } + } +} diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app/appsettings.Development.json b/examples/dotnet/epiphany-web-app/epiphany-web-app/appsettings.Development.json new file mode 100644 index 0000000000..fa8ce71a97 --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app/appsettings.json b/examples/dotnet/epiphany-web-app/epiphany-web-app/appsettings.json new file mode 100644 index 0000000000..5fff67bacc --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Warning" + } + } +} diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app/bundleconfig.json b/examples/dotnet/epiphany-web-app/epiphany-web-app/bundleconfig.json new file mode 100644 index 0000000000..6d3f9a57ae --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app/bundleconfig.json @@ -0,0 +1,24 @@ +// Configure bundling and minification for the project. +// More info at https://go.microsoft.com/fwlink/?LinkId=808241 +[ + { + "outputFileName": "wwwroot/css/site.min.css", + // An array of relative input file paths. Globbing patterns supported + "inputFiles": [ + "wwwroot/css/site.css" + ] + }, + { + "outputFileName": "wwwroot/js/site.min.js", + "inputFiles": [ + "wwwroot/js/site.js" + ], + // Optionally specify minification options + "minify": { + "enabled": true, + "renameLocals": true + }, + // Optionally generate .map file + "sourceMap": false + } +] diff --git a/examples/dotnet/epiphany-web-app/epiphany-web-app/epiphany-web-app.csproj b/examples/dotnet/epiphany-web-app/epiphany-web-app/epiphany-web-app.csproj new file mode 100644 index 0000000000..3f2326c178 --- /dev/null +++ b/examples/dotnet/epiphany-web-app/epiphany-web-app/epiphany-web-app.csproj @@ -0,0 +1,12 @@ + + + netcoreapp2.0 + ..\docker-compose.dcproj + + + + + + + + diff --git a/examples/dotnet/epiphany-web-app/kubernetes-configs/deploy.yml b/examples/dotnet/epiphany-web-app/kubernetes-configs/deploy.yml new file mode 100644 index 0000000000..a02a57066b --- /dev/null +++ b/examples/dotnet/epiphany-web-app/kubernetes-configs/deploy.yml @@ -0,0 +1,41 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: epiphany-web-deployment +spec: + replicas: 2 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + minReadySeconds: 5 + template: + metadata: + labels: + app: epiphany-web + spec: + containers: + - name: epiphany-web + image: your-docker-repository.azurecr.io/epiphany-web + imagePullPolicy: Always + ports: + - containerPort: 80 + nodeSelector: + beta.kubernetes.io/os: linux + imagePullSecrets: + - name: regcred +--- +apiVersion: v1 +kind: Service +metadata: + name: epiphany-web-svc + labels: + version: 1.0.0 +spec: + selector: + app: epiphany-web + ports: + - port: 80 + nodePort: 30001 + type: NodePort diff --git a/examples/keycloak/Readme.md b/examples/keycloak/Readme.md new file mode 100644 index 0000000000..0f7cc5b30a --- /dev/null +++ b/examples/keycloak/Readme.md @@ -0,0 +1,241 @@ +# KeyCloak examples readme + +This directory contains example applications built on top of keycloak. + +## Contents + +- [Introduction](#introduction) +- [Security concerns](#security-concerns) +- [Prerequisites](#prerequisites) +- [Setup test instance of KeyCloak](#setup-test-instance-of-KeyCloak) +- [Implicit flow examples](#implicit-flow-examples) + - [Implicit ReactJS SPA](#implicit-reactjs-spa) + - [Implicit Python](#implicit-python) + - [Implicit .NET Core](#implicit-.net-core) + - [Implicit Java](#implicit-java) +- [Authorization flow examples](#authorization-flow-examples) + - [Authorization ReactJS SPA](#authorization-reactjs-spa) + - [Authorization Python](#authorization-python) + - [Authorization .NET Core](#authorization-.net-core) + - [Authorization Java](#authorization-java) + +## Introduction + +This folder contains examples on how to implement authorization in [KeyCloak](https://www.keycloak.org/) using the OpenID standart in .NET core, python and Java (In progress). The examples cover the implicit and authorization flows and also show how to dail with role based access. + +An easy overview of the flows can be found [here](https://medium.com/google-cloud/understanding-oauth2-and-building-a-basic-authorization-server-of-your-own-a-beginners-guide-cf7451a16f66). + +## Security concerns + +While the examples cover implicit flow it`s not recommanded for security concerns and should be avoided. You can read more [here](https://oauth.net/2/grant-types/implicit/). + +## Prerequisites + +- **Keycloak** + - Docker +- **React** + - nodejs => 10.13.0 + - yarn => 1.12.3 + - create-react-app => 2.1.1 +- **Python** + - python 2.7.15 + - pipenv +- **Dotnet** + - .Net SDK 2.1.6 +- **Java** + - JDK => 1.8 + +## Setup test instance of KeyCloak + +This part describes the steps to setup a local KeyCloak instance for running the demos: + +1. Start the local KeyCLoak container with: + + ```bash + docker run -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin jboss/keycloak + ``` + + This will start a new container running on port 8080. It will have the user `admin` with password `admin`. + +2. Import the pre-prepared realm into the local KeyCloack container: + + - Goto and login to the administration console with the `admin` account. + - Goto `Import` and select the `realm-export.json`. Set `If a resource exists` to `Skip`. + +The master realm now has 2 clients (`demo-app-authorization`,`demo-app-implicit`) which both contain 2 roles (`Administrator`,`User`) which are used in the examples. + +Note: Users should be added manually and assigned one of the 2 (`Administrator`,`User`) client roles for testing. + +## Implicit flow examples + +### Implicit ReactJS SPA + +The .NET core, python and Java examples all relly on the same ReactJS SPA. This first needs to be build and deployed before any of the examples can be started: + +- Open a terminal here `examples/keycloak/implicit/react` +- Run the folling to provision the project: + + ```bash + yarn install + ``` +- Run the folling to build the project and copy the artifacts to the .NET core, python and Java examples: + + ```bash + yarn build + ``` + +### Implicit Python + +The Python example is based on a [flask](http://flask.pocoo.org/) and uses [JoseJWT](https://github.com/mpdavis/python-jose) for validation. + +To run the example: + +- Open a terminal here `examples/keycloak/implicit/python` +- Run the following to provision the project: + + ```bash + pipenv install + ``` +- Run the following start the app server on where it can be opened: + + ```bash + pipenv install run python + ``` + +### Implicit .NET core + +The .NET core example uses [IdentityServer4](http://docs.identityserver.io/en/latest/) todo the validation. + +To run the example: + +- Open a terminal here `examples/keycloak/implicit/dotnet/KeyCloak` +- Run the following to provision and build the project: + + ```bash + dotnet build + ``` +- Run the following start the app server on where it can be opened: + + ```bash + dotnet run + ``` + +### Implicit Java + +The Java example uses [Spring Boot](http://spring.io/projects/spring-boot) with [WebFlux](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html). + +To run the example: + +- Open a terminal here `examples/keycloak/implicit/java` +- Run the following to provision: + + ```bash + mvnw install + ``` +- Run the following start the app server on where it can be opened: + + ```bash + mvnw spring-boot:run + ``` + +## Authorization flow examples + +### Authorization ReactJS SPA + +The .NET core, python and Java examples all relly on the same ReactJS SPA. This first needs to be build and deployed before any of the examples can be started: + +- Open a terminal here `examples/keycloak/authorization/react` +- Run the folling to provision the project: + + ```bash + yarn install + ``` +- Run the folling to build the project and copy the artifacts to the .NET core, python and Java examples: + + ```bash + yarn build + ``` + +### Authorization Python + +The Python example is based on a [flask](http://flask.pocoo.org/) and uses [flas-oidc](https://flask-oidc.readthedocs.io/en/latest/) for validation. + +To run the example: + +- Open a terminal here `examples/keycloak/authorization/python` +- Run the following to provision the project: + + ```bash + pipenv install + ``` +- Run the following start the app server on where it can be opened: + + ```bash + pipenv install run python + ``` + +It might be the case that after the import of the `realm-export.json` the secret clientkey of `demo-app-authorization` needs to be reset. This can be done here in the KeyCloak administrator console: + +`Clients` > `demo-app-authorization` > `Credentials` > `Regenerate Secret` + +The new secret then needs the be update here: + +`examples/keycloak/authorization/python/appsettings.json` + +At the entry `client_secret`. + +### Authorization .NET core + +The .NET core example uses [IdentityServer4](http://docs.identityserver.io/en/latest/) todo the validation. + +To run the example: + +- Open a terminal here `examples/keycloak/authorization/dotnet/KeyCloak` +- Run the following to provision and build the project: + + ```bash + dotnet build + ``` +- Run the following start the app server on where it can be opened: + + ```bash + dotnet run + ``` + + It might be the case that after the import of the `realm-export.json` the secret clientkey of `demo-app-authorization` needs to be reset. This can be done here in the KeyCloak administrator console: + +`Clients` > `demo-app-authorization` > `Credentials` > `Regenerate Secret` + +The new secret then needs the be update here: + +`examples/keycloak/authorization/dotnet/KeyCloak/appsettings.json` + +At the entry `Jwt:ClientSecret`. + +### Authorization Java + +The Java example uses [Spring Boot](http://spring.io/projects/spring-boot) with [WebFlux](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html). + +To run the example: + +- Open a terminal here `examples/keycloak/authorization/java` +- Run the following to provision: + + ```bash + mvnw install + ``` +- Run the following start the app server on where it can be opened: + + ```bash + mvnw spring-boot:run + ``` + + It might be the case that after the import of the `realm-export.json` the secret clientkey of `demo-app-authorization` needs to be reset. This can be done here in the KeyCloak administrator console: + +`Clients` > `demo-app-authorization` > `Credentials` > `Regenerate Secret` + +The new secret then needs the be update here: + +`examples/keycloak/authorization/java/src/main/resources/application.properties` + +At the entry `keycloak.credentials.secret`. \ No newline at end of file diff --git a/examples/keycloak/authorization/dotnet/.gitignore b/examples/keycloak/authorization/dotnet/.gitignore new file mode 100644 index 0000000000..55b14022aa --- /dev/null +++ b/examples/keycloak/authorization/dotnet/.gitignore @@ -0,0 +1,330 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ \ No newline at end of file diff --git a/examples/keycloak/authorization/dotnet/KeyCloak.sln b/examples/keycloak/authorization/dotnet/KeyCloak.sln new file mode 100644 index 0000000000..6fc635f8fd --- /dev/null +++ b/examples/keycloak/authorization/dotnet/KeyCloak.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28010.2048 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeyCloak", "KeyCloak\KeyCloak.csproj", "{18B26B29-61EC-4246-A5F3-D492FB584180}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {18B26B29-61EC-4246-A5F3-D492FB584180}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {18B26B29-61EC-4246-A5F3-D492FB584180}.Debug|Any CPU.Build.0 = Debug|Any CPU + {18B26B29-61EC-4246-A5F3-D492FB584180}.Release|Any CPU.ActiveCfg = Release|Any CPU + {18B26B29-61EC-4246-A5F3-D492FB584180}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {AAE97081-2DCB-489D-8CA1-77CA9763C5A5} + EndGlobalSection +EndGlobal diff --git a/examples/keycloak/authorization/dotnet/KeyCloak/Controllers/AppController.cs b/examples/keycloak/authorization/dotnet/KeyCloak/Controllers/AppController.cs new file mode 100644 index 0000000000..f6daef36a1 --- /dev/null +++ b/examples/keycloak/authorization/dotnet/KeyCloak/Controllers/AppController.cs @@ -0,0 +1,77 @@ +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace KeyCloak.Controllers +{ + public class AppController : Controller + { + public class LoginState + { + public bool authenticated { get; set; } + } + + public class ApiToken + { + public string token { get; set; } + } + + private string GetBaseUrl() + { + var request = HttpContext.Request; + var baseUrl = string.Format("{0}://{1}", request.Scheme, request.Host); + return baseUrl; + } + + [HttpGet] + [Route("state")] + public LoginState State() + { + return new LoginState() + { + authenticated = User.Identity.IsAuthenticated + }; + } + + [HttpGet] + [Route("token")] + [Authorize()] + public ApiToken Token() + { + return new ApiToken() + { + token = HttpContext.GetTokenAsync("access_token").Result + }; + } + + [HttpGet] + [Route("login")] + [Authorize] + public IActionResult Login() + { + return Redirect(GetBaseUrl()); + } + + [HttpGet] + [Route("logout")] + [Authorize] + public async Task LogoutAsync() + { + /* + Logout has an issue where it doesnt support SSO logout at this point: + https://github.com/aspnet/Security/issues/1712 + */ + await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme); + foreach (var cookie in Request.Cookies.Keys) + { + Response.Cookies.Delete(cookie); + } + return Redirect(GetBaseUrl()); + } + } +} diff --git a/examples/keycloak/authorization/dotnet/KeyCloak/Controllers/ValuesController.cs b/examples/keycloak/authorization/dotnet/KeyCloak/Controllers/ValuesController.cs new file mode 100644 index 0000000000..82658879da --- /dev/null +++ b/examples/keycloak/authorization/dotnet/KeyCloak/Controllers/ValuesController.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using System.Security.Claims; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace KeyCloak.Controllers +{ + [Route("api/[controller]")] + public class ValuesController : Controller + { + public class Item + { + public int id { get; set; } + public string value { get; set; } + } + + private static List data = new List + { + new Item(){ + id = 1, + value = "1" + }, + new Item(){ + id = 2, + value = "2" + }, + new Item(){ + id = 3, + value = "3" + }, + new Item(){ + id = 4, + value = "4" + }, + }; + + public ValuesController() + { + } + + [HttpGet] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Policy = "Administrator")] + public IEnumerable Get() + { + return data; + } + + [HttpGet("{id}")] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + public Item Get(int id) + { + return new Item() + { + id = id, + value = "" + id + }; + } + } +} diff --git a/examples/keycloak/authorization/dotnet/KeyCloak/KeyCloak.csproj b/examples/keycloak/authorization/dotnet/KeyCloak/KeyCloak.csproj new file mode 100644 index 0000000000..d04d4c9b50 --- /dev/null +++ b/examples/keycloak/authorization/dotnet/KeyCloak/KeyCloak.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp2.1 + true + Latest + false + ClientApp\ + $(DefaultItemExcludes);$(SpaRoot)node_modules\** + + + + + + + diff --git a/examples/keycloak/authorization/dotnet/KeyCloak/Program.cs b/examples/keycloak/authorization/dotnet/KeyCloak/Program.cs new file mode 100644 index 0000000000..c8d23495d5 --- /dev/null +++ b/examples/keycloak/authorization/dotnet/KeyCloak/Program.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace KeyCloak +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/examples/keycloak/authorization/dotnet/KeyCloak/Startup.cs b/examples/keycloak/authorization/dotnet/KeyCloak/Startup.cs new file mode 100644 index 0000000000..0915a5ce9b --- /dev/null +++ b/examples/keycloak/authorization/dotnet/KeyCloak/Startup.cs @@ -0,0 +1,84 @@ +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Authorization; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Net; + +namespace KeyCloak +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + + services.AddAuthentication(options => + { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; + }) + .AddCookie("Cookies") + .AddOpenIdConnect(o => + { + o.Authority = Configuration["Jwt:Authority"]; + o.ClientId = Configuration["Jwt:Audience"]; + o.ClientSecret = Configuration["Jwt:ClientSecret"]; + o.SaveTokens = true; + o.GetClaimsFromUserInfoEndpoint = true; + o.ResponseType = OpenIdConnectResponseType.Code; + o.RequireHttpsMetadata = false; + }) + .AddJwtBearer(o => + { + o.Authority = Configuration["Jwt:Authority"]; + o.Audience = Configuration["Jwt:Audience"]; + o.RequireHttpsMetadata = false; + + o.Events = new JwtBearerEvents() + { + OnAuthenticationFailed = c => + { + c.Response.ContentType = "text/plain"; + return c.Response.WriteAsync("An error occured processing your authentication."); + } + }; + }); + + services.AddAuthorization(options => + { + options.AddPolicy("Administrator", policy => policy.RequireClaim("user_client_roles", "Administrator")); + }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseAuthentication(); + app.UseMvc(); + app.UseFileServer(); + } + } +} diff --git a/examples/keycloak/authorization/dotnet/KeyCloak/appsettings.Development.json b/examples/keycloak/authorization/dotnet/KeyCloak/appsettings.Development.json new file mode 100644 index 0000000000..5af1be3aeb --- /dev/null +++ b/examples/keycloak/authorization/dotnet/KeyCloak/appsettings.Development.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "Jwt:Authority": "http://localhost:8080/auth/realms/master", + "Jwt:Audience": "demo-app-authorization", + "Jwt:ClientSecret": "5ed7b5d6-d485-460d-9949-8bf5286efa4e" +} diff --git a/examples/keycloak/authorization/dotnet/KeyCloak/appsettings.json b/examples/keycloak/authorization/dotnet/KeyCloak/appsettings.json new file mode 100644 index 0000000000..f7dfb17355 --- /dev/null +++ b/examples/keycloak/authorization/dotnet/KeyCloak/appsettings.json @@ -0,0 +1,11 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*", + "Jwt:Authority": "http://localhost:8080/auth/realms/master", + "Jwt:Audience": "demo-app-authorization", + "Jwt:ClientSecret": "5ed7b5d6-d485-460d-9949-8bf5286efa4e" +} diff --git a/examples/keycloak/authorization/java/.gitignore b/examples/keycloak/authorization/java/.gitignore new file mode 100644 index 0000000000..f00caec38a --- /dev/null +++ b/examples/keycloak/authorization/java/.gitignore @@ -0,0 +1,28 @@ +/target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +/bin/ + +wwwroot/ \ No newline at end of file diff --git a/examples/keycloak/authorization/java/.mvn/wrapper/maven-wrapper.jar b/examples/keycloak/authorization/java/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000..01e6799737 Binary files /dev/null and b/examples/keycloak/authorization/java/.mvn/wrapper/maven-wrapper.jar differ diff --git a/examples/keycloak/authorization/java/.mvn/wrapper/maven-wrapper.properties b/examples/keycloak/authorization/java/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000..7179346716 --- /dev/null +++ b/examples/keycloak/authorization/java/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip diff --git a/examples/keycloak/authorization/java/mvnw b/examples/keycloak/authorization/java/mvnw new file mode 100644 index 0000000000..5551fde8e7 --- /dev/null +++ b/examples/keycloak/authorization/java/mvnw @@ -0,0 +1,286 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + wget "$jarUrl" -O "$wrapperJarPath" + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + curl -o "$wrapperJarPath" "$jarUrl" + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/examples/keycloak/authorization/java/mvnw.cmd b/examples/keycloak/authorization/java/mvnw.cmd new file mode 100644 index 0000000000..e5cfb0ae9e --- /dev/null +++ b/examples/keycloak/authorization/java/mvnw.cmd @@ -0,0 +1,161 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" +FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + echo Found %WRAPPER_JAR% +) else ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" + echo Finished downloading %WRAPPER_JAR% +) +@REM End of extension + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/examples/keycloak/authorization/java/pom.xml b/examples/keycloak/authorization/java/pom.xml new file mode 100644 index 0000000000..c9eaaa358b --- /dev/null +++ b/examples/keycloak/authorization/java/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.1.1.RELEASE + + + com.epiphany + keycloak + 0.0.1-SNAPSHOT + jar + keycloak + Demo project for Spring Boot + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-test + test + + + io.projectreactor + reactor-test + test + + + org.keycloak + keycloak-spring-boot-starter + + + + + + + org.keycloak.bom + keycloak-adapter-bom + 4.7.0.Final + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/examples/keycloak/authorization/java/src/main/java/com/epiphany/keycloak/KeycloakApplication.java b/examples/keycloak/authorization/java/src/main/java/com/epiphany/keycloak/KeycloakApplication.java new file mode 100644 index 0000000000..ea8dfc48cf --- /dev/null +++ b/examples/keycloak/authorization/java/src/main/java/com/epiphany/keycloak/KeycloakApplication.java @@ -0,0 +1,30 @@ +package com.epiphany.keycloak; + +import java.util.Properties; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.reactive.function.server.RouterFunctions; +import static org.springframework.web.reactive.function.server.RequestPredicates.GET; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; +import static org.springframework.http.MediaType.TEXT_HTML; + +@SpringBootApplication +public class KeycloakApplication { + public static void main(String[] args) { + SpringApplication app = new SpringApplication(KeycloakApplication.class); + + Properties properties = new Properties(); + properties.setProperty("spring.resources.static-locations", + "classpath:/wwwroot/"); + app.setDefaultProperties(properties); + app.run(args); + } +} + + diff --git a/examples/keycloak/authorization/java/src/main/java/com/epiphany/keycloak/config/SecurityConfig.java b/examples/keycloak/authorization/java/src/main/java/com/epiphany/keycloak/config/SecurityConfig.java new file mode 100644 index 0000000000..1397a0159b --- /dev/null +++ b/examples/keycloak/authorization/java/src/main/java/com/epiphany/keycloak/config/SecurityConfig.java @@ -0,0 +1,55 @@ +package com.epiphany.keycloak.config; + +import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver; +import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents; +import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider; +import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper; +import org.springframework.security.core.session.SessionRegistryImpl; +import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; +import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; + +@Configuration +@EnableWebSecurity +@ComponentScan( + basePackageClasses = KeycloakSecurityComponents.class, + excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "org.keycloak.adapters.springsecurity.management.HttpSessionManager")) +class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter { + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider(); + keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper()); + auth.authenticationProvider(keycloakAuthenticationProvider); + } + + @Bean + public KeycloakSpringBootConfigResolver KeycloakConfigResolver() { + return new KeycloakSpringBootConfigResolver(); + } + + @Bean + @Override + protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { + return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + super.configure(http); + http.authorizeRequests() + .antMatchers("/api/Values").hasRole("Administrator") + .antMatchers("/login").authenticated() + .antMatchers("/token").authenticated() + .antMatchers("/logout").authenticated() + .anyRequest() + .permitAll(); + } +} \ No newline at end of file diff --git a/examples/keycloak/authorization/java/src/main/java/com/epiphany/keycloak/controllers/AppController.java b/examples/keycloak/authorization/java/src/main/java/com/epiphany/keycloak/controllers/AppController.java new file mode 100644 index 0000000000..51f51b4b47 --- /dev/null +++ b/examples/keycloak/authorization/java/src/main/java/com/epiphany/keycloak/controllers/AppController.java @@ -0,0 +1,57 @@ +package com.epiphany.keycloak.controllers; + +import java.security.Principal; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.ServletException; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.view.RedirectView; +import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; +import org.keycloak.KeycloakPrincipal; +import org.keycloak.KeycloakSecurityContext; +import org.keycloak.representations.AccessToken; + +@Controller +public class AppController { + @RequestMapping(value = "/state", method = RequestMethod.GET) + @ResponseBody + public String state(HttpServletRequest request) { + Principal principal = request.getUserPrincipal(); + if (principal == null) { + return "{\"authenticated\": false}"; + } else { + return "{\"authenticated\": true}"; + } + } + + @RequestMapping(value = "/login", method = RequestMethod.GET) + @ResponseBody + public RedirectView login(HttpServletRequest request) { + return new RedirectView("/"); + } + + @RequestMapping(value = "/token", method = RequestMethod.GET) + @ResponseBody + public String token(HttpServletRequest request) { + KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) request.getUserPrincipal(); + KeycloakPrincipal principal = (KeycloakPrincipal)token.getPrincipal(); + KeycloakSecurityContext session = principal.getKeycloakSecurityContext(); + return "{\"token\": \"" + session.getTokenString() + "\"}"; + } + + @RequestMapping(value = "/logout", method = RequestMethod.GET) + @ResponseBody + public RedirectView logout(HttpServletRequest request) { + try { + request.logout(); + } + catch( Exception ex){ + + } + return new RedirectView("/"); + } +} \ No newline at end of file diff --git a/examples/keycloak/authorization/java/src/main/java/com/epiphany/keycloak/controllers/ValueController.java b/examples/keycloak/authorization/java/src/main/java/com/epiphany/keycloak/controllers/ValueController.java new file mode 100644 index 0000000000..d770ced083 --- /dev/null +++ b/examples/keycloak/authorization/java/src/main/java/com/epiphany/keycloak/controllers/ValueController.java @@ -0,0 +1,31 @@ +package com.epiphany.keycloak.controllers; + +import java.util.List; +import java.util.ArrayList; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.PathVariable; +import com.epiphany.keycloak.models.Value; + +@RestController +@RequestMapping("/api/Values") +class ValueController { + static final List data = new ArrayList() {{ + add(new Value(1, "1")); + add(new Value(2, "2")); + add(new Value(3, "3")); + }}; + + @GetMapping + public ResponseEntity> getAllValues() { + return ResponseEntity.ok(data); + } + + @GetMapping("/{id}") + public ResponseEntity getValueById(@PathVariable(value = "id") int valueId) { + return ResponseEntity.ok(new Value(valueId, Integer.toString(valueId))); + } +} \ No newline at end of file diff --git a/examples/keycloak/authorization/java/src/main/java/com/epiphany/keycloak/models/Value.java b/examples/keycloak/authorization/java/src/main/java/com/epiphany/keycloak/models/Value.java new file mode 100644 index 0000000000..8241a2e104 --- /dev/null +++ b/examples/keycloak/authorization/java/src/main/java/com/epiphany/keycloak/models/Value.java @@ -0,0 +1,28 @@ +package com.epiphany.keycloak.models; + +import java.io.Serializable; + +public class Value implements Serializable { + int id; + String value; + + public Value(int id, String value) { + this.id = id; + this.value = value; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getValue() { + return value; + } + public void setValue(String value) { + this.value = value; + } +} \ No newline at end of file diff --git a/examples/keycloak/authorization/java/src/main/resources/application.properties b/examples/keycloak/authorization/java/src/main/resources/application.properties new file mode 100644 index 0000000000..d2e822ef1f --- /dev/null +++ b/examples/keycloak/authorization/java/src/main/resources/application.properties @@ -0,0 +1,10 @@ +server.port = 8090 + +spring.main.allow-bean-definition-overriding=true + +keycloak.auth-server-url=http://localhost:8080/auth +keycloak.realm=master +keycloak.resource=demo-app-authorization +keycloak.public-client=false +keycloak.credentials.secret=ff54c30d-dc81-4759-9c1e-44d7e50a2475 +keycloak.use-resource-role-mappings=true \ No newline at end of file diff --git a/examples/keycloak/authorization/java/src/test/java/com/epiphany/keycloak/KeycloakApplicationTests.java b/examples/keycloak/authorization/java/src/test/java/com/epiphany/keycloak/KeycloakApplicationTests.java new file mode 100644 index 0000000000..ab98480f38 --- /dev/null +++ b/examples/keycloak/authorization/java/src/test/java/com/epiphany/keycloak/KeycloakApplicationTests.java @@ -0,0 +1,16 @@ +package com.epiphany.keycloak; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class KeycloakApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/examples/keycloak/authorization/python/.gitignore b/examples/keycloak/authorization/python/.gitignore new file mode 100644 index 0000000000..00c6e05f53 --- /dev/null +++ b/examples/keycloak/authorization/python/.gitignore @@ -0,0 +1 @@ +wwwroot/ \ No newline at end of file diff --git a/examples/keycloak/authorization/python/Pipfile b/examples/keycloak/authorization/python/Pipfile new file mode 100644 index 0000000000..3ea3802baf --- /dev/null +++ b/examples/keycloak/authorization/python/Pipfile @@ -0,0 +1,14 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] +flask = "*" +flask-oidc = "*" +requests = "*" + +[requires] +python_version = "2.7" diff --git a/examples/keycloak/authorization/python/Pipfile.lock b/examples/keycloak/authorization/python/Pipfile.lock new file mode 100644 index 0000000000..b3c8c67c53 --- /dev/null +++ b/examples/keycloak/authorization/python/Pipfile.lock @@ -0,0 +1,174 @@ +{ + "_meta": { + "hash": { + "sha256": "290bff0fe110917cedfc90fca9ced0d4680bfb533119b5de16b1d8bb6deded9e" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "2.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", + "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" + ], + "version": "==2018.10.15" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "click": { + "hashes": [ + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + ], + "version": "==7.0" + }, + "flask": { + "hashes": [ + "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", + "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" + ], + "index": "pypi", + "version": "==1.0.2" + }, + "flask-oidc": { + "hashes": [ + "sha256:0c12151139d47a562e1c5ae203fb9dbc759fe7474cc01e0238bef828ece58f4e" + ], + "index": "pypi", + "version": "==1.4.0" + }, + "httplib2": { + "hashes": [ + "sha256:f61fb838a94ce3b349aa32c92fd8430f7e3511afdb18bf9640d647e30c90a6d6" + ], + "version": "==0.12.0" + }, + "idna": { + "hashes": [ + "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", + "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + ], + "version": "==2.7" + }, + "itsdangerous": { + "hashes": [ + "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", + "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" + ], + "version": "==1.1.0" + }, + "jinja2": { + "hashes": [ + "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", + "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" + ], + "version": "==2.10" + }, + "markupsafe": { + "hashes": [ + "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", + "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", + "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", + "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", + "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", + "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", + "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", + "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", + "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", + "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", + "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", + "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", + "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", + "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", + "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", + "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", + "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", + "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", + "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", + "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", + "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", + "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", + "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", + "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", + "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", + "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", + "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", + "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" + ], + "version": "==1.1.0" + }, + "oauth2client": { + "hashes": [ + "sha256:b8a81cc5d60e2d364f0b1b98f958dbd472887acaf1a5b05e21c28c31a2d6d3ac", + "sha256:d486741e451287f69568a4d26d70d9acd73a2bbfa275746c535b4209891cccc6" + ], + "version": "==4.1.3" + }, + "pyasn1": { + "hashes": [ + "sha256:b9d3abc5031e61927c82d4d96c1cec1e55676c1a991623cfed28faea73cdd7ca", + "sha256:f58f2a3d12fd754aa123e9fa74fb7345333000a035f3921dbdaa08597aa53137" + ], + "version": "==0.4.4" + }, + "pyasn1-modules": { + "hashes": [ + "sha256:a0cf3e1842e7c60fde97cb22d275eb6f9524f5c5250489e292529de841417547", + "sha256:a38a8811ea784c0136abfdba73963876328f66172db21a05a82f9515909bfb4e" + ], + "version": "==0.2.2" + }, + "requests": { + "hashes": [ + "sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54", + "sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263" + ], + "index": "pypi", + "version": "==2.20.1" + }, + "rsa": { + "hashes": [ + "sha256:14ba45700ff1ec9eeb206a2ce76b32814958a98e372006c8fb76ba820211be66", + "sha256:1a836406405730121ae9823e19c6e806c62bbad73f890574fff50efa4122c487" + ], + "version": "==4.0" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + }, + "urllib3": { + "hashes": [ + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + ], + "version": "==1.24.1" + }, + "werkzeug": { + "hashes": [ + "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c", + "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b" + ], + "version": "==0.14.1" + } + }, + "develop": {} +} diff --git a/examples/keycloak/authorization/python/appsettings.json b/examples/keycloak/authorization/python/appsettings.json new file mode 100644 index 0000000000..ad28e62b10 --- /dev/null +++ b/examples/keycloak/authorization/python/appsettings.json @@ -0,0 +1,15 @@ +{ + "web": { + "issuer": "http://localhost:8080/auth/realms/master", + "auth_uri": "http://localhost:8080/auth/realms/master/protocol/openid-connect/auth", + "client_id": "demo-app-authorization", + "client_secret": "5ed7b5d6-d485-460d-9949-8bf5286efa4e", + "redirect_uris": [ + "http://localhost:8090/*" + ], + "userinfo_uri": "http://localhost:8080/auth/realms/master/protocol/openid-connect/userinfo", + "token_uri": "http://localhost:8080/auth/realms/master/protocol/openid-connect/token", + "token_introspection_uri": "http://localhost:8080/auth/realms/master/protocol/openid-connect/token/introspect", + "bearer_only": "true" + } + } \ No newline at end of file diff --git a/examples/keycloak/authorization/python/main.py b/examples/keycloak/authorization/python/main.py new file mode 100644 index 0000000000..267d0e2589 --- /dev/null +++ b/examples/keycloak/authorization/python/main.py @@ -0,0 +1,85 @@ +import json +import logging +from functools import wraps +from base64 import b64decode +from flask import Flask, g, redirect, abort +from flask_oidc import OpenIDConnect + +logging.basicConfig(level=logging.DEBUG) + +app = Flask(__name__, static_url_path='', static_folder='wwwroot') +app.config.update({ + 'SECRET_KEY': 'SomethingNotEntirelySecret', + 'TESTING': True, + 'DEBUG': True, + 'OIDC_CLIENT_SECRETS': 'appsettings.json', + 'OIDC_ID_TOKEN_COOKIE_SECURE': False, + 'OIDC_REQUIRE_VERIFIED_EMAIL': False, + 'OIDC_INTROSPECTION_AUTH_METHOD': 'client_secret_post', + 'OIDC_TOKEN_TYPE_HINT': 'access_token' +}) +oidc = OpenIDConnect(app) + +data = [ + {"id":1,"value":"1"}, + {"id":2,"value":"2"}, + {"id":3,"value":"3"}, + {"id":4,"value":"4"} +] + +def require_keycloak_role(client, role): + def wrapper(view_func): + @wraps(view_func) + def decorated(*args, **kwargs): + pre, tkn, post = oidc.get_access_token().split('.') + access_token = json.loads(b64decode(tkn + "===")) + if role in access_token['resource_access'][client]['roles']: + return view_func(*args, **kwargs) + else: + return abort(403) + return decorated + return wrapper + +@app.route('/') +def root(): + return app.send_static_file('index.html') + +@app.route('/login') +@oidc.require_login +def login(): + return redirect('/') + +@app.route('/logout') +def logout(): + ''' + Logout has an issue where it doesnt support SSO logout at this point: + https://github.com/puiterwijk/flask-oidc/issues/5#issuecomment-86187023 + ''' + oidc.logout() + return redirect('/') + +@app.route('/state') +def state(): + return json.dumps({'authenticated': oidc.user_loggedin}) + +@app.route('/token') +@oidc.require_login +def token(): + if oidc.user_loggedin: + return json.dumps({'token': oidc.get_access_token()}) + else: + return abort(403) + +@app.route('/api/Values/', methods=['GET']) +@oidc.accept_token(require_token=True) +def value(id): + return json.dumps({id: id}) + +@app.route('/api/Values', methods=['GET']) +@oidc.accept_token(require_token=True) +@require_keycloak_role('demo-app-authorization', 'Administrator') +def values(): + return json.dumps(data) + +if __name__ == '__main__': + app.run(port='8090') \ No newline at end of file diff --git a/examples/keycloak/authorization/react/.gitignore b/examples/keycloak/authorization/react/.gitignore new file mode 100644 index 0000000000..4d29575de8 --- /dev/null +++ b/examples/keycloak/authorization/react/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/examples/keycloak/authorization/react/package.json b/examples/keycloak/authorization/react/package.json new file mode 100644 index 0000000000..1f3885145f --- /dev/null +++ b/examples/keycloak/authorization/react/package.json @@ -0,0 +1,30 @@ +{ + "name": "keycloak-react", + "version": "0.1.0", + "private": true, + "dependencies": { + "react": "^16.6.3", + "react-dom": "^16.6.3", + "react-scripts": "2.1.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build && npm run copy-python && npm run copy-dotnet && npm run copy-java", + "copy-dotnet": "rimraf ../dotnet/KeyCloak/wwwroot/* && copyfiles -u 1 build/**/*.* ../dotnet/KeyCloak/wwwroot", + "copy-python": "rimraf ../python/wwwroot/* && copyfiles -u 1 build/**/*.* ../python/wwwroot", + "copy-java": "rimraf ../java/src/main/resources/wwwroot/* && copyfiles -u 1 build/**/*.* ../java/src/main/resources/wwwroot" + }, + "eslintConfig": { + "extends": "react-app" + }, + "devDependencies": { + "copyfiles": "^2.1.0", + "rimraf": "^2.6.2" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] +} diff --git a/examples/keycloak/authorization/react/public/favicon.ico b/examples/keycloak/authorization/react/public/favicon.ico new file mode 100644 index 0000000000..a11777cc47 Binary files /dev/null and b/examples/keycloak/authorization/react/public/favicon.ico differ diff --git a/examples/keycloak/authorization/react/public/index.html b/examples/keycloak/authorization/react/public/index.html new file mode 100644 index 0000000000..6e7c852db9 --- /dev/null +++ b/examples/keycloak/authorization/react/public/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + + Epiphany Login Service Demo + + + +
+ + + diff --git a/examples/keycloak/authorization/react/public/manifest.json b/examples/keycloak/authorization/react/public/manifest.json new file mode 100644 index 0000000000..1f2f141faf --- /dev/null +++ b/examples/keycloak/authorization/react/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/examples/keycloak/authorization/react/src/App.js b/examples/keycloak/authorization/react/src/App.js new file mode 100644 index 0000000000..6de6fcd72c --- /dev/null +++ b/examples/keycloak/authorization/react/src/App.js @@ -0,0 +1,113 @@ +import React, { Component } from 'react'; + +class App extends Component { + constructor(props) { + super(props); + this.state = {state: 'redirecting', token: null, data1: null, data2: null}; + } + + xhr( method, url, headers, callback ) { + const xhr = new XMLHttpRequest(); + xhr.open( method, window.location.origin + url ); + xhr.onreadystatechange = function() { + if ( xhr.readyState === 4 ) { + callback( xhr ); + } + }; + xhr.setRequestHeader('Accept', 'application/json'); + for(let i = 0; i < headers.length; i++) { + const header = headers[i]; + xhr.setRequestHeader(header.id, header.value); + } + xhr.send(); + }; + + loadData(path, key){ + const t = this; + let state = {}; + this.xhr('GET', + path, + [{ + "id" : "Authorization", + "value" : 'Bearer ' + t.state.token + }], + (response) => { + if (response.status === 200) { + state[key] = response.responseText; + t.setState(state); + } else if (response.status === 403) { + state[key] = 'Forbidden'; + t.setState(state); + } + } + ); + } + + login = () => { + window.location.href = window.location.origin + '/login'; + } + + logout = () => { + window.location.href = window.location.origin + '/logout'; + } + + componentDidMount() { + const t = this; + this.xhr('GET', + '/state', + [], + (response) => { + if (response.status === 200) { + if(JSON.parse(response.responseText).authenticated){ + t.setState({state: 'authenticated'}); + } else { + t.setState({state: 'not-authenticated'}); + } + t.setState({authenticated: JSON.parse(response.responseText).authenticated}); + if(t.state.state === 'authenticated') { + this.xhr('GET', + '/token', + [], + (response) => { + if (response.status === 200) { + t.setState({token: JSON.parse(response.responseText).token}); + this.loadData('/api/Values/1', 'data1'); + this.loadData('/api/Values', 'data2'); + } + } + ); + } + } + } + ); + } + + render() { + if (this.state.state === 'authenticated' ) { + return ( +
+

Authenticated!

+ +

Token:

+